mirror of
https://github.com/stupidcomputer/jsfw.git
synced 2024-12-26 21:42:23 -06:00
commit
3dacc54ef1
@ -3,6 +3,7 @@ BasedOnStyle: LLVM
|
|||||||
IndentWidth: 4
|
IndentWidth: 4
|
||||||
AlignConsecutiveDeclarations: true
|
AlignConsecutiveDeclarations: true
|
||||||
AlignConsecutiveAssignments: true
|
AlignConsecutiveAssignments: true
|
||||||
|
AlignArrayOfStructures: Left
|
||||||
PointerAlignment: Right
|
PointerAlignment: Right
|
||||||
ColumnLimit: 110
|
ColumnLimit: 130
|
||||||
IncludeBlocks: Regroup
|
IncludeBlocks: Regroup
|
||||||
|
11
Makefile
11
Makefile
@ -1,16 +1,23 @@
|
|||||||
Q=@
|
Q=@
|
||||||
CC=gcc
|
CC=gcc
|
||||||
CFLAGS=-g -Wall -Wno-format-truncation -pthread -lm
|
|
||||||
|
GCCCFLAGS=-Wno-format-truncation
|
||||||
|
CFLAGS=-std=c11 -pedantic -g -Wall -pthread -D_GNU_SOURCE
|
||||||
LDFLAGS=-lm
|
LDFLAGS=-lm
|
||||||
|
|
||||||
BUILD_DIR=./objects
|
BUILD_DIR=./objects
|
||||||
BIN=jsfw
|
BIN=jsfw
|
||||||
|
|
||||||
RUNARGS=client localhost 7776
|
RUNARGS=server 7776 ./server_config.json
|
||||||
|
|
||||||
SOURCES=$(wildcard *.c)
|
SOURCES=$(wildcard *.c)
|
||||||
|
|
||||||
OBJECTS:=$(patsubst %.c,$(BUILD_DIR)/%.o,$(SOURCES))
|
OBJECTS:=$(patsubst %.c,$(BUILD_DIR)/%.o,$(SOURCES))
|
||||||
|
|
||||||
|
ifeq ($(CC),gcc)
|
||||||
|
CFLAGS:=$(CFLAGS) $(GCCCFLAGS)
|
||||||
|
endif
|
||||||
|
|
||||||
.PHONY: run
|
.PHONY: run
|
||||||
run: $(BIN)
|
run: $(BIN)
|
||||||
@echo "RUN $(BIN) $(RUNARGS)"
|
@echo "RUN $(BIN) $(RUNARGS)"
|
||||||
|
175
README.md
175
README.md
@ -1,38 +1,6 @@
|
|||||||
# jsfw
|
# jsfw
|
||||||
|
|
||||||
Utility to forward uevent devices over network through a tcp connection.
|
Utility to forward evdev devices over network through a tcp connection.
|
||||||
|
|
||||||
# Usage
|
|
||||||
|
|
||||||
Start client:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
jsfw client [server address] [server port]
|
|
||||||
```
|
|
||||||
|
|
||||||
Start server:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
jsfw server [port]
|
|
||||||
```
|
|
||||||
|
|
||||||
When a device is connected to the server host, jsfw will notice it and assign it to one of the client which will in turn create a virtual device based on it.
|
|
||||||
|
|
||||||
The code can theoretically support any kind of device (mouse, keyboard, joystick...) but is artificially limited to PS4 controllers (see `hid.c::filter_event`), because the hidraw interface used to set additional device state (led color, flashing, rumble) only works with them. This could be easily edited tho (see `hid.c::apply_controller_state`, `net.h::MessageControllerState`, `net.c::{msg_serialize, msg_deserialize}` and `client.c::JControllerState`). To set the controller state from the client write the json state to the fifo (by default `/tmp/jsfw_fifo`).
|
|
||||||
|
|
||||||
The format for the controller state takes this form (comments not allowed):
|
|
||||||
|
|
||||||
```jsonc
|
|
||||||
{
|
|
||||||
"led_color": "#ff0000", // hex color string
|
|
||||||
"flash": [0.04, 0.11], // values are 0-1, first is time on second is time off
|
|
||||||
"rumble": [0, 0], // values are 0-1
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Any value can be ommitted, extra values will be ignored.
|
|
||||||
|
|
||||||
Some aspect are easily configurable through `const.c`.
|
|
||||||
|
|
||||||
# Building
|
# Building
|
||||||
|
|
||||||
@ -49,3 +17,144 @@ make jsfw
|
|||||||
```
|
```
|
||||||
|
|
||||||
output will be `./jsfw`.
|
output will be `./jsfw`.
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
Jsfw works with a server and one or more clients, each client has a configuration and so does the server. The server configuration assigns tags to devices based on a set of filter, for example, you could give the "Controller" tag to devices of a certain vendor and product id. The client configuration specifies what the client wants: it is a set of tags as well as a few additional properties for the virtual devices. To illustrate that a little better, a simple setup would have a server with this configuration:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"controllers": [
|
||||||
|
{
|
||||||
|
"filter": { "vendor": "1234", "product": "0000" },
|
||||||
|
"tag": "Controller"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
and a client with this one:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"controllers": [
|
||||||
|
{
|
||||||
|
"tag": "Controller"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This would result in the server picking up any device with the vendor id `1234` and the product id `0000`, taging it as a `Controller` and forwarding it to the client when it connects.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Jsfw makes use of two json configurations to dictate the behaviour of both the server and client.
|
||||||
|
|
||||||
|
### Server
|
||||||
|
|
||||||
|
The server configuration specifies some of the server settings, as well as which devices to assign to which tag.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Any property can be ommitted, unless specified otherwise
|
||||||
|
// The values listed here are examples
|
||||||
|
{
|
||||||
|
"controllers": [
|
||||||
|
{
|
||||||
|
// (required) The tag to assign the to devices matching the filter
|
||||||
|
"tag": "Joystick",
|
||||||
|
// (default: accept any device) Requirements for a device to be assigned the tag
|
||||||
|
"filter": {
|
||||||
|
// (default: none) Optionally match the uniq of a device, expects a 17 long string of this form
|
||||||
|
"uniq": "aa:bb:cc:dd:ee:ff",
|
||||||
|
// (default: none) Optionally match the vendor code of the device, expects a 4 long hex string
|
||||||
|
"vendor": "054c",
|
||||||
|
// (default: none) Optionally match the product code of the device, expects a 4 long hex string
|
||||||
|
"product": "abcd",
|
||||||
|
// (default: false) Wether to check for a js* entry in the device tree, useful to match only the
|
||||||
|
// controller when a device has multiple events (i.e in the case of a ps4 controller one device has
|
||||||
|
// a js and is the controller, and another, with the same uniq/vendor/product, is the mouse and keyboard).
|
||||||
|
"js": true,
|
||||||
|
// (default: none) Optionally match the name of the device
|
||||||
|
"name": "Asus keyboard"
|
||||||
|
},
|
||||||
|
// Additional properties for the jsfw behaviour, some properties may act as a filter.
|
||||||
|
"properties": {
|
||||||
|
// (default: false) Wether this device can be shared by multiple client
|
||||||
|
"duplicate": true,
|
||||||
|
// (default: false) Wether the devices are dualshock 4 controllers that can be controlled
|
||||||
|
// through the hidraw interface, this allows changing the led colors from the client by writing
|
||||||
|
// json to the fifo (see client configuration). If this is enabled, any device whose hidraw interface
|
||||||
|
// can't be found will be filtered out
|
||||||
|
"ps4_hidraw": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// (default: 1s) Number of seconds between each poll for physical devices
|
||||||
|
"poll_interval": 2.5,
|
||||||
|
// (default: 2s) Number of seconds to wait for a client's request before closing the connection
|
||||||
|
"request_timeout": 10
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The client configuration specifies what devices the client wants as well as under what name/properties should they appear
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Any property can be ommitted, unless specified otherwise
|
||||||
|
// The values listed here are examples
|
||||||
|
{
|
||||||
|
"controllers": [
|
||||||
|
{
|
||||||
|
// (required) Tag of the device to request
|
||||||
|
"tag": "Joystick",
|
||||||
|
// (default: 6969) Vendor code for the virtual device, expects a 4 long hex string
|
||||||
|
"vendor": "dead",
|
||||||
|
// (default: 0420) Product code for the virtual device, expects a 4 long hex string
|
||||||
|
"product": "beef",
|
||||||
|
// (default: "JSFW Virtual Device") Name for the virtual device
|
||||||
|
"name": "Emanuel"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// (default: "/tmp/jsfw_fifo") Path to the fifo for hidraw
|
||||||
|
"fifo_path": "/tmp/gaming",
|
||||||
|
// (default: 5s) Number of seconds between retries when connecting to the server
|
||||||
|
"retry_delay": 2.5
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Additionaly messages can be sent to the client's fifo to change the led colors (and more) of ps4\_hidraw devices, these take the form:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Any property can be ommitted, unless specified otherwise
|
||||||
|
// The values listed here are examples
|
||||||
|
{
|
||||||
|
// (default: 0) Index of the device to send the state to, this is the index in the client configuration controllers list
|
||||||
|
"index": 1,
|
||||||
|
// (default: [0, 0]) Setting for the rumble, values are in range 0-255 first element is small rumble, second is big
|
||||||
|
"rumble": [255, 0],
|
||||||
|
// (default: [0, 0]) Setting for led flash, values are in range 0-255, first element is led on, second is led off
|
||||||
|
"false": [0, 255],
|
||||||
|
// (default: "#FFFFFF") Hex color for the led
|
||||||
|
"led_color": "#FF0000"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Execution
|
||||||
|
|
||||||
|
To start the server:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./jsfw server [port] [path to config]
|
||||||
|
```
|
||||||
|
|
||||||
|
To start the client:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./jsfw client [address] [port] [path to config]
|
||||||
|
```
|
||||||
|
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
lol, lmao even. (nah just issues/PR I guess)
|
||||||
|
368
client.c
368
client.c
@ -4,13 +4,13 @@
|
|||||||
#include "json.h"
|
#include "json.h"
|
||||||
#include "net.h"
|
#include "net.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
#include "vec.h"
|
||||||
|
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <linux/input-event-codes.h>
|
#include <linux/input-event-codes.h>
|
||||||
#include <linux/input.h>
|
#include <linux/input.h>
|
||||||
#include <linux/uinput.h>
|
#include <linux/uinput.h>
|
||||||
#include <math.h>
|
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
@ -25,17 +25,10 @@
|
|||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
// The current device.
|
|
||||||
// The fd being -1 means there is none
|
|
||||||
typedef struct {
|
|
||||||
int fd;
|
|
||||||
MessageDeviceInfo info;
|
|
||||||
} VirtualDevice;
|
|
||||||
|
|
||||||
static int fifo_attempt = 0;
|
static int fifo_attempt = 0;
|
||||||
|
|
||||||
static struct sockaddr_in server_addr = {};
|
static struct sockaddr_in server_addr = {0};
|
||||||
static char server_addrp[64] = {};
|
static char server_addrp[64] = {0};
|
||||||
static uint16_t server_port = -1;
|
static uint16_t server_port = -1;
|
||||||
|
|
||||||
static struct pollfd poll_fds[2];
|
static struct pollfd poll_fds[2];
|
||||||
@ -44,65 +37,122 @@ static struct pollfd *socket_poll = &poll_fds[1];
|
|||||||
static int fifo = -1;
|
static int fifo = -1;
|
||||||
static int sock = -1;
|
static int sock = -1;
|
||||||
// static to avoid having this on the stack because a message is about 2kb in memory
|
// static to avoid having this on the stack because a message is about 2kb in memory
|
||||||
static Message message;
|
static Message message;
|
||||||
static VirtualDevice device = {};
|
|
||||||
|
|
||||||
// Test if the device exists
|
static Vec devices_fd;
|
||||||
static inline bool device_exists() { return device.fd > 0; }
|
static Vec devices_info;
|
||||||
|
|
||||||
// Struct representing the received json
|
static ClientConfig config;
|
||||||
typedef struct {
|
static MessageRequest device_request;
|
||||||
char *led_color;
|
|
||||||
double rumble_small;
|
|
||||||
double rumble_big;
|
|
||||||
double flash_on;
|
|
||||||
double flash_off;
|
|
||||||
} JControllerState;
|
|
||||||
|
|
||||||
static const JSONAdapter JControllerStateAdapter[] = {
|
static void default_fifo_path(void *ptr) { *(char **)ptr = (char *)FIFO_PATH; }
|
||||||
{".led_color", String, offsetof(JControllerState, led_color)},
|
static void default_retry_delay(void *ptr) { *(struct timespec *)ptr = CONNECTION_RETRY_DELAY; }
|
||||||
{".rumble.0", Number, offsetof(JControllerState, rumble_small)},
|
static void default_vendor(void *ptr) { *(int32_t *)ptr = VIRTUAL_DEVICE_VENDOR; }
|
||||||
{".rumble.1", Number, offsetof(JControllerState, rumble_big)},
|
static void default_product(void *ptr) { *(int32_t *)ptr = VIRTUAL_DEVICE_PRODUCT; }
|
||||||
{".flash.0", Number, offsetof(JControllerState, flash_on)},
|
static void default_name(void *ptr) { *(char **)ptr = (char *)VIRTUAL_DEVICE_NAME; }
|
||||||
{".flash.1", Number, offsetof(JControllerState, flash_off)},
|
static void default_to_white(void *ptr) {
|
||||||
|
uint8_t *color = ptr;
|
||||||
|
color[0] = 255;
|
||||||
|
color[1] = 255;
|
||||||
|
color[2] = 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const JSONPropertyAdapter ControllerStateAdapterProps[] = {
|
||||||
|
{".led_color", &StringAdapter, offsetof(MessageControllerState, led), default_to_white, tsf_hex_to_color },
|
||||||
|
{".rumble.0", &NumberAdapter, offsetof(MessageControllerState, small_rumble), default_to_zero_u8, tsf_num_to_u8_clamp},
|
||||||
|
{".rumble.1", &NumberAdapter, offsetof(MessageControllerState, big_rumble), default_to_zero_u8, tsf_num_to_u8_clamp},
|
||||||
|
{".flash.0", &NumberAdapter, offsetof(MessageControllerState, flash_on), default_to_zero_u8, tsf_num_to_u8_clamp},
|
||||||
|
{".flash.1", &NumberAdapter, offsetof(MessageControllerState, flash_off), default_to_zero_u8, tsf_num_to_u8_clamp},
|
||||||
|
{".index", &NumberAdapter, offsetof(MessageControllerState, index), default_to_zero_u32, tsf_num_to_int }
|
||||||
|
};
|
||||||
|
static const JSONAdapter ControllerStateAdapter = {
|
||||||
|
.props = (JSONPropertyAdapter *)ControllerStateAdapterProps,
|
||||||
|
.prop_count = sizeof(ControllerStateAdapterProps) / sizeof(JSONPropertyAdapter),
|
||||||
|
.size = sizeof(MessageControllerState),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Try to destroy the device
|
static const JSONPropertyAdapter ControllerAdapterProps[] = {
|
||||||
void device_destroy() {
|
{".tag", &StringAdapter, offsetof(ClientController, tag), default_to_null, NULL },
|
||||||
if (!device_exists()) {
|
{".vendor", &StringAdapter, offsetof(ClientController, device_vendor), default_vendor, tsf_hex_to_i32},
|
||||||
|
{".product", &StringAdapter, offsetof(ClientController, device_product), default_product, tsf_hex_to_i32},
|
||||||
|
{".name", &StringAdapter, offsetof(ClientController, device_name), default_name, NULL },
|
||||||
|
};
|
||||||
|
static const JSONAdapter ControllerAdapter = {
|
||||||
|
.props = ControllerAdapterProps,
|
||||||
|
.prop_count = sizeof(ControllerAdapterProps) / sizeof(JSONPropertyAdapter),
|
||||||
|
.size = sizeof(ClientController),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const JSONPropertyAdapter ClientConfigAdapterProps[] = {
|
||||||
|
{".controllers[]", &ControllerAdapter, offsetof(ClientConfig, controllers), default_to_null, NULL },
|
||||||
|
{".fifo_path", &StringAdapter, offsetof(ClientConfig, fifo_path), default_fifo_path, NULL },
|
||||||
|
{".retry_delay", &NumberAdapter, offsetof(ClientConfig, retry_delay), default_retry_delay, tsf_numsec_to_timespec}
|
||||||
|
};
|
||||||
|
static const JSONAdapter ConfigAdapter = {
|
||||||
|
.props = ClientConfigAdapterProps,
|
||||||
|
.prop_count = sizeof(ClientConfigAdapterProps) / sizeof(JSONPropertyAdapter),
|
||||||
|
.size = sizeof(ClientConfig),
|
||||||
|
};
|
||||||
|
|
||||||
|
void destroy_devices(void) {
|
||||||
|
for (int i = 0; i < config.controller_count; i++) {
|
||||||
|
int fd = *(int *)vec_get(&devices_fd, i);
|
||||||
|
MessageDeviceInfo *info = vec_get(&devices_info, i);
|
||||||
|
|
||||||
|
if (info->code == DeviceInfo) {
|
||||||
|
ioctl(fd, UI_DEV_DESTROY);
|
||||||
|
info->code = NoMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool device_exists(int index) {
|
||||||
|
if (index >= devices_info.len) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageDeviceInfo *info = vec_get(&devices_info, index);
|
||||||
|
return info->code == DeviceInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
void device_destroy(int index) {
|
||||||
|
if (index >= devices_info.len) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ioctl(device.fd, UI_DEV_DESTROY);
|
int fd = *(int *)vec_get(&devices_fd, index);
|
||||||
close(device.fd);
|
|
||||||
device.fd = -1;
|
MessageDeviceInfo *info = vec_get(&devices_info, index);
|
||||||
printf("CLIENT: Destroyed device\n");
|
|
||||||
|
if (info->code == DeviceInfo) {
|
||||||
|
ioctl(fd, UI_DEV_DESTROY);
|
||||||
|
info->code = NoMessage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// (Re)Initialize the device
|
|
||||||
void device_init(MessageDeviceInfo *dev) {
|
void device_init(MessageDeviceInfo *dev) {
|
||||||
device_destroy();
|
if (dev->index >= devices_info.len) {
|
||||||
|
printf("CLIENT: Got wrong device index\n");
|
||||||
int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
|
return;
|
||||||
if (fd < 0) {
|
|
||||||
perror("CLIENT: Error while opening /dev/uinput, ");
|
|
||||||
exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup device_info
|
device_destroy(dev->index);
|
||||||
|
|
||||||
|
int fd = *(int *)vec_get(&devices_fd, dev->index);
|
||||||
|
|
||||||
// Abs
|
// Abs
|
||||||
if (dev->abs_count > 0) {
|
if (dev->abs_count > 0) {
|
||||||
ioctl(fd, UI_SET_EVBIT, EV_ABS);
|
ioctl(fd, UI_SET_EVBIT, EV_ABS);
|
||||||
for (int i = 0; i < dev->abs_count; i++) {
|
for (int i = 0; i < dev->abs_count; i++) {
|
||||||
struct uinput_abs_setup setup = {};
|
struct uinput_abs_setup setup = {0};
|
||||||
setup.code = dev->abs_id[i];
|
|
||||||
setup.absinfo.minimum = dev->abs_min[i];
|
setup.code = dev->abs_id[i];
|
||||||
setup.absinfo.maximum = dev->abs_max[i];
|
setup.absinfo.minimum = dev->abs_min[i];
|
||||||
setup.absinfo.fuzz = dev->abs_fuzz[i];
|
setup.absinfo.maximum = dev->abs_max[i];
|
||||||
setup.absinfo.flat = dev->abs_flat[i];
|
setup.absinfo.fuzz = dev->abs_fuzz[i];
|
||||||
setup.absinfo.resolution = dev->abs_res[i];
|
setup.absinfo.flat = dev->abs_flat[i];
|
||||||
setup.absinfo.value = 0;
|
setup.absinfo.resolution = dev->abs_res[i];
|
||||||
|
setup.absinfo.value = 0;
|
||||||
ioctl(fd, UI_ABS_SETUP, &setup);
|
ioctl(fd, UI_ABS_SETUP, &setup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,78 +173,106 @@ void device_init(MessageDeviceInfo *dev) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct uinput_setup setup = {};
|
ClientController *ctr = &config.controllers[dev->index];
|
||||||
|
|
||||||
|
struct uinput_setup setup = {0};
|
||||||
|
|
||||||
setup.id.bustype = BUS_VIRTUAL;
|
setup.id.bustype = BUS_VIRTUAL;
|
||||||
setup.id.vendor = VIRTUAL_DEVICE_VENDOR;
|
setup.id.vendor = ctr->device_vendor;
|
||||||
setup.id.product = VIRTUAL_DEVICE_PRODUCT;
|
setup.id.product = ctr->device_product;
|
||||||
setup.id.version = VIRTUAL_DEVICE_VERSION;
|
setup.id.version = VIRTUAL_DEVICE_VERSION;
|
||||||
strncpy(setup.name, VIRTUAL_DEVICE_NAME, UINPUT_MAX_NAME_SIZE);
|
strncpy(setup.name, ctr->device_name, UINPUT_MAX_NAME_SIZE);
|
||||||
|
|
||||||
ioctl(fd, UI_DEV_SETUP, &setup);
|
ioctl(fd, UI_DEV_SETUP, &setup);
|
||||||
ioctl(fd, UI_DEV_CREATE);
|
ioctl(fd, UI_DEV_CREATE);
|
||||||
|
|
||||||
device.fd = fd;
|
MessageDeviceInfo *dst = vec_get(&devices_info, dev->index);
|
||||||
memcpy(&device.info, dev, sizeof(MessageDeviceInfo));
|
|
||||||
printf("CLIENT: Created device (abs: %d, rel: %d, key: %d)\n", dev->abs_count, dev->rel_count,
|
memcpy(dst, dev, sizeof(MessageDeviceInfo));
|
||||||
dev->key_count);
|
printf("CLIENT: Got device [%d]: '%s' (abs: %d, rel: %d, key: %d)\n", dev->index, ctr->device_name, dev->abs_count,
|
||||||
|
dev->rel_count, dev->key_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send an event to uinput, device must exist
|
// Send an event to uinput, device must exist
|
||||||
int device_emit(uint16_t type, uint16_t id, uint32_t value) {
|
bool device_emit(int index, uint16_t type, uint16_t id, uint32_t value) {
|
||||||
struct input_event event = {};
|
if (index >= devices_fd.len) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fd = *(int *)vec_get(&devices_fd, index);
|
||||||
|
struct input_event event = {0};
|
||||||
|
|
||||||
event.type = type;
|
event.type = type;
|
||||||
event.code = id;
|
event.code = id;
|
||||||
event.value = value;
|
event.value = value;
|
||||||
|
|
||||||
return write(device.fd, &event, sizeof(event)) != sizeof(event);
|
return write(fd, &event, sizeof(event)) != sizeof(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update device with report
|
// Update device with report
|
||||||
void device_handle_report(MessageDeviceReport *report) {
|
void device_handle_report(MessageDeviceReport *report) {
|
||||||
if (!device_exists()) {
|
if (!device_exists(report->index)) {
|
||||||
printf("CLIENT: Got report before device info\n");
|
printf("CLIENT: [%d] Got report before device info\n", report->index);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (report->abs_count != device.info.abs_count || report->rel_count != device.info.rel_count ||
|
MessageDeviceInfo *info = vec_get(&devices_info, report->index);
|
||||||
report->key_count != device.info.key_count) {
|
|
||||||
|
if (report->abs_count != info->abs_count || report->rel_count != info->rel_count || report->key_count != info->key_count) {
|
||||||
printf("CLIENT: Report doesn't match with device info\n");
|
printf("CLIENT: Report doesn't match with device info\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < report->abs_count; i++) {
|
for (int i = 0; i < report->abs_count; i++) {
|
||||||
if (device_emit(EV_ABS, device.info.abs_id[i], report->abs[i]) != 0) {
|
if (device_emit(report->index, EV_ABS, info->abs_id[i], report->abs[i]) != 0) {
|
||||||
printf("CLIENT: Error writing abs event to uinput\n");
|
printf("CLIENT: Error writing abs event to uinput\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < report->rel_count; i++) {
|
for (int i = 0; i < report->rel_count; i++) {
|
||||||
if (device_emit(EV_REL, device.info.rel_id[i], report->rel[i]) != 0) {
|
if (device_emit(report->index, EV_REL, info->rel_id[i], report->rel[i]) != 0) {
|
||||||
printf("CLIENT: Error writing rel event to uinput\n");
|
printf("CLIENT: Error writing rel event to uinput\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < report->key_count; i++) {
|
for (int i = 0; i < report->key_count; i++) {
|
||||||
if (device_emit(EV_KEY, device.info.key_id[i], (uint32_t)(!report->key[i]) - 1) != 0) {
|
if (device_emit(report->index, EV_KEY, info->key_id[i], (uint32_t)(!report->key[i]) - 1) != 0) {
|
||||||
printf("CLIENT: Error writing key event to uinput\n");
|
printf("CLIENT: Error writing key event to uinput\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Reports are sent by the server every time the server receives an EV_SYN from the physical device, so we
|
// Reports are sent by the server every time the server receives an EV_SYN from the physical device, so we
|
||||||
// send one when we receive the report to match
|
// send one when we receive the report to match
|
||||||
device_emit(EV_SYN, 0, 0);
|
device_emit(report->index, EV_SYN, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup_fifo();
|
void setup_devices(void) {
|
||||||
|
devices_fd = vec_of(int);
|
||||||
|
devices_info = vec_of(MessageDeviceInfo);
|
||||||
|
|
||||||
|
MessageDeviceInfo no_info = {0};
|
||||||
|
no_info.code = NoMessage;
|
||||||
|
|
||||||
|
for (int i = 0; i < config.controller_count; i++) {
|
||||||
|
int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
|
||||||
|
if (fd < 0) {
|
||||||
|
perror("CLIENT: Can't open /dev/uinput, aborting now");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec_push(&devices_fd, &fd);
|
||||||
|
vec_push(&devices_info, &no_info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup_fifo(void);
|
||||||
|
|
||||||
// (Re)Open the fifo
|
// (Re)Open the fifo
|
||||||
void open_fifo() {
|
void open_fifo(void) {
|
||||||
close(fifo);
|
close(fifo);
|
||||||
fifo = open(FIFO_PATH, O_RDONLY | O_NONBLOCK);
|
fifo = open(config.fifo_path, O_RDONLY | O_NONBLOCK);
|
||||||
if (fifo < 0 && fifo_attempt == 0) {
|
if (fifo < 0 && fifo_attempt == 0) {
|
||||||
fifo_attempt++;
|
fifo_attempt++;
|
||||||
unlink(FIFO_PATH);
|
unlink(config.fifo_path);
|
||||||
setup_fifo();
|
setup_fifo();
|
||||||
} else if (fifo < 0) {
|
} else if (fifo < 0) {
|
||||||
panicf("CLIENT: Couldn't open fifo, aborting\n");
|
panicf("CLIENT: Couldn't open fifo, aborting\n");
|
||||||
@ -203,9 +281,9 @@ void open_fifo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the fifo exists and opens it (also setup poll_fd)
|
// Ensure the fifo exists and opens it (also setup poll_fd)
|
||||||
void setup_fifo() {
|
void setup_fifo(void) {
|
||||||
mode_t prev = umask(0);
|
mode_t prev = umask(0);
|
||||||
mkfifo(FIFO_PATH, 0666);
|
mkfifo(config.fifo_path, 0666);
|
||||||
umask(prev);
|
umask(prev);
|
||||||
|
|
||||||
open_fifo();
|
open_fifo();
|
||||||
@ -215,12 +293,12 @@ void setup_fifo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// (Re)Connect to the server
|
// (Re)Connect to the server
|
||||||
void connect_server() {
|
void connect_server(void) {
|
||||||
while (1) {
|
while (true) {
|
||||||
if (sock > 0) {
|
if (sock > 0) {
|
||||||
// Close previous connection
|
// Close previous connection
|
||||||
device_destroy();
|
|
||||||
shutdown(sock, SHUT_RDWR);
|
shutdown(sock, SHUT_RDWR);
|
||||||
|
destroy_devices();
|
||||||
close(sock);
|
close(sock);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,11 +308,9 @@ void connect_server() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) != 0) {
|
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) != 0) {
|
||||||
printf("CLIENT: Couldn't connect to %s:%d, retrying in %ds\n", server_addrp, server_port,
|
printf("CLIENT: Couldn't connect to %s:%d, retrying in %lu.%09lus\n", server_addrp, server_port,
|
||||||
CONNECTION_RETRY_DELAY);
|
config.retry_delay.tv_sec, config.retry_delay.tv_nsec);
|
||||||
struct timespec ts = {};
|
nanosleep(&config.retry_delay, NULL);
|
||||||
ts.tv_sec = CONNECTION_RETRY_DELAY;
|
|
||||||
nanosleep(&ts, NULL);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Set non blocking, only do that after connection (instead of with SOCK_NONBLOCK at socket creation)
|
// Set non blocking, only do that after connection (instead of with SOCK_NONBLOCK at socket creation)
|
||||||
@ -242,6 +318,16 @@ void connect_server() {
|
|||||||
fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK);
|
fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK);
|
||||||
socket_poll->fd = sock;
|
socket_poll->fd = sock;
|
||||||
printf("CLIENT: Connected !\n");
|
printf("CLIENT: Connected !\n");
|
||||||
|
|
||||||
|
uint8_t buf[2048] __attribute__((aligned(4))) = {0};
|
||||||
|
|
||||||
|
int len = msg_serialize(buf, 2048, (Message *)&device_request);
|
||||||
|
if (len > 0) {
|
||||||
|
if (send(sock, buf, len, 0) > 0) {
|
||||||
|
printf("CLIENT: Sent device request\n");
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -264,21 +350,44 @@ void setup_server(char *address, uint16_t port) {
|
|||||||
connect_server();
|
connect_server();
|
||||||
}
|
}
|
||||||
|
|
||||||
void early_checks() {
|
void build_device_request(void) {
|
||||||
int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
|
char **tags = malloc(config.controller_count * sizeof(char *));
|
||||||
if (fd < 0) {
|
for (int i = 0; i < config.controller_count; i++) {
|
||||||
perror("CLIENT: Can't open /dev/uinput, aborting now: ");
|
tags[i] = config.controllers[i].tag;
|
||||||
exit(1);
|
|
||||||
}
|
}
|
||||||
close(fd);
|
|
||||||
|
device_request.code = Request;
|
||||||
|
device_request.request_count = config.controller_count;
|
||||||
|
device_request.requests = tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
void client_run(char *address, uint16_t port) {
|
void client_run(char *address, uint16_t port, char *config_path) {
|
||||||
// Device doesn't exist yet
|
// Parse the config
|
||||||
device.fd = -1;
|
{
|
||||||
|
FILE *configfd = fopen(config_path, "r");
|
||||||
|
if (configfd == NULL) {
|
||||||
|
perror("CLIENT: Couldn't open config file");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *cbuf = malloc(8192);
|
||||||
|
uint8_t *jbuf = (uint8_t *)cbuf + 4096;
|
||||||
|
|
||||||
|
int len = fread(cbuf, 1, 4096, configfd);
|
||||||
|
if (json_parse(cbuf, len, jbuf, 4096) != 0) {
|
||||||
|
printf("CLIENT: Couldn't parse config, %s (at index %lu)\n", json_strerr(), json_errloc());
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
json_adapt(jbuf, &ConfigAdapter, &config);
|
||||||
|
|
||||||
|
free(cbuf);
|
||||||
|
fclose(configfd);
|
||||||
|
}
|
||||||
|
|
||||||
early_checks();
|
|
||||||
setup_fifo();
|
setup_fifo();
|
||||||
|
build_device_request();
|
||||||
|
setup_devices();
|
||||||
setup_server(address, port);
|
setup_server(address, port);
|
||||||
|
|
||||||
uint8_t buf[2048] __attribute__((aligned(4)));
|
uint8_t buf[2048] __attribute__((aligned(4)));
|
||||||
@ -287,7 +396,7 @@ void client_run(char *address, uint16_t port) {
|
|||||||
while (1) {
|
while (1) {
|
||||||
int rc = poll(poll_fds, 2, -1);
|
int rc = poll(poll_fds, 2, -1);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
perror("CLIENT: Error on poll, ");
|
perror("CLIENT: Error on poll");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,55 +408,19 @@ void client_run(char *address, uint16_t port) {
|
|||||||
// We've got data from the fifo
|
// We've got data from the fifo
|
||||||
int rc = json_parse((char *)buf, len, json_buf, 2048);
|
int rc = json_parse((char *)buf, len, json_buf, 2048);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
printf("CLIENT: Error when parsing fifo message as json (%s at index %lu)\n",
|
printf("CLIENT: Error when parsing fifo message as json (%s at index %lu)\n", json_strerr(), json_errloc());
|
||||||
json_strerr(), json_errloc());
|
|
||||||
} else {
|
} else {
|
||||||
JControllerState state;
|
|
||||||
// default values
|
|
||||||
state.flash_off = 0.0;
|
|
||||||
state.flash_on = 0.0;
|
|
||||||
state.led_color = NULL;
|
|
||||||
state.rumble_small = 0.0;
|
|
||||||
state.rumble_big = 0.0;
|
|
||||||
|
|
||||||
json_adapt(json_buf, (JSONAdapter *)JControllerStateAdapter,
|
|
||||||
sizeof(JControllerStateAdapter) / sizeof(JSONAdapter), &state);
|
|
||||||
|
|
||||||
MessageControllerState msg;
|
MessageControllerState msg;
|
||||||
msg.code = ControllerState;
|
msg.code = ControllerState;
|
||||||
msg.small_rumble = (uint8_t)(fmax(fmin(1.0, state.rumble_small), 0.0) * 255.0);
|
json_adapt(json_buf, &ControllerStateAdapter, &msg);
|
||||||
msg.big_rumble = (uint8_t)(fmax(fmin(1.0, state.rumble_big), 0.0) * 255.0);
|
|
||||||
msg.flash_on = (uint8_t)(fmax(fmin(1.0, state.flash_on), 0.0) * 255.0);
|
|
||||||
msg.flash_off = (uint8_t)(fmax(fmin(1.0, state.flash_off), 0.0) * 255.0);
|
|
||||||
|
|
||||||
if (state.led_color == NULL || strnlen(state.led_color, 8) != 7) {
|
|
||||||
msg.led[0] = 0;
|
|
||||||
msg.led[1] = 0;
|
|
||||||
msg.led[2] = 0;
|
|
||||||
} else {
|
|
||||||
char *s = state.led_color;
|
|
||||||
msg.led[0] = parse_hex_digit(s[1]);
|
|
||||||
msg.led[0] <<= 4;
|
|
||||||
msg.led[0] += parse_hex_digit(s[2]);
|
|
||||||
|
|
||||||
msg.led[1] = parse_hex_digit(s[3]);
|
|
||||||
msg.led[1] <<= 4;
|
|
||||||
msg.led[1] += parse_hex_digit(s[4]);
|
|
||||||
|
|
||||||
msg.led[2] = parse_hex_digit(s[5]);
|
|
||||||
msg.led[2] <<= 4;
|
|
||||||
msg.led[2] += parse_hex_digit(s[6]);
|
|
||||||
|
|
||||||
free(state.led_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
int len = msg_serialize(buf, 2048, (Message *)&msg);
|
int len = msg_serialize(buf, 2048, (Message *)&msg);
|
||||||
if (len > 0) {
|
if (len > 0) {
|
||||||
if (send(sock, buf, len, 0) > 0) {
|
if (send(sock, buf, len, 0) > 0) {
|
||||||
printf("CLIENT: Sent controller state: #%02x%02x%02x flash: (%d, %d) rumble: "
|
printf("CLIENT: Sent controller state: #%02x%02x%02x flash: (%d, %d) rumble: "
|
||||||
"(%d, %d)\n",
|
"(%d, %d) -> [%d]\n",
|
||||||
msg.led[0], msg.led[1], msg.led[2], msg.flash_on, msg.flash_off,
|
msg.led[0], msg.led[1], msg.led[2], msg.flash_on, msg.flash_off, msg.small_rumble,
|
||||||
msg.small_rumble, msg.big_rumble);
|
msg.big_rumble, msg.index);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -356,19 +429,20 @@ void client_run(char *address, uint16_t port) {
|
|||||||
|
|
||||||
// A broken or closed socket produces a POLLIN event, so we check for error on the recv
|
// A broken or closed socket produces a POLLIN event, so we check for error on the recv
|
||||||
if (socket_poll->revents & POLLIN) {
|
if (socket_poll->revents & POLLIN) {
|
||||||
int len = recv(sock, buf, 2048, 0);
|
int len = recv(sock, buf, 2048, MSG_PEEK);
|
||||||
if (len <= 0) {
|
if (len <= 0) {
|
||||||
printf("CLIENT: Lost connection to server, reconnecting\n");
|
printf("CLIENT: Lost connection to server, reconnecting\n");
|
||||||
shutdown(sock, SHUT_RDWR);
|
|
||||||
connect_server();
|
connect_server();
|
||||||
// we can continue here because there's nothing after, unlike above for fifo (this reduces
|
// we can use continue here because there's nothing after, unlike above for fifo (this reduces
|
||||||
// indentation instead of needing an else block)
|
// indentation instead of needing an else block)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int msg_len = msg_deserialize(buf, len, &message);
|
||||||
// We've got data from the server
|
// We've got data from the server
|
||||||
if (msg_deserialize(buf, len, &message) != 0) {
|
if (msg_len < 0) {
|
||||||
printf("CLIENT: Couldn't parse message (code: %d, len: %d)\n", buf[0], len);
|
recv(sock, buf, 2048, 0);
|
||||||
|
printf("CLIENT: Couldn't parse message (code: %d, len: %d)\n", buf[4], len);
|
||||||
|
|
||||||
int l = len > 100 ? 100 : len;
|
int l = len > 100 ? 100 : len;
|
||||||
for (int i = 0; i < l; i++) {
|
for (int i = 0; i < l; i++) {
|
||||||
@ -383,14 +457,20 @@ void client_run(char *address, uint16_t port) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recv(sock, buf, msg_len, 0);
|
||||||
|
|
||||||
if (message.code == DeviceInfo) {
|
if (message.code == DeviceInfo) {
|
||||||
if (device_exists()) {
|
if (device_exists(message.device_info.index)) {
|
||||||
printf("CLIENT: Got more than one device info\n");
|
printf("CLIENT: Got more than one device info for same device\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
device_init((MessageDeviceInfo *)&message);
|
device_init((MessageDeviceInfo *)&message);
|
||||||
|
printf("CLIENT: Got device %d\n", message.device_info.index);
|
||||||
} else if (message.code == DeviceReport) {
|
} else if (message.code == DeviceReport) {
|
||||||
device_handle_report((MessageDeviceReport *)&message);
|
device_handle_report((MessageDeviceReport *)&message);
|
||||||
|
} else if (message.code == DeviceDestroy) {
|
||||||
|
device_destroy(message.destroy.index);
|
||||||
|
printf("CLIENT: Lost device %d\n", message.destroy.index);
|
||||||
} else {
|
} else {
|
||||||
printf("CLIENT: Illegal message\n");
|
printf("CLIENT: Illegal message\n");
|
||||||
}
|
}
|
||||||
|
18
client.h
18
client.h
@ -2,7 +2,23 @@
|
|||||||
#ifndef CLIENT_H_
|
#ifndef CLIENT_H_
|
||||||
#define CLIENT_H_
|
#define CLIENT_H_
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
void client_run(char *address, uint16_t port);
|
void client_run(char *address, uint16_t port, char *config_path);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *tag;
|
||||||
|
int32_t device_vendor;
|
||||||
|
int32_t device_product;
|
||||||
|
char *device_name;
|
||||||
|
} ClientController;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ClientController *controllers;
|
||||||
|
size_t controller_count;
|
||||||
|
|
||||||
|
char *fifo_path;
|
||||||
|
struct timespec retry_delay;
|
||||||
|
} ClientConfig;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
7
const.c
7
const.c
@ -3,14 +3,19 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
|
// These are default values for the most part
|
||||||
|
// Any value updated here should also be updated in README.md
|
||||||
|
|
||||||
// How long between each device poll
|
// How long between each device poll
|
||||||
const struct timespec POLL_DEVICE_INTERVAL = {.tv_sec = 1, .tv_nsec = 0};
|
const struct timespec POLL_DEVICE_INTERVAL = {.tv_sec = 1, .tv_nsec = 0};
|
||||||
|
// How long (in ms) to wait for a request message on a connection before giving up
|
||||||
|
const int REQUEST_TIMEOUT = 2000;
|
||||||
// Default name for physical device, only visible in logs
|
// Default name for physical device, only visible in logs
|
||||||
const char *DEVICE_DEFAULT_NAME = "Unnamed Device";
|
const char *DEVICE_DEFAULT_NAME = "Unnamed Device";
|
||||||
// Path to the fifo
|
// Path to the fifo
|
||||||
const char *FIFO_PATH = "/tmp/jsfw_fifo";
|
const char *FIFO_PATH = "/tmp/jsfw_fifo";
|
||||||
// Delay (in seconds) between each connection retry for the client
|
// Delay (in seconds) between each connection retry for the client
|
||||||
const uint32_t CONNECTION_RETRY_DELAY = 5;
|
const struct timespec CONNECTION_RETRY_DELAY = {.tv_sec = 5, .tv_nsec = 0};
|
||||||
// Displayed vendor for the virtual device
|
// Displayed vendor for the virtual device
|
||||||
const uint16_t VIRTUAL_DEVICE_VENDOR = 0x6969;
|
const uint16_t VIRTUAL_DEVICE_VENDOR = 0x6969;
|
||||||
// Displayed product for the virtual device
|
// Displayed product for the virtual device
|
||||||
|
3
const.h
3
const.h
@ -5,9 +5,10 @@
|
|||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
extern const struct timespec POLL_DEVICE_INTERVAL;
|
extern const struct timespec POLL_DEVICE_INTERVAL;
|
||||||
|
extern const int REQUEST_TIMEOUT;
|
||||||
extern const char *DEVICE_DEFAULT_NAME;
|
extern const char *DEVICE_DEFAULT_NAME;
|
||||||
extern const char *FIFO_PATH;
|
extern const char *FIFO_PATH;
|
||||||
extern const uint32_t CONNECTION_RETRY_DELAY;
|
extern const struct timespec CONNECTION_RETRY_DELAY;
|
||||||
extern const uint16_t VIRTUAL_DEVICE_VENDOR;
|
extern const uint16_t VIRTUAL_DEVICE_VENDOR;
|
||||||
extern const uint16_t VIRTUAL_DEVICE_PRODUCT;
|
extern const uint16_t VIRTUAL_DEVICE_PRODUCT;
|
||||||
extern const uint16_t VIRTUAL_DEVICE_VERSION;
|
extern const uint16_t VIRTUAL_DEVICE_VERSION;
|
||||||
|
416
hid.c
416
hid.c
@ -1,6 +1,7 @@
|
|||||||
#include "hid.h"
|
#include "hid.h"
|
||||||
|
|
||||||
#include "const.h"
|
#include "const.h"
|
||||||
|
#include "server.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "vec.h"
|
#include "vec.h"
|
||||||
|
|
||||||
@ -17,18 +18,20 @@
|
|||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
// List of uniq of the currently known devices
|
// List of ids of the currently known devices
|
||||||
static Vec devices;
|
static Vec known_devices;
|
||||||
// List of the new devices of a poll, static to keep the allocation alive
|
// Queue of available devices, devices that can only be given to one client
|
||||||
static Vec new_devices;
|
static Vec available_devices;
|
||||||
// Queue of devices to be taken by connections
|
// List of cloneable devices, devices that can be handed out to multiple clients
|
||||||
static Vec devices_queue;
|
static Vec cloneable_devices;
|
||||||
// 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
|
// Mutex for devices
|
||||||
static pthread_mutex_t devices_mutex = PTHREAD_MUTEX_INITIALIZER;
|
static pthread_mutex_t devices_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
// Condvar notified on devices update
|
||||||
|
static pthread_cond_t devices_cond = PTHREAD_COND_INITIALIZER;
|
||||||
|
// Mutex for devices
|
||||||
|
static pthread_mutex_t known_devices_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
|
static ServerConfig *config;
|
||||||
|
|
||||||
// uniqs are just hexadecimal numbers with colons in between each byte
|
// uniqs are just hexadecimal numbers with colons in between each byte
|
||||||
uniq_t parse_uniq(char uniq[17]) {
|
uniq_t parse_uniq(char uniq[17]) {
|
||||||
@ -64,8 +67,8 @@ void setup_device(PhysicalDevice *dev) {
|
|||||||
for (int i = 0; i < KEY_CNT; i++)
|
for (int i = 0; i < KEY_CNT; i++)
|
||||||
dev->mapping.key_indices[i] = -1;
|
dev->mapping.key_indices[i] = -1;
|
||||||
|
|
||||||
uint8_t type_bits[EV_MAX] = {};
|
uint8_t type_bits[EV_MAX] = {0};
|
||||||
uint8_t feat_bits[(KEY_MAX + 7) / 8] = {};
|
uint8_t feat_bits[(KEY_MAX + 7) / 8] = {0};
|
||||||
|
|
||||||
ioctl(dev->event, EVIOCGBIT(0, EV_MAX), type_bits);
|
ioctl(dev->event, EVIOCGBIT(0, EV_MAX), type_bits);
|
||||||
// Loop over all event types
|
// Loop over all event types
|
||||||
@ -116,13 +119,8 @@ void setup_device(PhysicalDevice *dev) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function used to filter out devices that we don't want.
|
bool filter_event(int fd, char *event, ControllerFilter *filter, uniq_t uniq) {
|
||||||
// This is pretty arbritrary
|
if (filter->js) {
|
||||||
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];
|
char device_path[64];
|
||||||
snprintf(device_path, 64, "/sys/class/input/%s/device", event);
|
snprintf(device_path, 64, "/sys/class/input/%s/device", event);
|
||||||
|
|
||||||
@ -144,85 +142,131 @@ bool filter_event(int fd, char *event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check product and vendor id 054c:05c4 => Dualshock 4 (09cc is for the second generation)
|
if (filter->name != NULL) {
|
||||||
uint16_t info[4];
|
char name[256] = {0};
|
||||||
ioctl(fd, EVIOCGID, info);
|
ioctl(fd, EVIOCGNAME(256), name);
|
||||||
return info[1] == 0x054c && (info[2] == 0x05c4 || info[2] == 0x09cc);
|
if (strcmp(name, filter->name) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter->uniq > 0 && uniq != filter->uniq) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct input_id ids;
|
||||||
|
ioctl(fd, EVIOCGID, &ids);
|
||||||
|
|
||||||
|
if (filter->vendor > 0 && filter->vendor != ids.vendor)
|
||||||
|
return false;
|
||||||
|
if (filter->product > 0 && filter->product != ids.product)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize vectors for polling
|
// Initialize vectors for polling
|
||||||
void poll_devices_init() {
|
void poll_devices_init(void) {
|
||||||
devices = vec_of(uniq_t);
|
known_devices = vec_of(Controller);
|
||||||
new_devices = vec_of(PhysicalDevice);
|
cloneable_devices = vec_of(Controller *);
|
||||||
devices_queue = vec_of(PhysicalDevice);
|
available_devices = vec_of(Controller *);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Block to get a device, this is thread safe
|
// Block to get a device, this is thread safe
|
||||||
PhysicalDevice get_device() {
|
// stop: additional condition to check before doing anything,
|
||||||
|
// if the condition is ever found to be true the function will return immediately with a NULL pointer.
|
||||||
|
Controller *get_device(char *tag, bool *stop) {
|
||||||
// Check if we can get one right away
|
// Check if we can get one right away
|
||||||
pthread_mutex_lock(&devices_queue_mutex);
|
pthread_mutex_lock(&devices_mutex);
|
||||||
if (devices_queue.len > 0) {
|
|
||||||
PhysicalDevice r;
|
|
||||||
vec_pop(&devices_queue, &r);
|
|
||||||
pthread_mutex_unlock(&devices_queue_mutex);
|
|
||||||
|
|
||||||
return r;
|
while (1) {
|
||||||
|
if (*stop) {
|
||||||
|
pthread_mutex_unlock(&devices_mutex);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < available_devices.len; i++) {
|
||||||
|
Controller *c = *(Controller **)vec_get(&available_devices, i);
|
||||||
|
if (strcmp(c->ctr.tag, tag) == 0) {
|
||||||
|
vec_remove(&available_devices, i, NULL);
|
||||||
|
pthread_mutex_unlock(&devices_mutex);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < cloneable_devices.len; i++) {
|
||||||
|
Controller *c = *(Controller **)vec_get(&cloneable_devices, i);
|
||||||
|
if (strcmp(c->ctr.tag, tag) == 0) {
|
||||||
|
pthread_mutex_unlock(&devices_mutex);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait on condvar until there's a device and we can unlock the mutex
|
||||||
|
pthread_cond_wait(&devices_cond, &devices_mutex);
|
||||||
}
|
}
|
||||||
// Wait on condvar until there's a device and we can unlock the mutex
|
|
||||||
while (devices_queue.len == 0) {
|
|
||||||
pthread_cond_wait(&devices_queue_cond, &devices_queue_mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Take a device from the queue
|
|
||||||
PhysicalDevice res;
|
|
||||||
vec_pop(&devices_queue, &res);
|
|
||||||
|
|
||||||
// Signal another thread if there are still device(s) left in the queue
|
|
||||||
if (devices_queue.len > 0) {
|
|
||||||
pthread_cond_signal(&devices_queue_cond);
|
|
||||||
}
|
|
||||||
|
|
||||||
pthread_mutex_unlock(&devices_queue_mutex);
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forget about a device. This is used on two cases:
|
// Return a device that isn't used anymore, this really only makes sense for non cloneable devices.
|
||||||
// - If the connection to a client is lost, the device is forgotten, picked up by the next device poll, and
|
void return_device(Controller *c) {
|
||||||
// put back in the queue
|
// If device is cloneable there is nothing to return
|
||||||
// - If the device dies (i.e unplugged), the connection to the client is closed and the device forgotten.
|
if (c->ctr.duplicate) {
|
||||||
//
|
return;
|
||||||
// This is thread safe
|
|
||||||
void return_device(PhysicalDevice *dev) {
|
|
||||||
if (dev->name != NULL && dev->name != DEVICE_DEFAULT_NAME) {
|
|
||||||
// Free the name if it was allocated
|
|
||||||
printf("HID: Returning device '%s' (%012lx)\n", dev->name, dev->uniq);
|
|
||||||
free(dev->name);
|
|
||||||
} else {
|
|
||||||
printf("HID: Returning device %012lx\n", dev->uniq);
|
|
||||||
}
|
}
|
||||||
// try to close the file descriptor, they may be already closed if the device was unpugged.
|
|
||||||
close(dev->event);
|
|
||||||
close(dev->hidraw);
|
|
||||||
|
|
||||||
// Safely remove device from the known device list
|
|
||||||
pthread_mutex_lock(&devices_mutex);
|
pthread_mutex_lock(&devices_mutex);
|
||||||
for (int i = 0; i < devices.len; i++) {
|
vec_push(&available_devices, &c);
|
||||||
uniq_t *uniq = vec_get(&devices, i);
|
// Signal that there are new devices
|
||||||
if (*uniq == dev->uniq) {
|
pthread_cond_broadcast(&devices_cond);
|
||||||
vec_remove(&devices, i, NULL);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&devices_mutex);
|
pthread_mutex_unlock(&devices_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find all available devices and pick up on new ones
|
// Forget about a broken device. This invalidates the reference to the controller
|
||||||
void poll_devices() {
|
void forget_device(Controller *c) {
|
||||||
vec_clear(&new_devices);
|
|
||||||
|
|
||||||
|
// If controller is cloneable we need to remove it from the cloneable list
|
||||||
|
if (c->ctr.duplicate) {
|
||||||
|
pthread_mutex_lock(&devices_mutex);
|
||||||
|
for (int i = 0; i < cloneable_devices.len; i++) {
|
||||||
|
Controller *d = *(Controller **)vec_get(&cloneable_devices, i);
|
||||||
|
if (d->dev.id == c->dev.id) {
|
||||||
|
vec_remove(&cloneable_devices, i, NULL);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&devices_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free the name if it was allocated
|
||||||
|
if (c->dev.name != NULL && c->dev.name != DEVICE_DEFAULT_NAME) {
|
||||||
|
printf("HID: Forgetting device '%s' (%016lx)\n", c->dev.name, c->dev.id);
|
||||||
|
free(c->dev.name);
|
||||||
|
} else {
|
||||||
|
printf("HID: Forgetting device %016lx\n", c->dev.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to close the file descriptor, they may be already closed if the device was unpugged.
|
||||||
|
close(c->dev.event);
|
||||||
|
close(c->dev.hidraw);
|
||||||
|
|
||||||
|
// Safely remove device from the known device list
|
||||||
|
pthread_mutex_lock(&known_devices_mutex);
|
||||||
|
for (int i = 0; i < known_devices.len; i++) {
|
||||||
|
Controller *d = vec_get(&known_devices, i);
|
||||||
|
if (d->dev.id == c->dev.id) {
|
||||||
|
vec_remove(&known_devices, i, NULL);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&known_devices_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all available devices and pick up on new ones
|
||||||
|
void poll_devices(void) {
|
||||||
// loop over all entries of /sys/class/input
|
// loop over all entries of /sys/class/input
|
||||||
DIR *input_dir = opendir("/sys/class/input");
|
DIR *input_dir = opendir("/sys/class/input");
|
||||||
struct dirent *input;
|
struct dirent *input;
|
||||||
|
|
||||||
while ((input = readdir(input_dir)) != NULL) {
|
while ((input = readdir(input_dir)) != NULL) {
|
||||||
// Ignore if the entry isn't a link or doesn't start with event
|
// Ignore if the entry isn't a link or doesn't start with event
|
||||||
if (input->d_type != DT_LNK || strncmp(input->d_name, "event", 5) != 0) {
|
if (input->d_type != DT_LNK || strncmp(input->d_name, "event", 5) != 0) {
|
||||||
@ -230,93 +274,120 @@ void poll_devices() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PhysicalDevice dev;
|
PhysicalDevice dev;
|
||||||
|
dev.hidraw = -1;
|
||||||
|
dev.uniq = 0;
|
||||||
|
|
||||||
// Open /dev/input/eventXX
|
// Open /dev/input/eventXX
|
||||||
char event_path[64];
|
{
|
||||||
snprintf(event_path, 64, "/dev/input/%s", input->d_name);
|
char event_path[64];
|
||||||
|
snprintf(event_path, 64, "/dev/input/%s", input->d_name);
|
||||||
|
|
||||||
dev.event = open(event_path, O_RDONLY);
|
dev.event = open(event_path, O_RDONLY);
|
||||||
|
|
||||||
if (dev.event < 0) { // Ignore device if we couldn't open
|
if (dev.event < 0) { // Ignore device if we couldn't open
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to get the name, default to DEFAULT_NAME if impossible
|
// Try to get the name, default to DEFAULT_NAME if impossible
|
||||||
char name_buf[256] = {};
|
char *name;
|
||||||
const char *name;
|
{
|
||||||
if (ioctl(dev.event, EVIOCGNAME(256), name_buf) >= 0) {
|
static char name_buf[256] = {0};
|
||||||
name = name_buf;
|
if (ioctl(dev.event, EVIOCGNAME(256), name_buf) >= 0) {
|
||||||
} else {
|
name = name_buf;
|
||||||
name = DEVICE_DEFAULT_NAME;
|
} else {
|
||||||
}
|
name = (char *)DEVICE_DEFAULT_NAME;
|
||||||
|
}
|
||||||
// Filter events we don't care about
|
|
||||||
if (!filter_event(dev.event, input->d_name)) {
|
|
||||||
goto skip;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to get uniq, drop device if we can't
|
// Try to get uniq, drop device if we can't
|
||||||
uniq_t uniq;
|
|
||||||
{
|
{
|
||||||
char uniq_str[17] = {};
|
char uniq_str[17] = {0};
|
||||||
|
|
||||||
ioctl(dev.event, EVIOCGUNIQ(17), uniq_str);
|
ioctl(dev.event, EVIOCGUNIQ(17), uniq_str);
|
||||||
uniq = parse_uniq(uniq_str);
|
dev.uniq = parse_uniq(uniq_str);
|
||||||
|
|
||||||
// If we couldn't parse the uniq (this assumes uniq can't be zero, which is probably alright)
|
|
||||||
if (uniq == 0) {
|
|
||||||
goto skip;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we already know of this device
|
// Used for linear searches
|
||||||
bool found = false;
|
bool found;
|
||||||
|
|
||||||
pthread_mutex_lock(&devices_mutex);
|
// Filter devices according server config
|
||||||
for (int i = 0; i < devices.len; i++) {
|
ServerConfigController *ctr;
|
||||||
uniq_t *dev_uniq = vec_get(&devices, i);
|
|
||||||
if (*dev_uniq == uniq) {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&devices_mutex);
|
|
||||||
|
|
||||||
if (found) { // Device isn't new
|
|
||||||
goto skip;
|
|
||||||
}
|
|
||||||
|
|
||||||
dev.uniq = uniq;
|
|
||||||
|
|
||||||
// Try to find hidraw path for the device, drop the device if we can't
|
|
||||||
char hidraw_path[64];
|
|
||||||
{
|
{
|
||||||
char hidraw_dir_path[256];
|
found = false;
|
||||||
snprintf(hidraw_dir_path, 256, "/sys/class/input/%s/device/device/hidraw", input->d_name);
|
for (int i = 0; i < config->controller_count; i++) {
|
||||||
|
ctr = &config->controllers[i];
|
||||||
|
|
||||||
DIR *hidraw_dir = opendir(hidraw_dir_path);
|
if (filter_event(dev.event, input->d_name, &ctr->filter, dev.uniq)) {
|
||||||
struct dirent *hidraw = NULL;
|
found = true;
|
||||||
while ((hidraw = readdir(hidraw_dir)) != NULL) {
|
|
||||||
if (strncmp(hidraw->d_name, "hidraw", 6) == 0) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hidraw == NULL) {
|
if (!found) {
|
||||||
printf("Couldn't get hidraw of %s", input->d_name);
|
goto skip;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
snprintf(hidraw_path, 64, "/dev/%s", hidraw->d_name);
|
|
||||||
|
|
||||||
closedir(hidraw_dir);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dev.hidraw = open(hidraw_path, O_WRONLY);
|
// Get the id
|
||||||
if (dev.hidraw < 0) {
|
{
|
||||||
goto skip;
|
struct input_id id;
|
||||||
|
ioctl(dev.event, EVIOCGID, &id);
|
||||||
|
dev.id = *(uint64_t *)&id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if we already know of this device
|
||||||
|
{
|
||||||
|
found = false;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&known_devices_mutex);
|
||||||
|
for (int i = 0; i < known_devices.len; i++) {
|
||||||
|
Controller *c = vec_get(&known_devices, i);
|
||||||
|
if (c->dev.id == dev.id) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&known_devices_mutex);
|
||||||
|
|
||||||
|
if (found) { // Device isn't new
|
||||||
|
goto skip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for hidraw if the device should have one (Dualshock 4 only, with ps4_hidraw property set)
|
||||||
|
if (ctr->ps4_hidraw) {
|
||||||
|
// Attempt to find the path
|
||||||
|
char hidraw_path[64];
|
||||||
|
{
|
||||||
|
char hidraw_dir_path[256];
|
||||||
|
snprintf(hidraw_dir_path, 256, "/sys/class/input/%s/device/device/hidraw", input->d_name);
|
||||||
|
|
||||||
|
DIR *hidraw_dir = opendir(hidraw_dir_path);
|
||||||
|
struct dirent *hidraw = NULL;
|
||||||
|
while ((hidraw = readdir(hidraw_dir)) != NULL) {
|
||||||
|
if (strncmp(hidraw->d_name, "hidraw", 6) == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hidraw == NULL) {
|
||||||
|
printf("HID: Couldn't get hidraw of %s", input->d_name);
|
||||||
|
goto skip;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(hidraw_path, 64, "/dev/%s", hidraw->d_name);
|
||||||
|
|
||||||
|
closedir(hidraw_dir);
|
||||||
|
}
|
||||||
|
// Try to open
|
||||||
|
dev.hidraw = open(hidraw_path, O_WRONLY);
|
||||||
|
if (dev.hidraw < 0) {
|
||||||
|
goto skip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate for name (only now to avoid unecessary allocations)
|
||||||
if (name != DEVICE_DEFAULT_NAME) {
|
if (name != DEVICE_DEFAULT_NAME) {
|
||||||
dev.name = malloc(256);
|
dev.name = malloc(256);
|
||||||
|
|
||||||
@ -327,40 +398,55 @@ void poll_devices() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setup_device(&dev);
|
// This code is only run if the device has passed all filters and requirements
|
||||||
|
{
|
||||||
|
setup_device(&dev);
|
||||||
|
Controller c = {.dev = dev, .ctr = *ctr};
|
||||||
|
|
||||||
pthread_mutex_lock(&devices_mutex);
|
pthread_mutex_lock(&known_devices_mutex);
|
||||||
vec_push(&devices, &uniq);
|
// Index of the device in known_devices
|
||||||
pthread_mutex_unlock(&devices_mutex);
|
int index = known_devices.len;
|
||||||
|
vec_push(&known_devices, &c);
|
||||||
|
pthread_mutex_unlock(&known_devices_mutex);
|
||||||
|
|
||||||
vec_push(&new_devices, &dev);
|
// Pointer to the device in known_devices
|
||||||
|
Controller *p = vec_get(&known_devices, index);
|
||||||
|
|
||||||
printf("HID: New device, %s (%s: %012lx)\n", name, input->d_name, dev.uniq);
|
printf("HID: New device, %s [%s] (%s: %016lx)\n", name, ctr->tag, input->d_name, dev.id);
|
||||||
// Continue here to avoid running cleanup code of skip
|
|
||||||
|
if (ctr->duplicate) {
|
||||||
|
pthread_mutex_lock(&devices_mutex);
|
||||||
|
vec_push(&cloneable_devices, &p);
|
||||||
|
// Signal that there are new cloneable devices
|
||||||
|
pthread_cond_broadcast(&devices_cond);
|
||||||
|
pthread_mutex_unlock(&devices_mutex);
|
||||||
|
} else {
|
||||||
|
pthread_mutex_lock(&devices_mutex);
|
||||||
|
vec_push(&available_devices, &p);
|
||||||
|
// Signal that there are new devices
|
||||||
|
pthread_cond_broadcast(&devices_cond);
|
||||||
|
pthread_mutex_unlock(&devices_mutex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Continue here avoids running cleanup code
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// close open file descriptor and continue
|
// close open file descriptor and continue
|
||||||
skip:
|
skip:
|
||||||
close(dev.event);
|
close(dev.event);
|
||||||
continue;
|
|
||||||
};
|
};
|
||||||
closedir(input_dir);
|
closedir(input_dir);
|
||||||
|
|
||||||
// Safely add new devices to the queue
|
|
||||||
if (new_devices.len > 0) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// "Execute" a MessageControllerState: set the led color, rumble and flash using the hidraw interface
|
// "Execute" a MessageControllerState: set the led color, rumble and flash using the hidraw interface (Dualshock 4 only)
|
||||||
void apply_controller_state(PhysicalDevice *dev, MessageControllerState *state) {
|
void apply_controller_state(Controller *c, MessageControllerState *state) {
|
||||||
printf("HID: (%012lx) Controller state: #%02x%02x%02x flash: (%d, %d) rumble: (%d, %d)\n", dev->uniq,
|
if (c->ctr.ps4_hidraw && c->dev.hidraw < 0) {
|
||||||
state->led[0], state->led[1], state->led[2], state->flash_on, state->flash_off,
|
printf("HID: Trying to apply controller state on incompatible device (%016lx)\n", c->dev.id);
|
||||||
state->small_rumble, state->big_rumble);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("HID: (%016lx) Controller state: #%02x%02x%02x flash: (%d, %d) rumble: (%d, %d)\n", c->dev.id, state->led[0],
|
||||||
|
state->led[1], state->led[2], state->flash_on, state->flash_off, state->small_rumble, state->big_rumble);
|
||||||
|
|
||||||
uint8_t buf[32] = {0x05, 0xff, 0x00, 0x00};
|
uint8_t buf[32] = {0x05, 0xff, 0x00, 0x00};
|
||||||
|
|
||||||
@ -372,24 +458,24 @@ void apply_controller_state(PhysicalDevice *dev, MessageControllerState *state)
|
|||||||
buf[9] = state->flash_on;
|
buf[9] = state->flash_on;
|
||||||
buf[10] = state->flash_off;
|
buf[10] = state->flash_off;
|
||||||
|
|
||||||
write(dev->hidraw, buf, 32);
|
write(c->dev.hidraw, buf, 32);
|
||||||
if (state->flash_on == 0 && state->flash_off == 0) {
|
if (state->flash_on == 0 && state->flash_off == 0) {
|
||||||
// May not be necessary
|
// May not be necessary
|
||||||
fsync(dev->hidraw);
|
fsync(c->dev.hidraw);
|
||||||
// Send a second time, to reenable the led
|
// Send a second time, to reenable the led
|
||||||
write(dev->hidraw, buf, 32);
|
write(c->dev.hidraw, buf, 32);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Body of the hid thread
|
// Body of the hid thread
|
||||||
void *hid_thread() {
|
void *hid_thread(void *arg) {
|
||||||
printf("HID: start\n");
|
printf("HID: start\n");
|
||||||
|
config = arg;
|
||||||
|
|
||||||
poll_devices_init();
|
poll_devices_init();
|
||||||
while (1) {
|
while (1) {
|
||||||
poll_devices();
|
poll_devices();
|
||||||
|
nanosleep(&config->poll_interval, NULL);
|
||||||
nanosleep(&POLL_DEVICE_INTERVAL, NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
17
hid.h
17
hid.h
@ -2,8 +2,10 @@
|
|||||||
#ifndef HID_H_
|
#ifndef HID_H_
|
||||||
#define HID_H_
|
#define HID_H_
|
||||||
#include "net.h"
|
#include "net.h"
|
||||||
|
#include "server.h"
|
||||||
|
|
||||||
#include <linux/input-event-codes.h>
|
#include <linux/input-event-codes.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
// Unique identifier for devices (provided by linux), May be the mac address
|
// Unique identifier for devices (provided by linux), May be the mac address
|
||||||
@ -24,14 +26,21 @@ typedef struct {
|
|||||||
int event;
|
int event;
|
||||||
int hidraw;
|
int hidraw;
|
||||||
uniq_t uniq;
|
uniq_t uniq;
|
||||||
|
uint64_t id;
|
||||||
char *name;
|
char *name;
|
||||||
DeviceMap mapping;
|
DeviceMap mapping;
|
||||||
MessageDeviceInfo device_info;
|
MessageDeviceInfo device_info;
|
||||||
} PhysicalDevice;
|
} PhysicalDevice;
|
||||||
|
|
||||||
void *hid_thread();
|
typedef struct {
|
||||||
void return_device(PhysicalDevice *dev);
|
PhysicalDevice dev;
|
||||||
PhysicalDevice get_device();
|
ServerConfigController ctr;
|
||||||
void apply_controller_state(PhysicalDevice *dev, MessageControllerState *state);
|
} Controller;
|
||||||
|
|
||||||
|
void *hid_thread(void *arg);
|
||||||
|
void return_device(Controller *c);
|
||||||
|
void forget_device(Controller *c);
|
||||||
|
Controller *get_device(char *tag, bool *stop);
|
||||||
|
void apply_controller_state(Controller *c, MessageControllerState *state);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
256
json.c
256
json.c
@ -16,11 +16,21 @@ static JSONError jerrno = NoError;
|
|||||||
static size_t jerr_index = 0;
|
static size_t jerr_index = 0;
|
||||||
|
|
||||||
// Get a string explaining the last json parsing error
|
// Get a string explaining the last json parsing error
|
||||||
const char *json_strerr() { return JSONErrorMessage[jerrno]; }
|
const char *json_strerr(void) { return JSONErrorMessage[jerrno]; }
|
||||||
// Get the code of the last json parsing error
|
// Get the code of the last json parsing error
|
||||||
JSONError json_errno() { return jerrno; }
|
JSONError json_errno(void) { return jerrno; }
|
||||||
// Get the location of the last json parsing error
|
// Get the location of the last json parsing error
|
||||||
size_t json_errloc() { return jerr_index; }
|
size_t json_errloc(void) { return jerr_index; }
|
||||||
|
|
||||||
|
static inline bool is_primitive(const JSONAdapter *adapter) { return adapter->props == NULL; }
|
||||||
|
|
||||||
|
static const char *json_type_name(JSONType type) {
|
||||||
|
if (type > 0 && type < 7) {
|
||||||
|
return JSONTypeName[type];
|
||||||
|
} else {
|
||||||
|
return JSONTypeName[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Shorthand to set jerno and return -1;
|
// Shorthand to set jerno and return -1;
|
||||||
// i.e
|
// i.e
|
||||||
@ -45,8 +55,7 @@ static int json_parse_value(const char **buf, const char *buf_end, uint8_t **res
|
|||||||
const uint8_t *dst_end); // Declaration for recursion
|
const uint8_t *dst_end); // Declaration for recursion
|
||||||
|
|
||||||
// *dst must be 8 aligned
|
// *dst must be 8 aligned
|
||||||
static inline int json_parse_string(const char **buf, const char *buf_end, uint8_t **restrict dst,
|
static inline int json_parse_string(const char **buf, const char *buf_end, uint8_t **restrict dst, const uint8_t *dst_end) {
|
||||||
const uint8_t *dst_end) {
|
|
||||||
// Ensure enough space for the header
|
// Ensure enough space for the header
|
||||||
if (*dst + sizeof(JSONHeader) >= dst_end) {
|
if (*dst + sizeof(JSONHeader) >= dst_end) {
|
||||||
return set_jerrno(DstOverflow);
|
return set_jerrno(DstOverflow);
|
||||||
@ -111,27 +120,27 @@ static inline int json_parse_string(const char **buf, const char *buf_end, uint8
|
|||||||
return set_jerrno(DstOverflow);
|
return set_jerrno(DstOverflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
*(*dst)++ = 0b11000000 | (un_codepoint >> 6 & 0b011111);
|
*(*dst)++ = 0xC0 | (un_codepoint >> 6 & 0x1F);
|
||||||
*(*dst)++ = 0b10000000 | (un_codepoint >> 0 & 0b111111);
|
*(*dst)++ = 0x80 | (un_codepoint >> 0 & 0x3F);
|
||||||
header->len += 2;
|
header->len += 2;
|
||||||
} else if (un_codepoint <= 0xffff) { // 3 byte codepoint
|
} else if (un_codepoint <= 0xffff) { // 3 byte codepoint
|
||||||
if (*dst + 3 >= dst_end) {
|
if (*dst + 3 >= dst_end) {
|
||||||
return set_jerrno(DstOverflow);
|
return set_jerrno(DstOverflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
*(*dst)++ = 0b11100000 | (un_codepoint >> 12 & 0b1111);
|
*(*dst)++ = 0xE0 | (un_codepoint >> 12 & 0x0F);
|
||||||
*(*dst)++ = 0b10000000 | (un_codepoint >> 6 & 0b111111);
|
*(*dst)++ = 0x80 | (un_codepoint >> 6 & 0x3F);
|
||||||
*(*dst)++ = 0b10000000 | (un_codepoint >> 0 & 0b111111);
|
*(*dst)++ = 0x80 | (un_codepoint >> 0 & 0x3F);
|
||||||
header->len += 3;
|
header->len += 3;
|
||||||
} else if (un_codepoint <= 0x10ffff) { // 4 byte codepoint
|
} else if (un_codepoint <= 0x10ffff) { // 4 byte codepoint
|
||||||
if (*dst + 4 >= dst_end) {
|
if (*dst + 4 >= dst_end) {
|
||||||
return set_jerrno(DstOverflow);
|
return set_jerrno(DstOverflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
*(*dst)++ = 0b11110000 | (un_codepoint >> 18 & 0b111);
|
*(*dst)++ = 0xF0 | (un_codepoint >> 18 & 0x07);
|
||||||
*(*dst)++ = 0b10000000 | (un_codepoint >> 12 & 0b111111);
|
*(*dst)++ = 0x80 | (un_codepoint >> 12 & 0x3F);
|
||||||
*(*dst)++ = 0b10000000 | (un_codepoint >> 6 & 0b111111);
|
*(*dst)++ = 0x80 | (un_codepoint >> 6 & 0x3F);
|
||||||
*(*dst)++ = 0b10000000 | (un_codepoint >> 0 & 0b111111);
|
*(*dst)++ = 0x80 | (un_codepoint >> 0 & 0x3F);
|
||||||
header->len += 4;
|
header->len += 4;
|
||||||
} else { // Illegal codepoint
|
} else { // Illegal codepoint
|
||||||
return set_jerrno(StringBadUnicode);
|
return set_jerrno(StringBadUnicode);
|
||||||
@ -198,9 +207,8 @@ static inline int json_parse_string(const char **buf, const char *buf_end, uint8
|
|||||||
(*buf)++;
|
(*buf)++;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
} else if ((c < ' ' && c != '\t') ||
|
} else if ((c < ' ' && c != '\t') || c == 0x7f) { // Illegal characters, technically tab isn't allowed either
|
||||||
c == 0x7f) { // Illegal characters, technically tab isn't allowed either
|
// but it felt weird so I added it
|
||||||
// but it felt weird so I added it
|
|
||||||
jerrno = StringBadChar;
|
jerrno = StringBadChar;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -219,8 +227,7 @@ static inline int json_parse_string(const char **buf, const char *buf_end, uint8
|
|||||||
}
|
}
|
||||||
|
|
||||||
// *dst must be 8 aligned
|
// *dst must be 8 aligned
|
||||||
static int json_parse_number(const char **buf, const char *buf_end, uint8_t **restrict dst,
|
static int json_parse_number(const char **buf, const char *buf_end, uint8_t **restrict dst, const uint8_t *dst_end) {
|
||||||
const uint8_t *dst_end) {
|
|
||||||
// Ensure enough space for header and value
|
// Ensure enough space for header and value
|
||||||
if (*dst + sizeof(JSONHeader) + sizeof(double) >= dst_end) {
|
if (*dst + sizeof(JSONHeader) + sizeof(double) >= dst_end) {
|
||||||
return set_jerrno(DstOverflow);
|
return set_jerrno(DstOverflow);
|
||||||
@ -339,8 +346,7 @@ static int json_parse_number(const char **buf, const char *buf_end, uint8_t **re
|
|||||||
}
|
}
|
||||||
|
|
||||||
// *dst must be 8 aligned
|
// *dst must be 8 aligned
|
||||||
static int json_parse_boolean(const char **buf, const char *buf_end, uint8_t **restrict dst,
|
static int json_parse_boolean(const char **buf, const char *buf_end, uint8_t **restrict dst, const uint8_t *dst_end) {
|
||||||
const uint8_t *dst_end) {
|
|
||||||
// Ensure enough space for header and value
|
// Ensure enough space for header and value
|
||||||
if (*dst + sizeof(JSONHeader) + 8 >= dst_end) { // 8: sizeof(uint64_t)
|
if (*dst + sizeof(JSONHeader) + 8 >= dst_end) { // 8: sizeof(uint64_t)
|
||||||
return set_jerrno(DstOverflow);
|
return set_jerrno(DstOverflow);
|
||||||
@ -379,8 +385,7 @@ static int json_parse_boolean(const char **buf, const char *buf_end, uint8_t **r
|
|||||||
}
|
}
|
||||||
|
|
||||||
// *dst must be 8 aligned
|
// *dst must be 8 aligned
|
||||||
static int json_parse_null(const char **buf, const char *buf_end, uint8_t **restrict dst,
|
static int json_parse_null(const char **buf, const char *buf_end, uint8_t **restrict dst, const uint8_t *dst_end) {
|
||||||
const uint8_t *dst_end) {
|
|
||||||
// Ensure enough size for the header (no value)
|
// Ensure enough size for the header (no value)
|
||||||
if (*dst + sizeof(JSONHeader) >= dst_end) {
|
if (*dst + sizeof(JSONHeader) >= dst_end) {
|
||||||
return set_jerrno(DstOverflow);
|
return set_jerrno(DstOverflow);
|
||||||
@ -405,8 +410,7 @@ static int json_parse_null(const char **buf, const char *buf_end, uint8_t **rest
|
|||||||
}
|
}
|
||||||
|
|
||||||
// *dst must be 8 aligned
|
// *dst must be 8 aligned
|
||||||
static int json_parse_array(const char **buf, const char *buf_end, uint8_t **restrict dst,
|
static int json_parse_array(const char **buf, const char *buf_end, uint8_t **restrict dst, const uint8_t *dst_end) {
|
||||||
const uint8_t *dst_end) {
|
|
||||||
// Ensure enough space for the header
|
// Ensure enough space for the header
|
||||||
if (*dst + sizeof(JSONHeader) >= dst_end) {
|
if (*dst + sizeof(JSONHeader) >= dst_end) {
|
||||||
return set_jerrno(DstOverflow);
|
return set_jerrno(DstOverflow);
|
||||||
@ -431,6 +435,7 @@ static int json_parse_array(const char **buf, const char *buf_end, uint8_t **res
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (**buf == ']') { // Array is empty
|
if (**buf == ']') { // Array is empty
|
||||||
|
(*buf)++;
|
||||||
header->len = 0;
|
header->len = 0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -466,8 +471,7 @@ static int json_parse_array(const char **buf, const char *buf_end, uint8_t **res
|
|||||||
}
|
}
|
||||||
|
|
||||||
// *dst must be 8 aligned
|
// *dst must be 8 aligned
|
||||||
static int json_parse_object(const char **buf, const char *buf_end, uint8_t **restrict dst,
|
static int json_parse_object(const char **buf, const char *buf_end, uint8_t **restrict dst, const uint8_t *dst_end) {
|
||||||
const uint8_t *dst_end) {
|
|
||||||
// Esnure enough space for the header
|
// Esnure enough space for the header
|
||||||
if (*dst + sizeof(JSONHeader) >= dst_end) {
|
if (*dst + sizeof(JSONHeader) >= dst_end) {
|
||||||
return set_jerrno(DstOverflow);
|
return set_jerrno(DstOverflow);
|
||||||
@ -490,6 +494,7 @@ static int json_parse_object(const char **buf, const char *buf_end, uint8_t **re
|
|||||||
return set_jerrno(SrcOverflow);
|
return set_jerrno(SrcOverflow);
|
||||||
}
|
}
|
||||||
if (**buf == '}') {
|
if (**buf == '}') {
|
||||||
|
(*buf)++;
|
||||||
// The object is empty
|
// The object is empty
|
||||||
header->len = 0;
|
header->len = 0;
|
||||||
return 0;
|
return 0;
|
||||||
@ -545,8 +550,7 @@ static int json_parse_object(const char **buf, const char *buf_end, uint8_t **re
|
|||||||
}
|
}
|
||||||
|
|
||||||
// *dst must be 8 aligned
|
// *dst must be 8 aligned
|
||||||
static int json_parse_value(const char **buf, const char *buf_end, uint8_t **restrict dst,
|
static int json_parse_value(const char **buf, const char *buf_end, uint8_t **restrict dst, const uint8_t *dst_end) {
|
||||||
const uint8_t *dst_end) {
|
|
||||||
for (; *buf < buf_end; (*buf)++) {
|
for (; *buf < buf_end; (*buf)++) {
|
||||||
// Ignore initial whitespaces
|
// Ignore initial whitespaces
|
||||||
if (is_whitespace(**buf))
|
if (is_whitespace(**buf))
|
||||||
@ -661,49 +665,185 @@ void json_print_value_priv(uint8_t **buf) {
|
|||||||
// /!\ doesn't handle strings well
|
// /!\ doesn't handle strings well
|
||||||
void json_print_value(uint8_t *buf) { json_print_value_priv(&buf); }
|
void json_print_value(uint8_t *buf) { json_print_value_priv(&buf); }
|
||||||
|
|
||||||
// Loop over adapters and set accordingly
|
void json_print_buffer(uint8_t *buf) {
|
||||||
static void json_adapt_set(uint8_t *buf, JSONAdapter *adapters, size_t adapter_count, void *ptr, char *path) {
|
uint8_t *end = buf + align_8(((JSONHeader *)buf)->len) + sizeof(JSONHeader);
|
||||||
JSONHeader *header = (JSONHeader *)buf;
|
while (buf < end) {
|
||||||
|
JSONHeader *header = (JSONHeader *)buf;
|
||||||
|
printf("[\033[32m%s\033[0m][\033[31m%lu\033[0m]", json_type_name(header->type), align_8(header->len));
|
||||||
|
buf += sizeof(JSONHeader);
|
||||||
|
|
||||||
for (int i = 0; i < adapter_count; i++) {
|
if (header->type == Object || header->type == Array)
|
||||||
if (strcmp(path, adapters[i].path) == 0 && header->type == adapters[i].type) {
|
continue;
|
||||||
|
|
||||||
void *p = ptr + adapters[i].offset;
|
printf("[\033[34m");
|
||||||
|
switch (header->type) {
|
||||||
|
case Number:
|
||||||
|
printf("%lf", *(double *)buf);
|
||||||
|
break;
|
||||||
|
case Boolean:
|
||||||
|
printf("%s", *(uint64_t *)buf == 1 ? "true" : "false");
|
||||||
|
break;
|
||||||
|
case Null:
|
||||||
|
printf("null");
|
||||||
|
break;
|
||||||
|
case String:
|
||||||
|
printf("\"%.*s\"", header->len, (char *)buf);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
printf("\033[0m]");
|
||||||
|
|
||||||
switch (header->type) {
|
buf += align_8(header->len);
|
||||||
case String: {
|
}
|
||||||
char *v = malloc(header->len + 1);
|
printf("\n");
|
||||||
strncpy(v, (char *)(buf + sizeof(JSONHeader)), header->len);
|
}
|
||||||
v[header->len] = '\0';
|
|
||||||
*(char **)p = v;
|
static inline bool ends_with(const char *str, const char *pat) {
|
||||||
} break;
|
size_t strl = strlen(str);
|
||||||
case Number:
|
size_t patl = strlen(pat);
|
||||||
*(double *)p = *(double *)(buf + sizeof(JSONHeader));
|
return strl >= patl && strcmp(str + strl - patl, pat) == 0;
|
||||||
break;
|
}
|
||||||
case Boolean:
|
|
||||||
*(bool *)p = *(uint64_t *)(buf + sizeof(JSONHeader)) == 1;
|
static void json_adapt_set_defaults(const JSONAdapter *adapter, void *ptr) {
|
||||||
break;
|
if (!is_primitive(adapter)) {
|
||||||
|
for (int i = 0; i < adapter->prop_count; i++) {
|
||||||
|
uint8_t *p = (uint8_t *)ptr + adapter->props[i].offset;
|
||||||
|
|
||||||
|
if (ends_with(adapter->props[i].path, "[]")) {
|
||||||
|
*(size_t *)(p + sizeof(void *)) = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (adapter->props[i].default_func != NULL) {
|
||||||
|
adapter->props[i].default_func(p);
|
||||||
|
} else if (!is_primitive(adapter->props[i].type)) {
|
||||||
|
json_adapt_set_defaults(adapter->props[i].type, p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run adapters on a value
|
// Run adapters on a value
|
||||||
static void json_adapt_priv(uint8_t **buf, JSONAdapter *adapters, size_t adapter_count, void *ptr,
|
// buf: is a reference of the pointer to the json buffer
|
||||||
char *full_path, char *path) {
|
// adapter: is the adapter to run
|
||||||
|
// ptr: points where to write the data
|
||||||
|
// path_buffer: points to the begining of the path buffer
|
||||||
|
// full_path: points to the "current" path
|
||||||
|
// path: points to the end of the current path (most of the times)
|
||||||
|
static void json_adapt_priv(uint8_t **buf, const JSONAdapter *adapter, void *ptr, char *path_buffer, char *full_path,
|
||||||
|
char *path) {
|
||||||
JSONHeader *header = (JSONHeader *)*buf;
|
JSONHeader *header = (JSONHeader *)*buf;
|
||||||
|
|
||||||
|
if (is_primitive(adapter)) {
|
||||||
|
// The type of a primitive adapter is stored in prop_count
|
||||||
|
JSONType type = adapter->prop_count;
|
||||||
|
|
||||||
|
if (type != header->type) {
|
||||||
|
printf("JSON: Mismatched type on %s: expected %s got %s\n", path_buffer, json_type_name(type),
|
||||||
|
json_type_name(header->type));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
*buf += sizeof(JSONHeader);
|
||||||
|
|
||||||
|
if (type == Boolean) {
|
||||||
|
|
||||||
|
*(bool *)ptr = *(uint64_t *)(*buf) == 1;
|
||||||
|
|
||||||
|
} else if (type == Number) {
|
||||||
|
|
||||||
|
*(double *)ptr = *(double *)(*buf);
|
||||||
|
|
||||||
|
} else if (type == String) {
|
||||||
|
|
||||||
|
char *v = malloc(header->len + 1);
|
||||||
|
strncpy(v, (char *)(*buf), header->len);
|
||||||
|
v[header->len] = '\0';
|
||||||
|
*(char **)ptr = v;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
printf("JSON: Unknown or illegal primitive adapter of type %s\n", json_type_name(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is only true once, so we set default values there
|
||||||
|
if (path == full_path) {
|
||||||
|
json_adapt_set_defaults(adapter, ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header->type == Array) {
|
||||||
|
path[0] = '[';
|
||||||
|
path[1] = ']';
|
||||||
|
path[2] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t buffer_small[64];
|
||||||
|
|
||||||
|
for (int i = 0; i < adapter->prop_count; i++) {
|
||||||
|
if (strcmp(adapter->props[i].path, full_path) == 0) {
|
||||||
|
uint8_t *p = (uint8_t *)ptr + adapter->props[i].offset;
|
||||||
|
size_t size = adapter->props[i].type->size;
|
||||||
|
|
||||||
|
if (header->type == Array) {
|
||||||
|
uint8_t *array_buf = *buf + sizeof(JSONHeader);
|
||||||
|
uint8_t *end = array_buf + header->len;
|
||||||
|
size_t len;
|
||||||
|
for (len = 0; array_buf < end; len++) {
|
||||||
|
array_buf += align_8(((JSONHeader *)array_buf)->len);
|
||||||
|
array_buf += sizeof(JSONHeader);
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t *array_ptr = malloc(len * size);
|
||||||
|
|
||||||
|
array_buf = *buf + sizeof(JSONHeader);
|
||||||
|
for (size_t index = 0; index < len; index++) {
|
||||||
|
path[0] = '.';
|
||||||
|
path[1] = '\0';
|
||||||
|
json_adapt_priv(&array_buf, adapter->props[i].type, array_ptr + index * size, path_buffer, path, path);
|
||||||
|
path[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (adapter->props[i].transformer != NULL) {
|
||||||
|
printf("JSON: Transformers aren't yet allowed on arrays\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
*(void **)p = array_ptr;
|
||||||
|
*(size_t *)(p + sizeof(void *)) = len;
|
||||||
|
} else {
|
||||||
|
void *tmp_ptr;
|
||||||
|
if (size <= 64) {
|
||||||
|
tmp_ptr = buffer_small;
|
||||||
|
} else {
|
||||||
|
tmp_ptr = malloc(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *new_buf = *buf;
|
||||||
|
path[0] = '.';
|
||||||
|
path[1] = '\0';
|
||||||
|
json_adapt_priv(&new_buf, adapter->props[i].type, tmp_ptr, path_buffer, path, path);
|
||||||
|
path[0] = '\0';
|
||||||
|
|
||||||
|
if (adapter->props[i].transformer != NULL) {
|
||||||
|
adapter->props[i].transformer(tmp_ptr, p);
|
||||||
|
} else {
|
||||||
|
memcpy(p, tmp_ptr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tmp_ptr != buffer_small) {
|
||||||
|
free(tmp_ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (header->type) {
|
switch (header->type) {
|
||||||
case String:
|
case String:
|
||||||
json_adapt_set(*buf, adapters, adapter_count, ptr, full_path);
|
|
||||||
*buf += sizeof(JSONHeader) + align_8(header->len);
|
*buf += sizeof(JSONHeader) + align_8(header->len);
|
||||||
break;
|
break;
|
||||||
case Number:
|
case Number:
|
||||||
json_adapt_set(*buf, adapters, adapter_count, ptr, full_path);
|
|
||||||
*buf += sizeof(JSONHeader) + sizeof(double);
|
*buf += sizeof(JSONHeader) + sizeof(double);
|
||||||
break;
|
break;
|
||||||
case Boolean:
|
case Boolean:
|
||||||
json_adapt_set(*buf, adapters, adapter_count, ptr, full_path);
|
|
||||||
*buf += sizeof(JSONHeader) + 8;
|
*buf += sizeof(JSONHeader) + 8;
|
||||||
break;
|
break;
|
||||||
case Null:
|
case Null:
|
||||||
@ -714,7 +854,7 @@ static void json_adapt_priv(uint8_t **buf, JSONAdapter *adapters, size_t adapter
|
|||||||
uint8_t *end = *buf + header->len;
|
uint8_t *end = *buf + header->len;
|
||||||
for (size_t index = 0; *buf < end; index++) {
|
for (size_t index = 0; *buf < end; index++) {
|
||||||
int len = sprintf(path, ".%lu", index);
|
int len = sprintf(path, ".%lu", index);
|
||||||
json_adapt_priv(buf, adapters, adapter_count, ptr, full_path, path + len);
|
json_adapt_priv(buf, adapter, ptr, path_buffer, full_path, path + len);
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case Object: {
|
case Object: {
|
||||||
@ -727,14 +867,14 @@ static void json_adapt_priv(uint8_t **buf, JSONAdapter *adapters, size_t adapter
|
|||||||
int len = sprintf(path, ".%.*s", key_header->len, *buf);
|
int len = sprintf(path, ".%.*s", key_header->len, *buf);
|
||||||
*buf += align_8(key_header->len);
|
*buf += align_8(key_header->len);
|
||||||
|
|
||||||
json_adapt_priv(buf, adapters, adapter_count, ptr, full_path, path + len);
|
json_adapt_priv(buf, adapter, ptr, path_buffer, full_path, path + len);
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run adapters on a json value
|
// Run adapter on a json value
|
||||||
void json_adapt(uint8_t *buf, JSONAdapter *adapters, size_t adapter_count, void *ptr) {
|
void json_adapt(uint8_t *buf, const JSONAdapter *adapter, void *ptr) {
|
||||||
char path[512] = ".";
|
char path[512] = ".";
|
||||||
json_adapt_priv(&buf, adapters, adapter_count, ptr, path, path);
|
json_adapt_priv(&buf, adapter, ptr, path, path, path);
|
||||||
}
|
}
|
||||||
|
63
json.h
63
json.h
@ -2,6 +2,7 @@
|
|||||||
#ifndef JSON_H_
|
#ifndef JSON_H_
|
||||||
#define JSON_H_
|
#define JSON_H_
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
@ -37,6 +38,34 @@ typedef enum {
|
|||||||
JERRORNO_MAX = 10
|
JERRORNO_MAX = 10
|
||||||
} JSONError;
|
} JSONError;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *path;
|
||||||
|
const struct JSONAdapter *type;
|
||||||
|
size_t offset;
|
||||||
|
// Function setting default value
|
||||||
|
void (*default_func)(void *ptr);
|
||||||
|
// Optional transformer, can be NULL
|
||||||
|
void (*transformer)(void *arg, void *ptr);
|
||||||
|
} JSONPropertyAdapter;
|
||||||
|
|
||||||
|
struct JSONAdapter {
|
||||||
|
const JSONPropertyAdapter *props;
|
||||||
|
size_t size;
|
||||||
|
size_t prop_count;
|
||||||
|
};
|
||||||
|
typedef struct JSONAdapter JSONAdapter;
|
||||||
|
|
||||||
|
void json_adapt(uint8_t *buf, const JSONAdapter *adapter, void *ptr);
|
||||||
|
int json_parse(const char *src, size_t src_len, uint8_t *dst, size_t dst_len);
|
||||||
|
void json_print_value(uint8_t *buf);
|
||||||
|
const char *json_strerr(void);
|
||||||
|
size_t json_errloc(void);
|
||||||
|
JSONError json_errno(void);
|
||||||
|
|
||||||
|
extern const JSONAdapter NumberAdapter;
|
||||||
|
extern const JSONAdapter StringAdapter;
|
||||||
|
extern const JSONAdapter BooleanAdapter;
|
||||||
|
|
||||||
#ifdef JSON_C_
|
#ifdef JSON_C_
|
||||||
static const char *JSONErrorMessage[JERRORNO_MAX + 1] = {
|
static const char *JSONErrorMessage[JERRORNO_MAX + 1] = {
|
||||||
"No error",
|
"No error",
|
||||||
@ -51,20 +80,26 @@ static const char *JSONErrorMessage[JERRORNO_MAX + 1] = {
|
|||||||
"Unexpected character in object",
|
"Unexpected character in object",
|
||||||
"?",
|
"?",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const char *JSONTypeName[7] = {
|
||||||
|
"[Unknown]", "String", "Number", "Object", "Array", "Boolean", "Null",
|
||||||
|
};
|
||||||
|
|
||||||
|
const JSONAdapter NumberAdapter = {
|
||||||
|
.prop_count = Number,
|
||||||
|
.props = NULL,
|
||||||
|
.size = sizeof(double),
|
||||||
|
};
|
||||||
|
const JSONAdapter StringAdapter = {
|
||||||
|
.prop_count = String,
|
||||||
|
.props = NULL,
|
||||||
|
.size = sizeof(char *),
|
||||||
|
};
|
||||||
|
const JSONAdapter BooleanAdapter = {
|
||||||
|
.prop_count = Boolean,
|
||||||
|
.props = NULL,
|
||||||
|
.size = sizeof(bool),
|
||||||
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// See client.c for usage of adapters
|
|
||||||
typedef struct {
|
|
||||||
char *path;
|
|
||||||
JSONType type;
|
|
||||||
size_t offset;
|
|
||||||
} JSONAdapter;
|
|
||||||
|
|
||||||
void json_adapt(uint8_t *buf, JSONAdapter *adapters, size_t adapter_count, void *ptr);
|
|
||||||
int json_parse(const char *src, size_t src_len, uint8_t *dst, size_t dst_len);
|
|
||||||
void json_print_value(uint8_t *buf);
|
|
||||||
const char *json_strerr();
|
|
||||||
size_t json_errloc();
|
|
||||||
JSONError json_errno();
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
37
main.c
37
main.c
@ -1,32 +1,27 @@
|
|||||||
#include "client.h"
|
#include "client.h"
|
||||||
#include "hid.h"
|
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
const char *USAGE[] = {
|
const char *USAGE[] = {
|
||||||
"jsfw client [address] [port]\n",
|
"jsfw client [address] [port] [config]\n",
|
||||||
"jsfw server [port]\n",
|
"jsfw server [port] [config]\n",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Start the server
|
// Start the server
|
||||||
void server(uint16_t port) {
|
void server(uint16_t port, char *config_path) {
|
||||||
printf("[Server (0.0.0.0:%u)]\n\n", port);
|
printf("[Server (0.0.0.0:%u)] <- %s\n\n", port, config_path);
|
||||||
|
|
||||||
pthread_t thread;
|
server_run(port, config_path);
|
||||||
pthread_create(&thread, NULL, hid_thread, NULL);
|
|
||||||
|
|
||||||
server_run(port);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the client
|
// Start the client
|
||||||
void client(char *address, uint16_t port) {
|
void client(char *address, uint16_t port, char *config_path) {
|
||||||
printf("[Client (%s:%d)]\n\n", address, port);
|
printf("[Client (%s:%d)] <- %s\n\n", address, port, config_path);
|
||||||
client_run(address, port);
|
client_run(address, port, config_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
@ -39,21 +34,23 @@ int main(int argc, char *argv[]) {
|
|||||||
char *mode = argv[1];
|
char *mode = argv[1];
|
||||||
|
|
||||||
if (strcmp(mode, "server") == 0) {
|
if (strcmp(mode, "server") == 0) {
|
||||||
if (argc < 3) {
|
if (argc < 4) {
|
||||||
panicf("Usage: %s", USAGE[1]);
|
panicf("Usage: %s", USAGE[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t port = parse_port(argv[2]);
|
uint16_t port = parse_port(argv[2]);
|
||||||
server(port);
|
char *config_path = argv[3];
|
||||||
|
server(port, config_path);
|
||||||
|
|
||||||
} else if (strcmp(mode, "client") == 0) {
|
} else if (strcmp(mode, "client") == 0) {
|
||||||
if (argc < 4) {
|
if (argc < 5) {
|
||||||
panicf("Usage: %s", USAGE[0]);
|
panicf("Usage: %s", USAGE[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
char *address = argv[2];
|
char *address = argv[2];
|
||||||
uint16_t port = parse_port(argv[3]);
|
uint16_t port = parse_port(argv[3]);
|
||||||
client(address, port);
|
char *config_path = argv[4];
|
||||||
|
client(address, port, config_path);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
printf("Unknown mode: '%s'\n", mode);
|
printf("Unknown mode: '%s'\n", mode);
|
||||||
|
268
net.c
268
net.c
@ -1,18 +1,36 @@
|
|||||||
#include "net.h"
|
#include "net.h"
|
||||||
|
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
// Deserialize the message in buf, buf must be at least 4 aligned. Returns -1 on error, otherwise returns 0
|
// Deserialize the message in buf, buf must be at least 4 aligned. Returns -1 on error, otherwise returns 0
|
||||||
// and writes result to dst
|
// and writes result to dst
|
||||||
int msg_deserialize(const uint8_t *buf, size_t len, Message *restrict dst) {
|
int msg_deserialize(const uint8_t *buf, size_t len, Message *restrict dst) {
|
||||||
|
{
|
||||||
|
if (len <= MAGIC_SIZE) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*(MAGIC_TYPE *)buf != MAGIC_BEG) {
|
||||||
|
printf("NET: No magic in message\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf += MAGIC_SIZE;
|
||||||
|
len -= MAGIC_SIZE;
|
||||||
|
}
|
||||||
// Decrement len so that it becomes the len of the data without the code.
|
// Decrement len so that it becomes the len of the data without the code.
|
||||||
if (len-- < 1)
|
if (len-- < 1)
|
||||||
return -1;
|
return -1;
|
||||||
// This ensures that only a byte is read instead of a full enum value
|
// This ensures that only a byte is read instead of a full enum value
|
||||||
uint8_t code_byte = buf[0];
|
uint8_t code_byte = buf[0];
|
||||||
MessageCode code = (MessageCode)code_byte;
|
MessageCode code = (MessageCode)code_byte;
|
||||||
|
uint32_t size = 0;
|
||||||
|
|
||||||
uint16_t abs, rel, key, *buf16;
|
uint16_t abs, rel, key, index, *buf16;
|
||||||
|
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case DeviceInfo:
|
case DeviceInfo:
|
||||||
@ -20,14 +38,16 @@ int msg_deserialize(const uint8_t *buf, size_t len, Message *restrict dst) {
|
|||||||
return -1;
|
return -1;
|
||||||
// buf + 2: a byte for code and a byte for padding
|
// buf + 2: a byte for code and a byte for padding
|
||||||
buf16 = (uint16_t *)(buf + 2);
|
buf16 = (uint16_t *)(buf + 2);
|
||||||
abs = buf16[0];
|
index = buf16[0];
|
||||||
rel = buf16[1];
|
abs = buf16[1];
|
||||||
key = buf16[2];
|
rel = buf16[2];
|
||||||
buf += 8;
|
key = buf16[3];
|
||||||
|
buf += 12;
|
||||||
if (MSS_DEVICE_INFO(abs, rel, key) > len)
|
if (MSS_DEVICE_INFO(abs, rel, key) > len)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
dst->device_info.code = code;
|
dst->device_info.code = code;
|
||||||
|
dst->device_info.index = index;
|
||||||
dst->device_info.abs_count = abs;
|
dst->device_info.abs_count = abs;
|
||||||
dst->device_info.rel_count = rel;
|
dst->device_info.rel_count = rel;
|
||||||
dst->device_info.key_count = key;
|
dst->device_info.key_count = key;
|
||||||
@ -57,21 +77,24 @@ int msg_deserialize(const uint8_t *buf, size_t len, Message *restrict dst) {
|
|||||||
buf += 2;
|
buf += 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
size = MSS_DEVICE_INFO(abs, rel, key) + 1;
|
||||||
|
break;
|
||||||
case DeviceReport:
|
case DeviceReport:
|
||||||
if (len < 7)
|
if (len < 7)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
// buf + 2: a byte for code and a byte of padding
|
// buf + 2: a byte for code and a byte of padding
|
||||||
buf16 = (uint16_t *)(buf + 2);
|
buf16 = (uint16_t *)(buf + 2);
|
||||||
abs = buf16[0];
|
index = buf16[0];
|
||||||
rel = buf16[1];
|
abs = buf16[1];
|
||||||
key = buf16[2];
|
rel = buf16[2];
|
||||||
buf += 8;
|
key = buf16[3];
|
||||||
|
buf += 12;
|
||||||
if (len < MSS_DEVICE_REPORT(abs, rel, key))
|
if (len < MSS_DEVICE_REPORT(abs, rel, key))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
dst->device_report.code = code;
|
dst->device_report.code = code;
|
||||||
|
dst->device_report.index = index;
|
||||||
dst->device_report.abs_count = abs;
|
dst->device_report.abs_count = abs;
|
||||||
dst->device_report.rel_count = rel;
|
dst->device_report.rel_count = rel;
|
||||||
dst->device_report.key_count = key;
|
dst->device_report.key_count = key;
|
||||||
@ -89,32 +112,104 @@ int msg_deserialize(const uint8_t *buf, size_t len, Message *restrict dst) {
|
|||||||
for (int i = 0; i < key; i++)
|
for (int i = 0; i < key; i++)
|
||||||
dst->device_report.key[i] = *(buf++);
|
dst->device_report.key[i] = *(buf++);
|
||||||
|
|
||||||
return 0;
|
buf += align_4(key) - key;
|
||||||
|
|
||||||
|
size = MSS_DEVICE_REPORT(abs, rel, key) + 1;
|
||||||
|
break;
|
||||||
case ControllerState:
|
case ControllerState:
|
||||||
if (len < MSS_CONTROLLER_STATE)
|
if (len < MSS_CONTROLLER_STATE)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
dst->code = code;
|
dst->code = code;
|
||||||
dst->controller_state.led[0] = buf[1];
|
dst->controller_state.index = *(uint16_t *)(buf + 2);
|
||||||
dst->controller_state.led[1] = buf[2];
|
dst->controller_state.led[0] = buf[4];
|
||||||
dst->controller_state.led[2] = buf[3];
|
dst->controller_state.led[1] = buf[5];
|
||||||
dst->controller_state.small_rumble = buf[4];
|
dst->controller_state.led[2] = buf[6];
|
||||||
dst->controller_state.big_rumble = buf[5];
|
dst->controller_state.small_rumble = buf[7];
|
||||||
dst->controller_state.flash_on = buf[6];
|
dst->controller_state.big_rumble = buf[8];
|
||||||
dst->controller_state.flash_off = buf[7];
|
dst->controller_state.flash_on = buf[9];
|
||||||
return 0;
|
dst->controller_state.flash_off = buf[10];
|
||||||
|
size = MSS_CONTROLLER_STATE + 1;
|
||||||
|
buf += size;
|
||||||
|
break;
|
||||||
|
case Request: {
|
||||||
|
if (len < 3)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
dst->code = code;
|
||||||
|
dst->request.request_count = *(uint16_t *)(buf + 2);
|
||||||
|
buf += 4; // 1 bytes for code, 1 byte for padding and 2 bytes for count
|
||||||
|
|
||||||
|
int count = dst->request.request_count;
|
||||||
|
char **tags = malloc(count * sizeof(char *));
|
||||||
|
// The length of the message, will be updated as we read more.
|
||||||
|
int expected_len = 3;
|
||||||
|
|
||||||
|
for (int i = 0; i < dst->request.request_count; i++) {
|
||||||
|
expected_len += 2;
|
||||||
|
if (len < expected_len) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t str_len = *(uint16_t *)buf;
|
||||||
|
buf += 2;
|
||||||
|
|
||||||
|
expected_len += align_2(str_len);
|
||||||
|
if (len < expected_len) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *str = malloc(str_len + 1);
|
||||||
|
str[str_len] = '\0';
|
||||||
|
|
||||||
|
strncpy(str, (char *)buf, str_len);
|
||||||
|
|
||||||
|
tags[i] = str;
|
||||||
|
|
||||||
|
buf += align_2(str_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
dst->request.requests = tags;
|
||||||
|
size = expected_len + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DeviceDestroy:
|
||||||
|
if (len < MSS_DESTROY)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
dst->code = code;
|
||||||
|
dst->destroy.index = *(uint16_t *)(buf + 2);
|
||||||
|
size = MSS_DESTROY + 1;
|
||||||
|
buf += size;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (size + MAGIC_SIZE > len + 1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*(MAGIC_TYPE *)buf != MAGIC_END) {
|
||||||
|
printf("NET: Magic not found\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return size + 2 * MAGIC_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize the message msg in buf, buf must be at least 4 aligned. Returns -1 on error (buf not big enough);
|
// Serialize the message msg in buf, buf must be at least 4 aligned. Returns -1 on error (buf not big enough);
|
||||||
int msg_serialize(uint8_t *restrict buf, size_t len, const Message *msg) {
|
int msg_serialize(uint8_t *restrict buf, size_t len, const Message *msg) {
|
||||||
// If len is 0 we can't serialize any message
|
// If len is less than the two magic and the code we can't serialize any message
|
||||||
if (len-- == 0)
|
if (len < MAGIC_SIZE * 2 + 1)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
*(MAGIC_TYPE *)buf = MAGIC_BEG;
|
||||||
|
buf += MAGIC_SIZE;
|
||||||
|
len -= MAGIC_SIZE + 1;
|
||||||
|
|
||||||
uint16_t abs, rel, key, *buf16;
|
uint16_t abs, rel, key, *buf16;
|
||||||
|
uint32_t size;
|
||||||
|
|
||||||
switch (msg->code) {
|
switch (msg->code) {
|
||||||
case DeviceInfo:
|
case DeviceInfo:
|
||||||
@ -129,10 +224,11 @@ int msg_serialize(uint8_t *restrict buf, size_t len, const Message *msg) {
|
|||||||
// buf + 2: a byte for code and a byte for padding
|
// buf + 2: a byte for code and a byte for padding
|
||||||
buf16 = (uint16_t *)(buf + 2);
|
buf16 = (uint16_t *)(buf + 2);
|
||||||
// 2 aligned here
|
// 2 aligned here
|
||||||
buf16[0] = abs;
|
buf16[0] = msg->device_info.index;
|
||||||
buf16[1] = rel;
|
buf16[1] = abs;
|
||||||
buf16[2] = key;
|
buf16[2] = rel;
|
||||||
buf += 8;
|
buf16[3] = key;
|
||||||
|
buf += 12;
|
||||||
|
|
||||||
// Back to 4 aligned
|
// Back to 4 aligned
|
||||||
for (int i = 0; i < abs; i++) {
|
for (int i = 0; i < abs; i++) {
|
||||||
@ -160,7 +256,8 @@ int msg_serialize(uint8_t *restrict buf, size_t len, const Message *msg) {
|
|||||||
buf += 2;
|
buf += 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
return MSS_DEVICE_INFO(abs, rel, key) + 1;
|
size = MSS_DEVICE_INFO(abs, rel, key) + 1;
|
||||||
|
break;
|
||||||
case DeviceReport:
|
case DeviceReport:
|
||||||
abs = msg->device_report.abs_count;
|
abs = msg->device_report.abs_count;
|
||||||
rel = msg->device_report.rel_count;
|
rel = msg->device_report.rel_count;
|
||||||
@ -171,10 +268,11 @@ int msg_serialize(uint8_t *restrict buf, size_t len, const Message *msg) {
|
|||||||
buf[0] = (uint8_t)msg->code;
|
buf[0] = (uint8_t)msg->code;
|
||||||
// buf + 2: a byte for code and a byte for padding
|
// buf + 2: a byte for code and a byte for padding
|
||||||
buf16 = (uint16_t *)(buf + 2);
|
buf16 = (uint16_t *)(buf + 2);
|
||||||
buf16[0] = abs;
|
buf16[0] = msg->device_report.index;
|
||||||
buf16[1] = rel;
|
buf16[1] = abs;
|
||||||
buf16[2] = key;
|
buf16[2] = rel;
|
||||||
buf += 8;
|
buf16[3] = key;
|
||||||
|
buf += 12;
|
||||||
// We're 4 aligned already
|
// We're 4 aligned already
|
||||||
for (int i = 0; i < abs; i++) {
|
for (int i = 0; i < abs; i++) {
|
||||||
*(uint32_t *)buf = msg->device_report.abs[i];
|
*(uint32_t *)buf = msg->device_report.abs[i];
|
||||||
@ -189,22 +287,114 @@ int msg_serialize(uint8_t *restrict buf, size_t len, const Message *msg) {
|
|||||||
for (int i = 0; i < key; i++)
|
for (int i = 0; i < key; i++)
|
||||||
*(buf++) = msg->device_report.key[i];
|
*(buf++) = msg->device_report.key[i];
|
||||||
|
|
||||||
return MSS_DEVICE_REPORT(abs, rel, key) + 1;
|
size = MSS_DEVICE_REPORT(abs, rel, key) + 1;
|
||||||
|
buf += align_4(key) - key;
|
||||||
|
break;
|
||||||
case ControllerState:
|
case ControllerState:
|
||||||
if (len < MSS_CONTROLLER_STATE)
|
if (len < MSS_CONTROLLER_STATE)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
buf[0] = (uint8_t)msg->code;
|
buf[0] = (uint8_t)msg->code;
|
||||||
buf[1] = msg->controller_state.led[0];
|
|
||||||
buf[2] = msg->controller_state.led[1];
|
*(uint16_t *)(buf + 2) = msg->controller_state.index;
|
||||||
buf[3] = msg->controller_state.led[2];
|
|
||||||
buf[4] = msg->controller_state.small_rumble;
|
buf[4] = msg->controller_state.led[0];
|
||||||
buf[5] = msg->controller_state.big_rumble;
|
buf[5] = msg->controller_state.led[1];
|
||||||
buf[6] = msg->controller_state.flash_on;
|
buf[6] = msg->controller_state.led[2];
|
||||||
buf[7] = msg->controller_state.flash_off;
|
buf[7] = msg->controller_state.small_rumble;
|
||||||
return MSS_CONTROLLER_STATE + 1;
|
buf[8] = msg->controller_state.big_rumble;
|
||||||
|
buf[9] = msg->controller_state.flash_on;
|
||||||
|
buf[10] = msg->controller_state.flash_off;
|
||||||
|
size = MSS_CONTROLLER_STATE + 1;
|
||||||
|
buf += size;
|
||||||
|
break;
|
||||||
|
case Request: {
|
||||||
|
int expected_len = MSS_REQUEST(msg->request.request_count);
|
||||||
|
if (len < expected_len)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
buf[0] = (uint8_t)msg->code;
|
||||||
|
buf16 = (uint16_t *)(buf + 2);
|
||||||
|
buf16[0] = msg->request.request_count;
|
||||||
|
|
||||||
|
buf += 4;
|
||||||
|
buf16++;
|
||||||
|
|
||||||
|
for (int i = 0; i < msg->request.request_count; i++) {
|
||||||
|
int str_len = strlen(msg->request.requests[i]);
|
||||||
|
int byte_len = align_2(str_len);
|
||||||
|
*buf16++ = str_len;
|
||||||
|
buf = (uint8_t *)buf16;
|
||||||
|
|
||||||
|
expected_len += byte_len + 2;
|
||||||
|
if (len < expected_len) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
strncpy((char *)buf, msg->request.requests[i], str_len);
|
||||||
|
buf += byte_len;
|
||||||
|
// Buf has to be aligned here since byte_len is two aligned and we started off two aligned
|
||||||
|
buf16 = (uint16_t *)buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
size = expected_len;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DeviceDestroy:
|
||||||
|
if (len < MSS_DESTROY)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
buf[0] = (uint8_t)msg->code;
|
||||||
|
|
||||||
|
*(uint16_t *)(buf + 2) = msg->controller_state.index;
|
||||||
|
size = MSS_DESTROY + 1;
|
||||||
|
buf += size;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
printf("ERR(msg_serialize): Trying to serialize unknown message of code %d\n", msg->code);
|
printf("ERR(msg_serialize): Trying to serialize unknown message of code %d\n", msg->code);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (size + MAGIC_SIZE > len) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*(MAGIC_TYPE *)buf = MAGIC_END;
|
||||||
|
|
||||||
|
return size + MAGIC_SIZE * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void msg_free(Message *msg) {
|
||||||
|
if (msg->code == Request) {
|
||||||
|
for (int i = 0; i < msg->request.request_count; i++) {
|
||||||
|
free(msg->request.requests[i]);
|
||||||
|
}
|
||||||
|
free(msg->request.requests);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_message_buffer(const uint8_t *buf, int len) {
|
||||||
|
bool last_beg = false;
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
if (i + MAGIC_SIZE <= len) {
|
||||||
|
MAGIC_TYPE magic = *(MAGIC_TYPE *)(&buf[i]);
|
||||||
|
if (magic == MAGIC_BEG) {
|
||||||
|
printf(" \033[32m%08X\033[0m", magic);
|
||||||
|
i += MAGIC_SIZE - 1;
|
||||||
|
last_beg = true;
|
||||||
|
continue;
|
||||||
|
} else if (magic == MAGIC_END) {
|
||||||
|
printf(" \033[32m%08X\033[0m", magic);
|
||||||
|
i += MAGIC_SIZE - 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last_beg) {
|
||||||
|
last_beg = false;
|
||||||
|
printf(" \033[034m%02X\033[0m", buf[i]);
|
||||||
|
} else {
|
||||||
|
printf(" %02X", buf[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
54
net.h
54
net.h
@ -1,15 +1,24 @@
|
|||||||
// vi:ft=c
|
// vi:ft=c
|
||||||
#ifndef NET_H_
|
#ifndef NET_H_
|
||||||
#define NET_H_
|
#define NET_H_
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
#include <linux/input-event-codes.h>
|
#include <linux/input-event-codes.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#define MAGIC_TYPE uint32_t
|
||||||
|
#define MAGIC_SIZE sizeof(MAGIC_TYPE)
|
||||||
|
static const MAGIC_TYPE MAGIC_BEG = 0xDEADCAFE;
|
||||||
|
static const MAGIC_TYPE MAGIC_END = 0xCAFEDEAD;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
NoMessage = 0,
|
||||||
DeviceInfo = 1,
|
DeviceInfo = 1,
|
||||||
DeviceReport = 2,
|
DeviceReport = 2,
|
||||||
DeviceDestroy = 3,
|
DeviceDestroy = 3,
|
||||||
ControllerState = 4,
|
ControllerState = 4,
|
||||||
|
Request = 5,
|
||||||
} MessageCode;
|
} MessageCode;
|
||||||
|
|
||||||
// Alignment 4
|
// Alignment 4
|
||||||
@ -17,6 +26,8 @@ typedef struct {
|
|||||||
MessageCode code;
|
MessageCode code;
|
||||||
// + 1 byte of padding
|
// + 1 byte of padding
|
||||||
|
|
||||||
|
uint16_t index;
|
||||||
|
|
||||||
uint16_t abs_count;
|
uint16_t abs_count;
|
||||||
uint16_t rel_count;
|
uint16_t rel_count;
|
||||||
uint16_t key_count;
|
uint16_t key_count;
|
||||||
@ -33,7 +44,7 @@ typedef struct {
|
|||||||
|
|
||||||
uint16_t key_id[KEY_CNT];
|
uint16_t key_id[KEY_CNT];
|
||||||
} MessageDeviceInfo;
|
} MessageDeviceInfo;
|
||||||
#define MSS_DEVICE_INFO(abs, rel, key) (8 + abs * 24 + rel * 2 + key * 2 + 1)
|
#define MSS_DEVICE_INFO(abs, rel, key) (10 + abs * 24 + rel * 2 + key * 2 + 1)
|
||||||
// MSS -> Message Serialized Size:
|
// MSS -> Message Serialized Size:
|
||||||
// Size of the data of the message when serialized (no alignment / padding)
|
// Size of the data of the message when serialized (no alignment / padding)
|
||||||
|
|
||||||
@ -41,6 +52,7 @@ typedef struct {
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
MessageCode code;
|
MessageCode code;
|
||||||
// + 1 byte of padding
|
// + 1 byte of padding
|
||||||
|
uint16_t index;
|
||||||
|
|
||||||
uint16_t abs_count;
|
uint16_t abs_count;
|
||||||
uint16_t rel_count;
|
uint16_t rel_count;
|
||||||
@ -50,28 +62,50 @@ typedef struct {
|
|||||||
uint32_t rel[REL_CNT];
|
uint32_t rel[REL_CNT];
|
||||||
uint8_t key[KEY_CNT];
|
uint8_t key[KEY_CNT];
|
||||||
} MessageDeviceReport;
|
} MessageDeviceReport;
|
||||||
#define MSS_DEVICE_REPORT(abs, rel, key) (6 + abs * 4 + rel * 4 + key * 1 + 1)
|
#define MSS_DEVICE_REPORT(abs, rel, key) (11 + abs * 4 + rel * 4 + align_4(key))
|
||||||
|
|
||||||
// 1 aligned
|
// 1 aligned
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MessageCode code;
|
MessageCode code;
|
||||||
|
// + 1 byte of padding
|
||||||
|
|
||||||
uint8_t led[3];
|
uint16_t index;
|
||||||
uint8_t small_rumble;
|
uint8_t led[3];
|
||||||
uint8_t big_rumble;
|
uint8_t small_rumble;
|
||||||
uint8_t flash_on;
|
uint8_t big_rumble;
|
||||||
uint8_t flash_off;
|
uint8_t flash_on;
|
||||||
|
uint8_t flash_off;
|
||||||
} MessageControllerState;
|
} MessageControllerState;
|
||||||
#define MSS_CONTROLLER_STATE 7
|
#define MSS_CONTROLLER_STATE 10
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
MessageCode code;
|
||||||
|
|
||||||
|
char **requests;
|
||||||
|
uint16_t request_count;
|
||||||
|
} MessageRequest;
|
||||||
|
#define MSS_REQUEST(count) (2 + 2 * count)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
MessageCode code;
|
||||||
|
// + 1 byte of padding
|
||||||
|
|
||||||
|
uint16_t index;
|
||||||
|
} MessageDestroy;
|
||||||
|
#define MSS_DESTROY 3
|
||||||
|
|
||||||
typedef union {
|
typedef union {
|
||||||
MessageCode code;
|
MessageCode code;
|
||||||
|
MessageRequest request;
|
||||||
|
MessageDestroy destroy;
|
||||||
MessageDeviceInfo device_info;
|
MessageDeviceInfo device_info;
|
||||||
MessageDeviceReport device_report;
|
MessageDeviceReport device_report;
|
||||||
MessageControllerState controller_state;
|
MessageControllerState controller_state;
|
||||||
} Message;
|
} Message;
|
||||||
|
|
||||||
int msg_deserialize(const uint8_t *buf, size_t len, Message *restrict dst);
|
int msg_deserialize(const uint8_t *buf, size_t len, Message *restrict dst);
|
||||||
int msg_serialize(uint8_t *restrict buf, size_t len, const Message *msg);
|
int msg_serialize(uint8_t *restrict buf, size_t len, const Message *msg);
|
||||||
|
void msg_free(Message *msg);
|
||||||
|
void print_message_buffer(const uint8_t *buf, int len);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
449
server.c
449
server.c
@ -1,7 +1,11 @@
|
|||||||
|
#include "server.h"
|
||||||
|
|
||||||
#include "const.h"
|
#include "const.h"
|
||||||
#include "hid.h"
|
#include "hid.h"
|
||||||
|
#include "json.h"
|
||||||
#include "net.h"
|
#include "net.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
#include "vec.h"
|
||||||
|
|
||||||
#include <linux/input-event-codes.h>
|
#include <linux/input-event-codes.h>
|
||||||
#include <linux/input.h>
|
#include <linux/input.h>
|
||||||
@ -9,6 +13,8 @@
|
|||||||
#include <netinet/tcp.h>
|
#include <netinet/tcp.h>
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@ -20,8 +26,194 @@
|
|||||||
struct Connection {
|
struct Connection {
|
||||||
int socket;
|
int socket;
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
|
bool closed;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct DeviceThreadArgs {
|
||||||
|
int index;
|
||||||
|
char *tag;
|
||||||
|
Controller **controller;
|
||||||
|
struct Connection *conn;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void default_timespec(void *ptr) { *(struct timespec *)ptr = POLL_DEVICE_INTERVAL; }
|
||||||
|
static void default_request_timeout(void *ptr) { *(uint32_t *)ptr = REQUEST_TIMEOUT; }
|
||||||
|
|
||||||
|
const JSONPropertyAdapter FilterAdapterProps[] = {
|
||||||
|
{".uniq", &StringAdapter, offsetof(ControllerFilter, uniq), default_to_zero_u64, tsf_uniq_to_u64},
|
||||||
|
{".vendor", &StringAdapter, offsetof(ControllerFilter, vendor), default_to_negative_one_i32, tsf_hex_to_i32 },
|
||||||
|
{".product", &StringAdapter, offsetof(ControllerFilter, product), default_to_negative_one_i32, tsf_hex_to_i32 },
|
||||||
|
{".js", &BooleanAdapter, offsetof(ControllerFilter, js), default_to_false, NULL },
|
||||||
|
{".name", &StringAdapter, offsetof(ControllerFilter, name), default_to_null, NULL },
|
||||||
|
};
|
||||||
|
const JSONAdapter FilterAdapter = {
|
||||||
|
.props = FilterAdapterProps,
|
||||||
|
.prop_count = sizeof(FilterAdapterProps) / sizeof(JSONPropertyAdapter),
|
||||||
|
.size = sizeof(ControllerFilter),
|
||||||
|
};
|
||||||
|
|
||||||
|
const JSONPropertyAdapter ControllerAdapterProps[] = {
|
||||||
|
{".filter", &FilterAdapter, offsetof(ServerConfigController, filter), NULL, NULL},
|
||||||
|
{".tag", &StringAdapter, offsetof(ServerConfigController, tag), default_to_null, NULL},
|
||||||
|
{".properties.duplicate", &BooleanAdapter, offsetof(ServerConfigController, duplicate), default_to_false, NULL},
|
||||||
|
{".properties.ps4_hidraw", &BooleanAdapter, offsetof(ServerConfigController, ps4_hidraw), default_to_false, NULL},
|
||||||
|
};
|
||||||
|
const JSONAdapter ControllerAdapter = {
|
||||||
|
.props = ControllerAdapterProps,
|
||||||
|
.prop_count = sizeof(ControllerAdapterProps) / sizeof(JSONPropertyAdapter),
|
||||||
|
.size = sizeof(ServerConfigController),
|
||||||
|
};
|
||||||
|
|
||||||
|
const JSONPropertyAdapter ConfigAdapterProps[] = {
|
||||||
|
{".controllers[]", &ControllerAdapter, offsetof(ServerConfig, controllers), default_to_null, NULL },
|
||||||
|
{".poll_interval", &NumberAdapter, offsetof(ServerConfig, poll_interval), default_timespec, tsf_numsec_to_timespec},
|
||||||
|
{".request_timeout", &NumberAdapter, offsetof(ServerConfig, request_timeout), default_request_timeout, tsf_numsec_to_intms }
|
||||||
|
};
|
||||||
|
const JSONAdapter ConfigAdapter = {
|
||||||
|
.props = ConfigAdapterProps,
|
||||||
|
.prop_count = sizeof(ConfigAdapterProps) / sizeof(JSONPropertyAdapter),
|
||||||
|
.size = sizeof(ServerConfig),
|
||||||
|
};
|
||||||
|
|
||||||
|
static ServerConfig config;
|
||||||
|
static pthread_key_t device_args_key;
|
||||||
|
static sigset_t empty_sigset;
|
||||||
|
|
||||||
|
#define TRAP(sig, handler) \
|
||||||
|
if (sigaction(sig, &(struct sigaction){.sa_handler = handler, .sa_mask = empty_sigset, .sa_flags = 0}, NULL) != 0) \
|
||||||
|
printf("SERVER: can't trap " #sig ".\n")
|
||||||
|
#define TRAP_IGN(sig) \
|
||||||
|
if (sigaction(sig, &(struct sigaction){{SIG_IGN}}, NULL) != 0) \
|
||||||
|
printf("SERVER: can't ignore " #sig ".\n")
|
||||||
|
|
||||||
|
void device_thread_exit(int _sig) {
|
||||||
|
struct DeviceThreadArgs *args = pthread_getspecific(device_args_key);
|
||||||
|
printf("CONN(%d): [%d] exiting\n", args->conn->id, args->index);
|
||||||
|
|
||||||
|
Controller *ctr = *args->controller;
|
||||||
|
if (ctr != NULL) {
|
||||||
|
return_device(ctr);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(args->tag);
|
||||||
|
free(args);
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *device_thread(void *args_) {
|
||||||
|
struct DeviceThreadArgs *args = args_;
|
||||||
|
|
||||||
|
pthread_setspecific(device_args_key, args);
|
||||||
|
|
||||||
|
TRAP_IGN(SIGPIPE);
|
||||||
|
TRAP(SIGTERM, device_thread_exit);
|
||||||
|
|
||||||
|
uint8_t buf[2048] __attribute__((aligned(4))) = {0};
|
||||||
|
MessageDeviceInfo dev_info;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
*args->controller = NULL;
|
||||||
|
Controller *ctr = get_device(args->tag, &args->conn->closed);
|
||||||
|
if (ctr == NULL) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
*args->controller = ctr;
|
||||||
|
dev_info = ctr->dev.device_info;
|
||||||
|
dev_info.index = args->index;
|
||||||
|
|
||||||
|
printf("CONN(%d): [%d] Found suitable [%s] device: '%s' (%016lx)\n", args->conn->id, args->index, args->tag,
|
||||||
|
ctr->dev.name, ctr->dev.id);
|
||||||
|
|
||||||
|
// Send over device info
|
||||||
|
{
|
||||||
|
int len = msg_serialize(buf, 2048, (Message *)&dev_info);
|
||||||
|
if (write(args->conn->socket, buf, len) == -1) {
|
||||||
|
printf("CONN(%d): [%d] Couldn't send device info\n", args->conn->id, args->index);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageDeviceReport report = {0};
|
||||||
|
|
||||||
|
report.code = DeviceReport;
|
||||||
|
report.abs_count = ctr->dev.device_info.abs_count;
|
||||||
|
report.rel_count = ctr->dev.device_info.rel_count;
|
||||||
|
report.key_count = ctr->dev.device_info.key_count;
|
||||||
|
report.index = args->index;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
struct input_event event;
|
||||||
|
|
||||||
|
int len = read(ctr->dev.event, &event, sizeof(struct input_event));
|
||||||
|
|
||||||
|
if (len <= 0) {
|
||||||
|
// We lost the device, so we mark it as broken (we forget it) and try to get a new one (in the next iteration of
|
||||||
|
// the outer while)
|
||||||
|
forget_device(ctr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len < sizeof(struct input_event)) {
|
||||||
|
printf("CONN(%d): [%d] error reading event\n", args->conn->id, args->index);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type == EV_SYN) {
|
||||||
|
int len = msg_serialize(buf, 2048, (Message *)&report);
|
||||||
|
|
||||||
|
if (len < 0) {
|
||||||
|
printf("CONN(%d): [%d] Couldn't serialize report %d\n", args->conn->id, args->index, len);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
send(args->conn->socket, buf, len, 0);
|
||||||
|
} else if (event.type == EV_ABS) {
|
||||||
|
int index = ctr->dev.mapping.abs_indices[event.code];
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
printf("CONN(%d): [%d] Invalid abs\n", args->conn->id, args->index);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
report.abs[index] = event.value;
|
||||||
|
} else if (event.type == EV_REL) {
|
||||||
|
int index = ctr->dev.mapping.rel_indices[event.code];
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
printf("CONN(%d): [%d] Invalid rel\n", args->conn->id, args->index);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
report.rel[index] = event.value;
|
||||||
|
} else if (event.type == EV_KEY) {
|
||||||
|
int index = ctr->dev.mapping.key_indices[event.code];
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
printf("CONN(%d): [%d] Invalid key\n", args->conn->id, args->index);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
report.key[index] = !!event.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send device destroy message
|
||||||
|
{
|
||||||
|
MessageDestroy dstr;
|
||||||
|
dstr.code = DeviceDestroy;
|
||||||
|
dstr.index = args->index;
|
||||||
|
|
||||||
|
int len = msg_serialize(buf, 2048, (Message *)&dstr);
|
||||||
|
if (write(args->conn->socket, buf, len) == -1) {
|
||||||
|
printf("CONN(%d): [%d] Couldn't send device destroy message\n", args->conn->id, args->index);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
device_thread_exit(SIGTERM);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
void *server_handle_conn(void *args_) {
|
void *server_handle_conn(void *args_) {
|
||||||
struct Connection *args = args_;
|
struct Connection *args = args_;
|
||||||
|
|
||||||
@ -36,154 +228,190 @@ void *server_handle_conn(void *args_) {
|
|||||||
if (setsockopt(args->socket, SOL_TCP, TCP_KEEPINTVL, &TCP_KEEPALIVE_RETRY_INTERVAL, sizeof(int)) != 0)
|
if (setsockopt(args->socket, SOL_TCP, TCP_KEEPINTVL, &TCP_KEEPALIVE_RETRY_INTERVAL, sizeof(int)) != 0)
|
||||||
printf("ERR(server_handle_conn): Setting idle retry interval\n");
|
printf("ERR(server_handle_conn): Setting idle retry interval\n");
|
||||||
|
|
||||||
uint8_t buf[2048] __attribute__((aligned(4))) = {};
|
uint8_t buf[2048] __attribute__((aligned(4))) = {0};
|
||||||
|
|
||||||
PhysicalDevice dev = get_device();
|
char *closing_message = "";
|
||||||
printf("CONN(%u): got device '%s'\n", args->id, dev.name);
|
bool got_request = false;
|
||||||
|
Vec device_threads = vec_of(pthread_t);
|
||||||
|
Vec device_controllers = vec_of(Controller *);
|
||||||
|
|
||||||
char *closing_message = "";
|
struct pollfd pfd = {.fd = args->socket, .events = POLLIN};
|
||||||
|
|
||||||
int len = msg_serialize(buf, 2048, (Message *)&dev.device_info);
|
|
||||||
if (len > 0) {
|
|
||||||
if (write(args->socket, buf, len) == -1) {
|
|
||||||
perror("SERVER: Couldn't send device info, ");
|
|
||||||
closing_message = "Socket error";
|
|
||||||
goto conn_end;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
perror("SERVER: Couldn't serialize device info, ");
|
|
||||||
closing_message = "Device info error";
|
|
||||||
goto conn_end;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct pollfd pfds[2] = {};
|
|
||||||
struct pollfd *socket_poll = &pfds[0];
|
|
||||||
struct pollfd *event_poll = &pfds[1];
|
|
||||||
|
|
||||||
socket_poll->fd = args->socket;
|
|
||||||
socket_poll->events = POLLIN;
|
|
||||||
event_poll->fd = dev.event;
|
|
||||||
event_poll->events = POLLIN;
|
|
||||||
|
|
||||||
MessageDeviceReport report = {};
|
|
||||||
|
|
||||||
report.code = DeviceReport;
|
|
||||||
report.abs_count = dev.device_info.abs_count;
|
|
||||||
report.rel_count = dev.device_info.rel_count;
|
|
||||||
report.key_count = dev.device_info.key_count;
|
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
int rc = poll(pfds, 2, -1);
|
int rc = poll(&pfd, 1, config.request_timeout);
|
||||||
if (rc < 0) { // error (connection closed)
|
|
||||||
|
// If poll timed out
|
||||||
|
if (rc == 0) {
|
||||||
|
if (!got_request) {
|
||||||
|
printf("CONN(%d): Didn't get a device request within %i.%03ds\n", args->id, config.request_timeout / 1000,
|
||||||
|
config.request_timeout % 1000);
|
||||||
|
closing_message = "Timed out";
|
||||||
|
goto conn_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
} else if (rc < 0) { // If poll failed (basically never)
|
||||||
closing_message = "Poll error";
|
closing_message = "Poll error";
|
||||||
goto conn_end;
|
goto conn_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown connection if we lost the peer
|
// Test for error on socket
|
||||||
if (socket_poll->revents & POLLHUP || socket_poll->revents & POLLERR) {
|
if (pfd.revents & POLLHUP || pfd.revents & POLLERR) {
|
||||||
closing_message = "Lost peer";
|
closing_message = "Lost peer";
|
||||||
goto conn_end;
|
goto conn_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (socket_poll->revents & POLLIN) {
|
// Receive data
|
||||||
int len = recv(args->socket, buf, 2048, 0);
|
int len = recv(args->socket, buf, 2048, 0);
|
||||||
|
if (len <= 0) {
|
||||||
if (len <= 0) {
|
closing_message = "Lost peer (from recv)";
|
||||||
closing_message = "Lost peer";
|
|
||||||
goto conn_end;
|
|
||||||
}
|
|
||||||
|
|
||||||
Message msg;
|
|
||||||
if (msg_deserialize(buf, len, &msg) == 0) {
|
|
||||||
|
|
||||||
if (msg.code == ControllerState) {
|
|
||||||
apply_controller_state(&dev, (MessageControllerState *)&msg);
|
|
||||||
} else {
|
|
||||||
printf("CONN(%d): Illegal message\n", args->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
printf("CONN(%d): Couldn't parse message.\n", args->id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown connection if we lost the device
|
|
||||||
if (event_poll->revents & POLLHUP || event_poll->revents & POLLERR) {
|
|
||||||
closing_message = "Lost device";
|
|
||||||
goto conn_end;
|
goto conn_end;
|
||||||
|
} else if (len > 1 + MAGIC_SIZE * 2) {
|
||||||
|
printf("CONN(%d): Got message: ", args->id);
|
||||||
|
printf("\n");
|
||||||
|
} else {
|
||||||
|
printf("CONN(%d): Malformed message\n", args->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event_poll->revents & POLLIN) {
|
// Parse message
|
||||||
struct input_event event;
|
Message msg;
|
||||||
|
int msg_len = msg_deserialize(buf, len, &msg);
|
||||||
int len = read(dev.event, &event, sizeof(struct input_event));
|
if (msg_len < 0) {
|
||||||
|
if (len > 1 + MAGIC_SIZE * 2) {
|
||||||
if (len <= 0) {
|
printf("CONN(%d): Couldn't parse message: ", args->id);
|
||||||
closing_message = "Lost device";
|
print_message_buffer(buf, len);
|
||||||
goto conn_end;
|
printf("\n");
|
||||||
|
} else {
|
||||||
|
printf("CONN(%d): Couldn't parse message", args->id);
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (len < sizeof(struct input_event)) {
|
// Handle message
|
||||||
printf("CONN(%d): error reading event\n", args->id);
|
if (msg.code == ControllerState) {
|
||||||
|
int i = msg.controller_state.index;
|
||||||
|
if (i >= device_controllers.len) {
|
||||||
|
printf("CONN(%d): Invalid controller index in controller state message\n", args->id);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type == EV_SYN) {
|
Controller *ctr = *(Controller **)vec_get(&device_controllers, i);
|
||||||
int len = msg_serialize(buf, 2048, (Message *)&report);
|
if (ctr == NULL) {
|
||||||
|
printf("CONN(%d): Received controller state message but the device hasn't yet been received\n", args->id);
|
||||||
if (len < 0) {
|
continue;
|
||||||
printf("CONN(%d): Couldn't serialize report %d\n", args->id, len);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
write(args->socket, buf, len);
|
|
||||||
} else if (event.type == EV_ABS) {
|
|
||||||
int index = dev.mapping.abs_indices[event.code];
|
|
||||||
|
|
||||||
if (index < 0) {
|
|
||||||
printf("CONN(%d): Invalid abs\n", args->id);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
report.abs[index] = event.value;
|
|
||||||
} else if (event.type == EV_REL) {
|
|
||||||
int index = dev.mapping.rel_indices[event.code];
|
|
||||||
|
|
||||||
if (index < 0) {
|
|
||||||
printf("CONN(%d): Invalid rel\n", args->id);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
report.rel[index] = event.value;
|
|
||||||
} else if (event.type == EV_KEY) {
|
|
||||||
int index = dev.mapping.key_indices[event.code];
|
|
||||||
|
|
||||||
if (index < 0) {
|
|
||||||
printf("CONN(%d): Invalid key\n", args->id);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
report.key[index] = !!event.value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply_controller_state(ctr, &msg.controller_state);
|
||||||
|
} else if (msg.code == Request) {
|
||||||
|
if (got_request) {
|
||||||
|
printf("CONN(%d): Illegal Request message after initial request\n", args->id);
|
||||||
|
msg_free(&msg);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
got_request = true;
|
||||||
|
|
||||||
|
printf("CONN(%d): Got client request\n", args->id);
|
||||||
|
|
||||||
|
for (int i = 0; i < msg.request.request_count; i++) {
|
||||||
|
int index = device_controllers.len;
|
||||||
|
Controller *ctr = NULL;
|
||||||
|
vec_push(&device_controllers, &ctr);
|
||||||
|
|
||||||
|
struct DeviceThreadArgs *dev_args = malloc(sizeof(struct DeviceThreadArgs));
|
||||||
|
dev_args->controller = vec_get(&device_controllers, index);
|
||||||
|
dev_args->tag = strdup(msg.request.requests[i]);
|
||||||
|
dev_args->conn = args;
|
||||||
|
dev_args->index = index;
|
||||||
|
|
||||||
|
pthread_t thread;
|
||||||
|
pthread_create(&thread, NULL, device_thread, dev_args);
|
||||||
|
vec_push(&device_threads, &thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
msg_free(&msg);
|
||||||
|
} else {
|
||||||
|
printf("CONN(%d): Illegal message\n", args->id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
conn_end:
|
conn_end:
|
||||||
shutdown(args->socket, SHUT_RDWR);
|
shutdown(args->socket, SHUT_RDWR);
|
||||||
printf("CONN(%u): connection closed (%s)\n", args->id, closing_message);
|
printf("CONN(%u): connection closed (%s)\n", args->id, closing_message);
|
||||||
return_device(&dev);
|
args->closed = true;
|
||||||
|
for (int i = 0; i < device_threads.len; i++) {
|
||||||
|
pthread_t thread = *(pthread_t *)vec_get(&device_threads, i);
|
||||||
|
pthread_kill(thread, SIGTERM);
|
||||||
|
pthread_join(thread, NULL);
|
||||||
|
}
|
||||||
free(args);
|
free(args);
|
||||||
|
vec_free(device_threads);
|
||||||
|
vec_free(device_controllers);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void server_run(uint16_t port) {
|
static int sockfd;
|
||||||
|
|
||||||
|
void clean_exit(int _sig) {
|
||||||
|
printf("\rSERVER: exiting\n");
|
||||||
|
close(sockfd);
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void server_run(uint16_t port, char *config_path) {
|
||||||
|
sigemptyset(&empty_sigset);
|
||||||
|
pthread_key_create(&device_args_key, NULL);
|
||||||
printf("SERVER: start\n");
|
printf("SERVER: start\n");
|
||||||
|
|
||||||
|
// Parse the config
|
||||||
|
{
|
||||||
|
FILE *configfd = fopen(config_path, "r");
|
||||||
|
if (configfd == NULL) {
|
||||||
|
perror("SERVER: Couldn't open config file");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *cbuf = malloc(8192);
|
||||||
|
uint8_t *jbuf = (uint8_t *)cbuf + 4096;
|
||||||
|
|
||||||
|
int len = fread(cbuf, 1, 4096, configfd);
|
||||||
|
if (json_parse(cbuf, len, jbuf, 4096) != 0) {
|
||||||
|
printf("SERVER: Couldn't parse config, %s (at index %lu)\n", json_strerr(), json_errloc());
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
json_adapt(jbuf, &ConfigAdapter, &config);
|
||||||
|
|
||||||
|
free(cbuf);
|
||||||
|
fclose(configfd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the hid thread
|
||||||
|
{
|
||||||
|
pthread_t _thread;
|
||||||
|
pthread_create(&_thread, NULL, hid_thread, &config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the TCP server
|
||||||
|
|
||||||
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
if (sock < 0) {
|
if (sock < 0) {
|
||||||
panicf("Couldn't open socket\n");
|
panicf("Couldn't open socket\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sockaddr_in addr = {};
|
sockfd = sock;
|
||||||
|
|
||||||
|
TRAP(SIGINT, clean_exit);
|
||||||
|
TRAP(SIGHUP, clean_exit);
|
||||||
|
TRAP(SIGQUIT, clean_exit);
|
||||||
|
TRAP(SIGTERM, clean_exit);
|
||||||
|
|
||||||
|
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) < 0)
|
||||||
|
printf("SERVER: error when trying to enable SO_REUSEADDR\n");
|
||||||
|
if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &(int){1}, sizeof(int)) < 0)
|
||||||
|
printf("SERVER: error when trying to enable SO_REUSEPORT\n");
|
||||||
|
|
||||||
|
struct sockaddr_in addr = {0};
|
||||||
addr.sin_family = AF_INET;
|
addr.sin_family = AF_INET;
|
||||||
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
addr.sin_port = htons(port);
|
addr.sin_port = htons(port);
|
||||||
@ -203,6 +431,7 @@ void server_run(uint16_t port) {
|
|||||||
struct Connection conn;
|
struct Connection conn;
|
||||||
|
|
||||||
conn.socket = accept(sock, &con_addr, &con_len);
|
conn.socket = accept(sock, &con_addr, &con_len);
|
||||||
|
conn.closed = false;
|
||||||
|
|
||||||
if (conn.socket >= 0) {
|
if (conn.socket >= 0) {
|
||||||
printf("SERVER: got connection\n");
|
printf("SERVER: got connection\n");
|
||||||
|
30
server.h
30
server.h
@ -1,8 +1,36 @@
|
|||||||
// vi:ft=c
|
// vi:ft=c
|
||||||
#ifndef SERVER_H_
|
#ifndef SERVER_H_
|
||||||
#define SERVER_H_
|
#define SERVER_H_
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
void server_run(uint16_t port);
|
typedef struct {
|
||||||
|
// the 0 uniq represents no filter
|
||||||
|
uint64_t uniq;
|
||||||
|
// negative values means no filter
|
||||||
|
int32_t vendor;
|
||||||
|
// negative values means no filter
|
||||||
|
int32_t product;
|
||||||
|
bool js;
|
||||||
|
// NULL means no filter
|
||||||
|
char *name;
|
||||||
|
} ControllerFilter;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ControllerFilter filter;
|
||||||
|
char *tag;
|
||||||
|
bool duplicate;
|
||||||
|
bool ps4_hidraw;
|
||||||
|
} ServerConfigController;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ServerConfigController *controllers;
|
||||||
|
size_t controller_count;
|
||||||
|
struct timespec poll_interval;
|
||||||
|
uint32_t request_timeout;
|
||||||
|
} ServerConfig;
|
||||||
|
|
||||||
|
void server_run(uint16_t port, char *config_path);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
145
util.c
145
util.c
@ -1,9 +1,12 @@
|
|||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
#ifndef __has_builtin
|
#ifndef __has_builtin
|
||||||
#define __has_builtin(_) 0
|
#define __has_builtin(_) 0
|
||||||
@ -34,3 +37,145 @@ uint8_t parse_hex_digit(char h) {
|
|||||||
else
|
else
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Defaults for json parsing
|
||||||
|
void default_to_null(void *ptr) { *(void **)ptr = NULL; }
|
||||||
|
void default_to_false(void *ptr) { *(bool *)ptr = false; }
|
||||||
|
void default_to_zero_u8(void *ptr) { *(uint8_t *)ptr = 0; }
|
||||||
|
void default_to_zero_u32(void *ptr) { *(uint32_t *)ptr = 0; }
|
||||||
|
void default_to_zero_u64(void *ptr) { *(uint64_t *)ptr = 0; }
|
||||||
|
void default_to_zero_size(void *ptr) { *(size_t *)ptr = 0; }
|
||||||
|
void default_to_zero_double(void *ptr) { *(double *)ptr = 0.0; }
|
||||||
|
void default_to_one_size(void *ptr) { *(size_t *)ptr = 1; }
|
||||||
|
void default_to_negative_one_i32(void *ptr) { *(int32_t *)ptr = -1; }
|
||||||
|
|
||||||
|
// Transformers for json parsing
|
||||||
|
void tsf_numsec_to_timespec(void *arg, void *ptr) {
|
||||||
|
double seconds = *(double *)arg;
|
||||||
|
|
||||||
|
struct timespec ts;
|
||||||
|
ts.tv_sec = floor(seconds);
|
||||||
|
ts.tv_nsec = (seconds - floor(seconds)) * 1000000000;
|
||||||
|
|
||||||
|
*(struct timespec *)ptr = ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tsf_numsec_to_intms(void *arg, void *ptr) {
|
||||||
|
double seconds = *(double *)arg;
|
||||||
|
*(uint32_t *)ptr = seconds * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tsf_uniq_to_u64(void *arg, void *ptr) {
|
||||||
|
char *s = *(char **)arg;
|
||||||
|
if (strnlen(s, 18) != 17) {
|
||||||
|
printf("JSON: wrong length for uniq, expected 'xx:xx:xx:xx:xx:xx'\n");
|
||||||
|
free(s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint64_t mac = 0;
|
||||||
|
for (int i = 0; i < 17; i++) {
|
||||||
|
char c = s[i];
|
||||||
|
uint8_t digit = 0;
|
||||||
|
|
||||||
|
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 if (c == ':')
|
||||||
|
continue;
|
||||||
|
else {
|
||||||
|
printf("JSON: unexpected character '%c' in uniq at position %i (%s)\n", c, i, s);
|
||||||
|
free(s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mac <<= 4;
|
||||||
|
mac |= digit;
|
||||||
|
}
|
||||||
|
free(s);
|
||||||
|
*(uint64_t *)ptr = mac;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tsf_hex_to_i32(void *arg, void *ptr) {
|
||||||
|
char *s = *(char **)arg;
|
||||||
|
char *f = s;
|
||||||
|
char c;
|
||||||
|
int32_t res = 0;
|
||||||
|
while ((c = *s++) != '\0') {
|
||||||
|
uint8_t digit = 0;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
printf("JSON: unexpected character '%c' in hex string\n", c);
|
||||||
|
free(f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res <<= 4;
|
||||||
|
res |= digit;
|
||||||
|
}
|
||||||
|
free(f);
|
||||||
|
*(int32_t *)ptr = res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tsf_double_to_size(void *arg, void *ptr) {
|
||||||
|
double d = *(double *)arg;
|
||||||
|
*(size_t *)ptr = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t hex_digit(char c) {
|
||||||
|
if (c >= '0' && c <= '9')
|
||||||
|
return c - '0';
|
||||||
|
else if (c >= 'a' && c <= 'f')
|
||||||
|
return c - 'a' + 10;
|
||||||
|
else if (c >= 'A' && c <= 'F')
|
||||||
|
return c - 'A' + 10;
|
||||||
|
else
|
||||||
|
return 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tsf_hex_to_color(void *arg, void *ptr) {
|
||||||
|
char *s = *(char **)arg;
|
||||||
|
int len = strnlen(s, 8);
|
||||||
|
if (len != 7 || s[0] != '#') {
|
||||||
|
printf("JSON: bad hex color format expected '#RRGGBB' or '#rrggbb', got '%s'\n", s);
|
||||||
|
free(s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t digit[6] = {0};
|
||||||
|
uint8_t *color = ptr;
|
||||||
|
|
||||||
|
for (int i = 1; i < 7; i++) {
|
||||||
|
digit[i] = hex_digit(s[i]);
|
||||||
|
if (digit[i] == 16) {
|
||||||
|
printf("JSON: illegal character in hex color: '%c'\n", s[i]);
|
||||||
|
free(s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i % 2 == 0) {
|
||||||
|
uint8_t c = digit[i];
|
||||||
|
uint8_t p = digit[i - 1];
|
||||||
|
color[i / 2] = (p << 4) | c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tsf_num_to_u8_clamp(void *arg, void *ptr) {
|
||||||
|
double n = *(double *)arg;
|
||||||
|
*(uint8_t *)ptr = n > 255.0 ? 255.0 : n < 0.0 ? 0.0 : n;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tsf_num_to_int(void *arg, void *ptr) {
|
||||||
|
double n = *(double *)arg;
|
||||||
|
*(int *)ptr = n;
|
||||||
|
}
|
||||||
|
23
util.h
23
util.h
@ -13,6 +13,29 @@ uint16_t parse_port(const char *str);
|
|||||||
static inline bool bit_set(uint8_t *bits, int i) { return bits[i / 8] & (1 << (i % 8)); }
|
static inline bool bit_set(uint8_t *bits, int i) { return bits[i / 8] & (1 << (i % 8)); }
|
||||||
// Align n to the next 8 boundary
|
// Align n to the next 8 boundary
|
||||||
static inline size_t align_8(size_t n) { return (((n - 1) >> 3) + 1) << 3; }
|
static inline size_t align_8(size_t n) { return (((n - 1) >> 3) + 1) << 3; }
|
||||||
|
// Align n to the next 4 boundary
|
||||||
|
static inline size_t align_4(size_t n) { return (((n - 1) >> 2) + 1) << 2; }
|
||||||
|
// Align n to the next 2 boundary
|
||||||
|
static inline size_t align_2(size_t n) { return (((n - 1) >> 1) + 1) << 1; }
|
||||||
uint8_t parse_hex_digit(char h);
|
uint8_t parse_hex_digit(char h);
|
||||||
|
|
||||||
|
void default_to_null(void *ptr);
|
||||||
|
void default_to_false(void *ptr);
|
||||||
|
void default_to_zero_u8(void *ptr);
|
||||||
|
void default_to_zero_u32(void *ptr);
|
||||||
|
void default_to_zero_u64(void *ptr);
|
||||||
|
void default_to_zero_size(void *ptr);
|
||||||
|
void default_to_zero_double(void *ptr);
|
||||||
|
void default_to_one_size(void *ptr);
|
||||||
|
void default_to_negative_one_i32(void *ptr);
|
||||||
|
|
||||||
|
void tsf_numsec_to_timespec(void *arg, void *ptr);
|
||||||
|
void tsf_numsec_to_intms(void *arg, void *ptr);
|
||||||
|
void tsf_uniq_to_u64(void *arg, void *ptr);
|
||||||
|
void tsf_hex_to_i32(void *arg, void *ptr);
|
||||||
|
void tsf_double_to_size(void *arg, void *ptr);
|
||||||
|
void tsf_hex_to_color(void *arg, void *ptr);
|
||||||
|
void tsf_num_to_u8_clamp(void *arg, void *ptr);
|
||||||
|
void tsf_num_to_int(void *arg, void *ptr);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
11
vec.c
11
vec.c
@ -1,12 +1,13 @@
|
|||||||
#include "vec.h"
|
#include "vec.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#define INIT_CAP 8
|
#define INIT_CAP 8
|
||||||
|
|
||||||
static void handle_alloc_error() {
|
static void handle_alloc_error(void) {
|
||||||
printf("Error when allocating memory.\n");
|
printf("Error when allocating memory.\n");
|
||||||
exit(2);
|
exit(2);
|
||||||
}
|
}
|
||||||
@ -40,7 +41,7 @@ static inline void vec_grow(Vec *v, size_t cap) {
|
|||||||
|
|
||||||
void vec_push(Vec *v, void *data) {
|
void vec_push(Vec *v, void *data) {
|
||||||
vec_grow(v, v->len + 1);
|
vec_grow(v, v->len + 1);
|
||||||
memcpy(v->data + v->stride * v->len++, data, v->stride);
|
memcpy((uint8_t *)v->data + v->stride * v->len++, data, v->stride);
|
||||||
}
|
}
|
||||||
|
|
||||||
void vec_pop(Vec *v, void *data) {
|
void vec_pop(Vec *v, void *data) {
|
||||||
@ -68,7 +69,7 @@ void vec_insert(Vec *v, void *data, size_t index) {
|
|||||||
}
|
}
|
||||||
vec_grow(v, v->len + 1);
|
vec_grow(v, v->len + 1);
|
||||||
|
|
||||||
void *slot = v->data + index * v->stride;
|
uint8_t *slot = v->data + index * v->stride;
|
||||||
if (index < v->len) {
|
if (index < v->len) {
|
||||||
memmove(slot + v->stride, slot, (v->len - index) * v->stride);
|
memmove(slot + v->stride, slot, (v->len - index) * v->stride);
|
||||||
}
|
}
|
||||||
@ -86,7 +87,7 @@ void vec_remove(Vec *v, size_t index, void *data) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *slot = v->data + index * v->stride;
|
uint8_t *slot = v->data + index * v->stride;
|
||||||
if (data != NULL) {
|
if (data != NULL) {
|
||||||
memcpy(data, slot, v->stride);
|
memcpy(data, slot, v->stride);
|
||||||
}
|
}
|
||||||
@ -103,7 +104,7 @@ void vec_extend(Vec *v, void *data, size_t len) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
vec_grow(v, v->len + len);
|
vec_grow(v, v->len + len);
|
||||||
memcpy(v->data + v->stride * v->len, data, v->stride * len);
|
memcpy((uint8_t *)v->data + v->stride * v->len, data, v->stride * len);
|
||||||
v->len += len;
|
v->len += len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
9
vec.h
9
vec.h
@ -1,15 +1,16 @@
|
|||||||
// vi:ft=c
|
// vi:ft=c
|
||||||
#ifndef VEC_H_
|
#ifndef VEC_H_
|
||||||
#define VEC_H_
|
#define VEC_H_
|
||||||
|
#include <stdint.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#define vec_of(type) vec_new(sizeof(type))
|
#define vec_of(type) vec_new(sizeof(type))
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void *data;
|
uint8_t *data;
|
||||||
size_t cap;
|
size_t cap;
|
||||||
size_t len;
|
size_t len;
|
||||||
size_t stride;
|
size_t stride;
|
||||||
} Vec;
|
} Vec;
|
||||||
|
|
||||||
// Create a new vector
|
// Create a new vector
|
||||||
|
Loading…
Reference in New Issue
Block a user