#include "client.h" #include "json.h" #include "net.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef struct { int fd; MessageDeviceInfo info; } VirtualDevice; #ifdef JSFW_DEV // Path for dev environment (no root) const char *FIFO_PATH = "/tmp/jsfw_fifo"; #else const char *FIFO_PATH = "/run/jsfw_fifo"; #endif const int CONN_RETRY_DELAY = 5; // Constant for the virtual device const uint16_t VIRT_VENDOR = 0x6969; const uint16_t VIRT_PRODUCT = 0x0420; const uint16_t VIRT_VERSION = 1; const char *VIRT_NAME = "JSFW Virtual Device"; static int fifo_attempt = 0; static struct sockaddr_in server_addr = {}; static char server_addrp[64] = {}; static uint16_t server_port = -1; static struct pollfd poll_fds[2]; static struct pollfd *fifo_poll = &poll_fds[0]; static struct pollfd *socket_poll = &poll_fds[1]; static int fifo = -1; static int sock = -1; // static to avoid having this on the stack because a message is about 2kb in memory static Message message; static VirtualDevice device = {}; static inline bool device_exists() { return device.fd > 0; } typedef struct { char *led_color; double rumble_small; double rumble_big; double flash_on; double flash_off; } JControllerState; static const JSONAdapter JControllerStateAdapter[] = { {".led_color", String, offsetof(JControllerState, led_color)}, {".rumble.0", Number, offsetof(JControllerState, rumble_small)}, {".rumble.1", Number, offsetof(JControllerState, rumble_big)}, {".flash.0", Number, offsetof(JControllerState, flash_on)}, {".flash.1", Number, offsetof(JControllerState, flash_off)}, }; void device_destroy() { if (!device_exists()) { return; } ioctl(device.fd, UI_DEV_DESTROY); close(device.fd); device.fd = -1; printf("CLIENT: Destroyed device\n"); } void device_init(MessageDeviceInfo *dev) { device_destroy(); int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); if (fd < 0) { perror("CLIENT: Error while opening /dev/uinput, "); exit(1); } // Abs if (dev->abs_count > 0) { ioctl(fd, UI_SET_EVBIT, EV_ABS); for (int i = 0; i < dev->abs_count; i++) { struct uinput_abs_setup setup = {}; setup.code = dev->abs_id[i]; setup.absinfo.minimum = dev->abs_min[i]; setup.absinfo.maximum = dev->abs_max[i]; setup.absinfo.fuzz = dev->abs_fuzz[i]; setup.absinfo.flat = dev->abs_flat[i]; setup.absinfo.resolution = dev->abs_res[i]; setup.absinfo.value = 0; ioctl(fd, UI_ABS_SETUP, &setup); } } // Rel if (dev->rel_count > 0) { ioctl(fd, UI_SET_EVBIT, EV_REL); for (int i = 0; i < dev->rel_count; i++) { ioctl(fd, UI_SET_RELBIT, dev->rel_id[i]); } } // Key if (dev->key_count > 0) { ioctl(fd, UI_SET_EVBIT, EV_KEY); for (int i = 0; i < dev->key_count; i++) { ioctl(fd, UI_SET_KEYBIT, dev->key_id[i]); } } struct uinput_setup setup = {}; setup.id.bustype = BUS_VIRTUAL; setup.id.vendor = VIRT_VENDOR; setup.id.product = VIRT_PRODUCT; setup.id.version = VIRT_VERSION; strncpy(setup.name, VIRT_NAME, UINPUT_MAX_NAME_SIZE); ioctl(fd, UI_DEV_SETUP, &setup); ioctl(fd, UI_DEV_CREATE); device.fd = fd; memcpy(&device.info, dev, sizeof(MessageDeviceInfo)); printf("CLIENT: Created device (abs: %d, rel: %d, key: %d)\n", dev->abs_count, dev->rel_count, dev->key_count); } int device_emit(uint16_t type, uint16_t id, uint32_t value) { struct input_event event = {}; event.type = type; event.code = id; event.value = value; return write(device.fd, &event, sizeof(event)) != sizeof(event); } void device_handle_report(MessageDeviceReport *report) { if (!device_exists()) { printf("CLIENT: Got report but device info\n"); return; } if (report->abs_count != device.info.abs_count || report->rel_count != device.info.rel_count || report->key_count != device.info.key_count) { printf("CLIENT: Report doesn't match with device info\n"); return; } for (int i = 0; i < report->abs_count; i++) { if (device_emit(EV_ABS, device.info.abs_id[i], report->abs[i]) != 0) printf("CLIENT: Error writing abs event to uinput\n"); } for (int i = 0; i < report->rel_count; i++) { if (device_emit(EV_REL, device.info.rel_id[i], report->rel[i]) != 0) printf("CLIENT: Error writing rel event to uinput\n"); } 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) printf("CLIENT: Error writing key event to uinput\n"); } device_emit(EV_SYN, 0, 0); } void setup_fifo(); void open_fifo() { close(fifo); fifo = open(FIFO_PATH, O_RDONLY | O_NONBLOCK); if (fifo < 0 && fifo_attempt == 0) { fifo_attempt++; unlink(FIFO_PATH); setup_fifo(); } else if (fifo < 0) { panicf("CLIENT: Couldn't open fifo, aborting\n"); } } void setup_fifo() { mode_t prev = umask(0); mkfifo(FIFO_PATH, 0666); umask(prev); open_fifo(); fifo_poll->fd = fifo; fifo_poll->events = POLLIN; } void connect_server() { while (1) { if (sock > 0) { device_destroy(); shutdown(sock, SHUT_RDWR); close(sock); } sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) panicf("Couldn't create socket\n"); 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, CONN_RETRY_DELAY); struct timespec ts = {}; ts.tv_sec = CONN_RETRY_DELAY; nanosleep(&ts, NULL); continue; } // Set non blocking fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK); socket_poll->fd = sock; printf("CLIENT: Connected !\n"); return; } } void setup_server(char *address, uint16_t port) { // setup address server_addr.sin_family = AF_INET; if (inet_pton(AF_INET, address, &server_addr.sin_addr) == 0) printf("CLIENT: failed to parse address '%s', defaulting to 0.0.0.0 (localhost)\n", address); inet_ntop(AF_INET, &server_addr.sin_addr, server_addrp, 64); server_port = port; server_addr.sin_port = htons(port); socket_poll->events = POLLIN; connect_server(); } void early_checks() { int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); if (fd < 0) { perror("CLIENT: Can't open /dev/uinput, aborting now: "); exit(1); } close(fd); } static uint8_t parse_hex_digit(char h) { if (h >= '0' && h <= '9') return h - '0'; else if (h >= 'a' && h <= 'f') return h - 'a' + 10; else if (h >= 'A' && h <= 'F') return h - 'A' + 10; else return 0; } void client_run(char *address, uint16_t port) { // Device doesn't exist yet device.fd = -1; early_checks(); setup_fifo(); setup_server(address, port); uint8_t buf[2048] __attribute__((aligned(4))); uint8_t json_buf[2048] __attribute__((aligned(8))); while (1) { int rc = poll(poll_fds, 2, -1); if (rc < 0) { perror("CLIENT: Error on poll, "); exit(1); } if (fifo_poll->revents & POLLIN || fifo_poll->revents & POLLHUP || fifo_poll->revents & POLLERR) { int len = read(fifo, buf, 2048); if (len <= 0) { open_fifo(); } else { // We've got data from the fifo int rc = json_parse((char *)buf, len, json_buf, 2048); if (rc < 0) { printf("CLIENT: Error when parsing fifo message as json (%s at index %lu)\n", json_strerr(), json_err_loc()); } 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; msg.code = ControllerState; msg.small_rumble = (uint8_t)(fmax(fmin(1.0, state.rumble_small), 0.0) * 255.0); 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); if (len > 0) { if (send(sock, buf, len, 0) > 0) { printf("CLIENT: Sent controller state: #%02x%02x%02x flash: (%d, %d) rumble: " "(%d, %d)\n", msg.led[0], msg.led[1], msg.led[2], msg.flash_on, msg.flash_off, msg.small_rumble, msg.big_rumble); }; }; } } } // A broken or closed socket produces a POLLIN event, so we check for error on the recv if (socket_poll->revents & POLLIN) { int len = recv(sock, buf, 2048, 0); if (len <= 0) { printf("CLIENT: Lost connection to server, reconnecting\n"); shutdown(sock, SHUT_RDWR); connect_server(); // we can continue here because there's nothing after, unlike above for fifo (this reduces // indentation) continue; } // We've got data from the server if (msg_deserialize(buf, len, &message) != 0) { printf("CLIENT: Couldn't parse message (code: %d, len: %d)\n", buf[0], len); int l = len > 100 ? 100 : len; for (int i = 0; i < l; i++) { printf("%02x", buf[i]); } if (len > 100) { printf(" ... %d more bytes", len - 100); } printf("\n"); continue; } if (message.code == DeviceInfo) { if (device_exists()) printf("CLIENT: Got more than one device info\n"); device_init((MessageDeviceInfo *)&message); } else if (message.code == DeviceReport) { device_handle_report((MessageDeviceReport *)&message); } else { printf("CLIENT: Illegal message\n"); } } } }