mirror of
https://github.com/dperelman/wii-u-gc-adapter
synced 2019-03-20 22:32:19 -04:00
672 lines
18 KiB
C
672 lines
18 KiB
C
// See LICENSE for license
|
|
|
|
#define _XOPEN_SOURCE 600
|
|
|
|
#include <time.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <linux/input.h>
|
|
#include <linux/uinput.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <signal.h>
|
|
#include <errno.h>
|
|
|
|
#include <libudev.h>
|
|
#include <libusb.h>
|
|
#include <pthread.h>
|
|
|
|
#if (!defined(LIBUSBX_API_VERSION) || LIBUSBX_API_VERSION < 0x01000102) && (!defined(LIBUSB_API_VERSION) || LIBUSB_API_VERSION < 0x01000102)
|
|
#error libusb(x) 1.0.16 or higher is required
|
|
#endif
|
|
|
|
#define EP_IN 0x81
|
|
#define EP_OUT 0x02
|
|
|
|
#define STATE_NORMAL 0x10
|
|
#define STATE_WAVEBIRD 0x20
|
|
|
|
#define MAX_FF_EVENTS 4
|
|
/* #undef PAD_BTN */
|
|
|
|
const int BUTTON_OFFSET_VALUES[16] = {
|
|
BTN_START,
|
|
BTN_Z,
|
|
BTN_TR,
|
|
BTN_TL,
|
|
-1,
|
|
-1,
|
|
-1,
|
|
-1,
|
|
BTN_A,
|
|
BTN_B,
|
|
BTN_X,
|
|
BTN_Y,
|
|
BTN_DPAD_LEFT,
|
|
BTN_DPAD_RIGHT,
|
|
BTN_DPAD_DOWN,
|
|
BTN_DPAD_UP,
|
|
};
|
|
|
|
const int AXIS_OFFSET_VALUES[6] = {
|
|
ABS_X,
|
|
ABS_Y,
|
|
ABS_RX,
|
|
ABS_RY,
|
|
ABS_THROTTLE,
|
|
ABS_RUDDER
|
|
};
|
|
|
|
struct ff_event
|
|
{
|
|
bool in_use;
|
|
bool forever;
|
|
int duration;
|
|
int delay;
|
|
int repetitions;
|
|
struct timespec start_time;
|
|
struct timespec end_time;
|
|
};
|
|
|
|
struct ports
|
|
{
|
|
bool connected;
|
|
bool extra_power;
|
|
int uinput;
|
|
unsigned char type;
|
|
uint16_t buttons;
|
|
uint8_t axis[6];
|
|
struct ff_event ff_events[MAX_FF_EVENTS];
|
|
};
|
|
|
|
struct adapter
|
|
{
|
|
volatile bool quitting;
|
|
struct libusb_device *device;
|
|
struct libusb_device_handle *handle;
|
|
pthread_t thread;
|
|
unsigned char rumble[5];
|
|
struct ports controllers[4];
|
|
struct adapter *next;
|
|
};
|
|
|
|
static bool raw_mode;
|
|
|
|
static volatile int quitting;
|
|
|
|
static struct adapter adapters;
|
|
|
|
static const char *uinput_path;
|
|
|
|
static unsigned char connected_type(unsigned char status)
|
|
{
|
|
unsigned char type = status & (STATE_NORMAL | STATE_WAVEBIRD);
|
|
switch (type)
|
|
{
|
|
case STATE_NORMAL:
|
|
case STATE_WAVEBIRD:
|
|
return type;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static bool uinput_create(int i, struct ports *port, unsigned char type)
|
|
{
|
|
fprintf(stderr, "connecting on port %d\n", i);
|
|
struct uinput_user_dev uinput_dev;
|
|
memset(&uinput_dev, 0, sizeof(uinput_dev));
|
|
port->uinput = open(uinput_path, O_RDWR | O_NONBLOCK);
|
|
|
|
// buttons
|
|
ioctl(port->uinput, UI_SET_EVBIT, EV_KEY);
|
|
ioctl(port->uinput, UI_SET_KEYBIT, BTN_A);
|
|
ioctl(port->uinput, UI_SET_KEYBIT, BTN_B);
|
|
ioctl(port->uinput, UI_SET_KEYBIT, BTN_X);
|
|
ioctl(port->uinput, UI_SET_KEYBIT, BTN_Y);
|
|
ioctl(port->uinput, UI_SET_KEYBIT, BTN_START);
|
|
#ifdef PAD_BTN
|
|
ioctl(port->uinput, UI_SET_KEYBIT, BTN_DPAD_UP);
|
|
ioctl(port->uinput, UI_SET_KEYBIT, BTN_DPAD_DOWN);
|
|
ioctl(port->uinput, UI_SET_KEYBIT, BTN_DPAD_LEFT);
|
|
ioctl(port->uinput, UI_SET_KEYBIT, BTN_DPAD_RIGHT);
|
|
#endif
|
|
ioctl(port->uinput, UI_SET_KEYBIT, BTN_TL);
|
|
ioctl(port->uinput, UI_SET_KEYBIT, BTN_TR);
|
|
ioctl(port->uinput, UI_SET_KEYBIT, BTN_Z);
|
|
|
|
// axis
|
|
ioctl(port->uinput, UI_SET_EVBIT, EV_ABS);
|
|
ioctl(port->uinput, UI_SET_ABSBIT, ABS_X);
|
|
ioctl(port->uinput, UI_SET_ABSBIT, ABS_Y);
|
|
ioctl(port->uinput, UI_SET_ABSBIT, ABS_RX);
|
|
ioctl(port->uinput, UI_SET_ABSBIT, ABS_RY);
|
|
ioctl(port->uinput, UI_SET_ABSBIT, ABS_THROTTLE);
|
|
ioctl(port->uinput, UI_SET_ABSBIT, ABS_RUDDER);
|
|
#ifndef PAD_BTN
|
|
ioctl(port->uinput, UI_SET_ABSBIT, ABS_HAT0X);
|
|
ioctl(port->uinput, UI_SET_ABSBIT, ABS_HAT0Y);
|
|
#endif
|
|
|
|
if (raw_mode)
|
|
{
|
|
uinput_dev.absmin[ABS_X] = 0; uinput_dev.absmax[ABS_X] = 255;
|
|
uinput_dev.absmin[ABS_Y] = 0; uinput_dev.absmax[ABS_Y] = 255;
|
|
uinput_dev.absmin[ABS_RX] = 0; uinput_dev.absmax[ABS_RX] = 255;
|
|
uinput_dev.absmin[ABS_RY] = 0; uinput_dev.absmax[ABS_RY] = 255;
|
|
uinput_dev.absmin[ABS_THROTTLE] = -255; uinput_dev.absmax[ABS_THROTTLE] = 255;
|
|
uinput_dev.absmin[ABS_RUDDER] = -255; uinput_dev.absmax[ABS_RUDDER] = 255;
|
|
}
|
|
else
|
|
{
|
|
uinput_dev.absmin[ABS_X] = 20; uinput_dev.absmax[ABS_X] = 235;
|
|
uinput_dev.absmin[ABS_Y] = 20; uinput_dev.absmax[ABS_Y] = 235;
|
|
uinput_dev.absmin[ABS_RX] = 30; uinput_dev.absmax[ABS_RX] = 225;
|
|
uinput_dev.absmin[ABS_RY] = 30; uinput_dev.absmax[ABS_RY] = 225;
|
|
uinput_dev.absmin[ABS_THROTTLE] = -225; uinput_dev.absmax[ABS_THROTTLE] = 225; uinput_dev.absflat[ABS_THROTTLE] = 30;
|
|
uinput_dev.absmin[ABS_RUDDER] = -225; uinput_dev.absmax[ABS_RUDDER] = 225; uinput_dev.absflat[ABS_RUDDER] = 30;
|
|
}
|
|
#ifndef PAD_BTN
|
|
uinput_dev.absmin[ABS_HAT0X] = -1; uinput_dev.absmax[ABS_HAT0X] = 1;
|
|
uinput_dev.absmin[ABS_HAT0Y] = -1; uinput_dev.absmax[ABS_HAT0Y] = 1;
|
|
#endif
|
|
|
|
// rumble
|
|
ioctl(port->uinput, UI_SET_EVBIT, EV_FF);
|
|
ioctl(port->uinput, UI_SET_FFBIT, FF_CONSTANT);
|
|
ioctl(port->uinput, UI_SET_FFBIT, FF_RUMBLE);
|
|
uinput_dev.ff_effects_max = MAX_FF_EVENTS;
|
|
|
|
#ifdef PAD_BTN
|
|
snprintf(uinput_dev.name, sizeof(uinput_dev.name), "Wii U GameCube AbsAdpt Port %d", i+1);
|
|
#else
|
|
snprintf(uinput_dev.name, sizeof(uinput_dev.name), "Wii U GameCube HatAdpt Port %d", i+1);
|
|
#endif
|
|
uinput_dev.name[sizeof(uinput_dev.name)-1] = 0;
|
|
uinput_dev.id.bustype = BUS_USB;
|
|
if (write(port->uinput, &uinput_dev, sizeof(uinput_dev)) != sizeof(uinput_dev))
|
|
{
|
|
fprintf(stderr, "error writing uinput device settings");
|
|
close(port->uinput);
|
|
return false;
|
|
}
|
|
|
|
if (ioctl(port->uinput, UI_DEV_CREATE) != 0)
|
|
{
|
|
fprintf(stderr, "error creating uinput device");
|
|
close(port->uinput);
|
|
return false;
|
|
}
|
|
port->type = type;
|
|
port->connected = true;
|
|
return true;
|
|
}
|
|
|
|
static void uinput_destroy(int i, struct ports *port)
|
|
{
|
|
fprintf(stderr, "disconnecting on port %d\n", i);
|
|
ioctl(port->uinput, UI_DEV_DESTROY);
|
|
close(port->uinput);
|
|
port->connected = false;
|
|
}
|
|
|
|
static struct timespec ts_add(struct timespec *start, int milliseconds)
|
|
{
|
|
struct timespec ret = *start;
|
|
int s = milliseconds / 1000;
|
|
int ns = (milliseconds % 1000) * 1000000;
|
|
ret.tv_sec += s ;
|
|
ret.tv_nsec += ns ;
|
|
if (ret.tv_nsec >= 1000000000L)
|
|
{
|
|
ret.tv_sec++;
|
|
ret.tv_nsec -= 1000000000L;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static bool ts_greaterthan(struct timespec *first, struct timespec *second)
|
|
{
|
|
return (first->tv_sec >= second->tv_sec || (first->tv_sec == second->tv_sec && first->tv_nsec >= second->tv_nsec));
|
|
}
|
|
|
|
static bool ts_lessthan(struct timespec *first, struct timespec *second)
|
|
{
|
|
return (first->tv_sec <= second->tv_sec || (first->tv_sec == second->tv_sec && first->tv_nsec <= second->tv_nsec));
|
|
}
|
|
|
|
static void update_ff_start_stop(struct ff_event *e, struct timespec *current_time)
|
|
{
|
|
e->repetitions--;
|
|
|
|
if (e->repetitions < 0)
|
|
{
|
|
e->repetitions = 0;
|
|
e->start_time.tv_sec = 0;
|
|
e->start_time.tv_nsec = 0;
|
|
e->end_time.tv_sec = 0;
|
|
e->end_time.tv_nsec = 0;
|
|
}
|
|
else
|
|
{
|
|
e->start_time = ts_add(current_time, e->delay);
|
|
if (e->duration == 0)
|
|
{
|
|
e->end_time.tv_sec = INT_MAX;
|
|
e->end_time.tv_nsec = 999999999L;
|
|
}
|
|
else
|
|
{
|
|
e->end_time = ts_add(&e->start_time, e->duration);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int create_ff_event(struct ports *port, struct uinput_ff_upload *upload)
|
|
{
|
|
if (upload->old.type != 0)
|
|
{
|
|
port->ff_events[upload->old.id].forever = (upload->effect.replay.length == 0);
|
|
port->ff_events[upload->old.id].duration = upload->effect.replay.length;
|
|
port->ff_events[upload->old.id].delay = upload->effect.replay.delay;
|
|
port->ff_events[upload->old.id].repetitions = 0;
|
|
return upload->old.id;
|
|
}
|
|
for (int i = 0; i < MAX_FF_EVENTS; i++)
|
|
{
|
|
if (!port->ff_events[i].in_use)
|
|
{
|
|
port->ff_events[i].in_use = true;
|
|
port->ff_events[i].forever = (upload->effect.replay.length == 0);
|
|
port->ff_events[i].duration = upload->effect.replay.length;
|
|
port->ff_events[i].delay = upload->effect.replay.delay;
|
|
port->ff_events[i].repetitions = 0;
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static void handle_payload(int i, struct ports *port, unsigned char *payload, struct timespec *current_time)
|
|
{
|
|
unsigned char status = payload[0];
|
|
unsigned char type = connected_type(status);
|
|
|
|
if (type != 0 && !port->connected)
|
|
{
|
|
uinput_create(i, port, type);
|
|
}
|
|
else if (type == 0 && port->connected)
|
|
{
|
|
uinput_destroy(i, port);
|
|
}
|
|
|
|
if (!port->connected)
|
|
return;
|
|
|
|
port->extra_power = ((status & 0x04) != 0);
|
|
|
|
if (type != port->type)
|
|
{
|
|
fprintf(stderr, "controller on port %d changed controller type???", i+1);
|
|
port->type = type;
|
|
}
|
|
|
|
struct input_event events[12+6+1]; // buttons + axis + syn event
|
|
memset(&events, 0, sizeof(events));
|
|
int e_count = 0;
|
|
|
|
uint16_t btns = (uint16_t) payload[1] << 8 | (uint16_t) payload[2];
|
|
|
|
for (int j = 0; j < 16; j++)
|
|
{
|
|
if (BUTTON_OFFSET_VALUES[j] == -1)
|
|
continue;
|
|
|
|
uint16_t mask = (1 << j);
|
|
uint16_t pressed = btns & mask;
|
|
|
|
if ((port->buttons & mask) != pressed)
|
|
{
|
|
#ifndef PAD_BTN
|
|
switch (BUTTON_OFFSET_VALUES[j]) {
|
|
case BTN_DPAD_RIGHT:
|
|
case BTN_DPAD_LEFT:
|
|
case BTN_DPAD_DOWN:
|
|
case BTN_DPAD_UP:
|
|
events[e_count].type = EV_ABS;
|
|
events[e_count].code = (BUTTON_OFFSET_VALUES[j] == BTN_DPAD_UP || BUTTON_OFFSET_VALUES[j] == BTN_DPAD_DOWN)
|
|
? ABS_HAT0Y : ABS_HAT0X;
|
|
events[e_count].value = (BUTTON_OFFSET_VALUES[j] == BTN_DPAD_UP || BUTTON_OFFSET_VALUES[j] == BTN_DPAD_LEFT)
|
|
? (pressed == 0) ? 0 : -1 : (pressed == 0) ? 0 : 1;
|
|
break;
|
|
default:
|
|
#endif
|
|
|
|
events[e_count].type = EV_KEY;
|
|
events[e_count].code = BUTTON_OFFSET_VALUES[j];
|
|
events[e_count].value = (pressed == 0) ? 0 : 1;
|
|
|
|
#ifndef PAD_BTN
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
e_count++;
|
|
port->buttons &= ~mask;
|
|
port->buttons |= pressed;
|
|
}
|
|
}
|
|
|
|
for (int j = 0; j < 6; j++)
|
|
{
|
|
unsigned char value = payload[j+3];
|
|
|
|
if (AXIS_OFFSET_VALUES[j] == ABS_Y || AXIS_OFFSET_VALUES[j] == ABS_RY)
|
|
value ^= 0xFF; // flip from 0 - 255 to 255 - 0
|
|
|
|
if (port->axis[j] != value)
|
|
{
|
|
events[e_count].type = EV_ABS;
|
|
events[e_count].code = AXIS_OFFSET_VALUES[j];
|
|
events[e_count].value = value;
|
|
e_count++;
|
|
port->axis[j] = value;
|
|
}
|
|
}
|
|
|
|
if (e_count > 0)
|
|
{
|
|
events[e_count].type = EV_SYN;
|
|
events[e_count].code = SYN_REPORT;
|
|
e_count++;
|
|
size_t to_write = sizeof(events[0]) * e_count;
|
|
size_t written = 0;
|
|
while (written < to_write)
|
|
{
|
|
ssize_t write_ret = write(port->uinput, (const char*)events + written, to_write - written);
|
|
if (write_ret < 0)
|
|
{
|
|
char msg[128];
|
|
strerror_r(errno, msg, sizeof(msg));
|
|
fprintf(stderr, "Warning: writing input events failed: %s\n", msg);
|
|
break;
|
|
}
|
|
written += write_ret;
|
|
}
|
|
}
|
|
|
|
// check for rumble events
|
|
struct input_event e;
|
|
ssize_t ret = read(port->uinput, &e, sizeof(e));
|
|
if (ret == sizeof(e))
|
|
{
|
|
if (e.type == EV_UINPUT)
|
|
{
|
|
switch (e.code)
|
|
{
|
|
case UI_FF_UPLOAD:
|
|
{
|
|
struct uinput_ff_upload upload = { 0 };
|
|
upload.request_id = e.value;
|
|
ioctl(port->uinput, UI_BEGIN_FF_UPLOAD, &upload);
|
|
int id = create_ff_event(port, &upload);
|
|
if (id < 0)
|
|
{
|
|
// TODO: what's the proper error code for this?
|
|
upload.retval = -1;
|
|
}
|
|
else
|
|
{
|
|
upload.retval = 0;
|
|
upload.effect.id = id;
|
|
}
|
|
ioctl(port->uinput, UI_END_FF_UPLOAD, &upload);
|
|
break;
|
|
}
|
|
case UI_FF_ERASE:
|
|
{
|
|
struct uinput_ff_erase erase = { 0 };
|
|
erase.request_id = e.value;
|
|
ioctl(port->uinput, UI_BEGIN_FF_ERASE, &erase);
|
|
if (erase.effect_id < MAX_FF_EVENTS)
|
|
port->ff_events[erase.effect_id].in_use = false;
|
|
ioctl(port->uinput, UI_END_FF_ERASE, &erase);
|
|
}
|
|
}
|
|
}
|
|
else if (e.type == EV_FF)
|
|
{
|
|
if (e.code < MAX_FF_EVENTS && port->ff_events[e.code].in_use)
|
|
{
|
|
port->ff_events[e.code].repetitions = e.value;
|
|
update_ff_start_stop(&port->ff_events[e.code], current_time);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void *adapter_thread(void *data)
|
|
{
|
|
struct adapter *a = (struct adapter *)data;
|
|
|
|
while (!a->quitting)
|
|
{
|
|
unsigned char payload[37];
|
|
int size = 0;
|
|
libusb_interrupt_transfer(a->handle, EP_IN, payload, sizeof(payload), &size, 100);
|
|
if (size != 37 || payload[0] != 0x21)
|
|
continue;
|
|
|
|
unsigned char *controller = &payload[1];
|
|
|
|
unsigned char rumble[5] = { 0x11, 0, 0, 0, 0 };
|
|
struct timespec current_time = { 0 };
|
|
clock_gettime(CLOCK_REALTIME, ¤t_time);
|
|
for (int i = 0; i < 4; i++, controller += 9)
|
|
{
|
|
handle_payload(i, &a->controllers[i], controller, ¤t_time);
|
|
rumble[i+1] = 0;
|
|
if (a->controllers[i].extra_power && a->controllers[i].type == STATE_NORMAL)
|
|
{
|
|
for (int j = 0; j < MAX_FF_EVENTS; j++)
|
|
{
|
|
struct ff_event *e = &a->controllers[i].ff_events[j];
|
|
if (e->in_use)
|
|
{
|
|
if (ts_lessthan(&e->start_time, ¤t_time) && ts_greaterthan(&e->end_time, ¤t_time))
|
|
rumble[i+1] = 1;
|
|
else
|
|
update_ff_start_stop(e, ¤t_time);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (memcmp(rumble, a->rumble, sizeof(rumble)) != 0)
|
|
{
|
|
memcpy(a->rumble, rumble, sizeof(rumble));
|
|
libusb_interrupt_transfer(a->handle, EP_OUT, a->rumble, sizeof(a->rumble), &size, 100);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
if (a->controllers[i].connected)
|
|
uinput_destroy(i, &a->controllers[i]);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void add_adapter(struct libusb_device *dev)
|
|
{
|
|
struct adapter *a = (struct adapter *)calloc(1, sizeof(struct adapter));
|
|
if (a == NULL)
|
|
{
|
|
fprintf(stderr, "FATAL: calloc() failed");
|
|
exit(-1);
|
|
}
|
|
a->device = dev;
|
|
|
|
if (libusb_open(a->device, &a->handle) != 0)
|
|
{
|
|
fprintf(stderr, "Error opening device %p\n", a->device);
|
|
return;
|
|
}
|
|
|
|
if (libusb_kernel_driver_active(a->handle, 0) == 1 && libusb_detach_kernel_driver(a->handle, 0))
|
|
{
|
|
fprintf(stderr, "Error detaching handle %p from kernel\n", a->handle);
|
|
return;
|
|
}
|
|
|
|
if (libusb_claim_interface(a->handle, 0) != 0)
|
|
{
|
|
fprintf(stderr, "Error claiming interface 0 on adapter 0x%p from kernel\n", a->handle);
|
|
return;
|
|
}
|
|
|
|
int tmp;
|
|
unsigned char payload[1] = { 0x13 };
|
|
libusb_interrupt_transfer(a->handle, EP_OUT, payload, sizeof(payload), &tmp, 100);
|
|
|
|
struct adapter *old_head = adapters.next;
|
|
adapters.next = a;
|
|
a->next = old_head;
|
|
|
|
pthread_create(&a->thread, NULL, adapter_thread, a);
|
|
|
|
fprintf(stderr, "adapter %p connected\n", a->device);
|
|
}
|
|
|
|
static void remove_adapter(struct libusb_device *dev)
|
|
{
|
|
struct adapter *a = &adapters;
|
|
while (a->next != NULL)
|
|
{
|
|
if (a->next->device == dev)
|
|
{
|
|
a->next->quitting = true;
|
|
libusb_release_interface(a->next->handle, 0);
|
|
pthread_join(a->next->thread, NULL);
|
|
fprintf(stderr, "adapter %p disconnected\n", a->next->device);
|
|
libusb_close(a->next->handle);
|
|
struct adapter *new_next = a->next->next;
|
|
free(a->next);
|
|
a->next = new_next;
|
|
return;
|
|
}
|
|
|
|
a = a->next;
|
|
}
|
|
}
|
|
|
|
static int hotplug_callback(struct libusb_context *ctx, struct libusb_device *dev, libusb_hotplug_event event, void *user_data)
|
|
{
|
|
(void)ctx;
|
|
(void)user_data;
|
|
if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED)
|
|
{
|
|
add_adapter(dev);
|
|
}
|
|
else if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT)
|
|
{
|
|
remove_adapter(dev);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void quitting_signal(int sig)
|
|
{
|
|
(void)sig;
|
|
quitting = 1;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
struct udev *udev;
|
|
struct udev_device *uinput;
|
|
struct sigaction sa;
|
|
|
|
memset(&sa, 0, sizeof(sa));
|
|
|
|
if (argc > 1 && (strcmp(argv[1], "-r") == 0 || strcmp(argv[1], "--raw") == 0))
|
|
{
|
|
fprintf(stderr, "raw mode enabled\n");
|
|
raw_mode = true;
|
|
}
|
|
|
|
sa.sa_handler = quitting_signal;
|
|
sa.sa_flags = SA_RESTART | SA_RESETHAND;
|
|
sigemptyset(&sa.sa_mask);
|
|
|
|
sigaction(SIGINT, &sa, NULL);
|
|
sigaction(SIGTERM, &sa, NULL);
|
|
|
|
udev = udev_new();
|
|
if (udev == NULL) {
|
|
fprintf(stderr, "udev init errors\n");
|
|
return -1;
|
|
}
|
|
|
|
uinput = udev_device_new_from_subsystem_sysname(udev, "misc", "uinput");
|
|
if (uinput == NULL)
|
|
{
|
|
fprintf(stderr, "uinput creation failed\n");
|
|
return -1;
|
|
}
|
|
|
|
uinput_path = udev_device_get_devnode(uinput);
|
|
if (uinput_path == NULL)
|
|
{
|
|
fprintf(stderr, "cannot find path to uinput\n");
|
|
return -1;
|
|
}
|
|
|
|
libusb_init(NULL);
|
|
|
|
struct libusb_device **devices;
|
|
|
|
int count = libusb_get_device_list(NULL, &devices);
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
struct libusb_device_descriptor desc;
|
|
libusb_get_device_descriptor(devices[i], &desc);
|
|
if (desc.idVendor == 0x057e && desc.idProduct == 0x0337)
|
|
add_adapter(devices[i]);
|
|
}
|
|
|
|
if (count > 0)
|
|
libusb_free_device_list(devices, 1);
|
|
|
|
libusb_hotplug_callback_handle callback;
|
|
int hotplug_ret = libusb_hotplug_register_callback(NULL, LIBUSB_HOTPLUG_MATCH_ANY, 0, 0x057e, 0x0337, LIBUSB_HOTPLUG_MATCH_ANY, hotplug_callback, NULL, &callback);
|
|
|
|
if (hotplug_ret != 0)
|
|
fprintf(stderr, "cannot register hotplug callback, hotplugging not enabled\n");
|
|
|
|
// pump events until shutdown & all helper threads finish cleaning up
|
|
while (!quitting)
|
|
libusb_handle_events_completed(NULL, (int *)&quitting);
|
|
|
|
while (adapters.next)
|
|
remove_adapter(adapters.next->device);
|
|
|
|
if (hotplug_ret == 0)
|
|
libusb_hotplug_deregister_callback(NULL, callback);
|
|
|
|
libusb_exit(NULL);
|
|
udev_device_unref(uinput);
|
|
udev_unref(udev);
|
|
return 0;
|
|
}
|