From 402bf1269c59db4b3a78077ab095a84181181808 Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Thu, 28 Jul 2022 20:31:28 +0200 Subject: Remove dependency on libevdev (mostly complete) + minor fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- Makefile | 3 + common.h | 21 +++- config.mk | 4 +- libgamepad.h | 180 +++++++++++++++++++++++++++------ libgamepad_close_device.c | 16 ++- libgamepad_create_attachment_monitor.c | 2 +- libgamepad_drain__.c | 55 ++++++++++ libgamepad_drain_events.c | 18 ++++ libgamepad_generate_sync_events.c | 94 +++++++++++++++++ libgamepad_get_absolute_axis_name.c | 2 +- libgamepad_get_button_name.c | 2 +- libgamepad_get_relative_axis_name.c | 2 +- libgamepad_next_event.c | 145 +++++++++++++++++++------- libgamepad_open_device.c | 53 ++++------ test-input.c | 28 ++--- test-visual.c | 6 +- 16 files changed, 502 insertions(+), 129 deletions(-) create mode 100644 libgamepad_drain__.c create mode 100644 libgamepad_drain_events.c create mode 100644 libgamepad_generate_sync_events.c diff --git a/Makefile b/Makefile index 0ef9633..24dc048 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,10 @@ OBJ =\ libgamepad_create_attachment_monitor.o\ libgamepad_destroy_attachment_monitor.o\ libgamepad_disable_force_feedback_autocenter.o\ + libgamepad_drain__.o\ + libgamepad_drain_events.o\ libgamepad_find_sound_devices.o\ + libgamepad_generate_sync_events.o\ libgamepad_get_absolute_axis_by_name.o\ libgamepad_get_absolute_axis_info.o\ libgamepad_get_absolute_axis_name.o\ diff --git a/common.h b/common.h index 9889858..0458063 100644 --- a/common.h +++ b/common.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -17,7 +18,6 @@ #include #include -#include #include #include @@ -34,8 +34,19 @@ #define ELEMSOF(A) (sizeof(A) / sizeof(*(A))) #define BITSOF(T) (8 * sizeof(T)) +#define XORBIT(ARR, I) (READ_(ARR, I) ^= 1 << SHIFT_(ARR, I)) #define SETBIT(ARR, I) (READ_(ARR, I) |= 1 << SHIFT_(ARR, I)) #define GETBIT(ARR, I) ((READ_(ARR, I) >> SHIFT_(ARR, I)) & 1) +#define ELEMS_REQUIRED(BITS, TYPE) ((BITS + BITSOF(TYPE) - 1) / BITSOF(TYPE)) + +#define DEFER_EINTR_(DEVICE, ERROR_CONDITION, GOTO_ON_FAILURE)\ + do {\ + while (ERROR_CONDITION) {\ + if (errno != EINTR)\ + goto GOTO_ON_FAILURE;\ + (DEVICE)->internals->deferred_error = EINTR;\ + }\ + } while (0) struct libgamepad_attachment_monitor { @@ -45,11 +56,14 @@ struct libgamepad_attachment_monitor { struct libgamepad_device_internals { + int deferred_error; int close_fd; int require_sync; - struct libevdev *dev; struct input_absinfo *absinfo; uint8_t *buttons; + struct input_event *evbuf; + size_t evbuf_size; + size_t ev_queued; }; @@ -64,3 +78,6 @@ extern const char *libgamepad_absolute_axis_names__[ extern const char *libgamepad_relative_axis_names__[ #include "relative-axis.count" ]; + + +int libgamepad_drain__(struct libgamepad_device *device); diff --git a/config.mk b/config.mk index 7e9fb7c..8ace118 100644 --- a/config.mk +++ b/config.mk @@ -8,7 +8,7 @@ CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE CFLAGS = -Wall -g LDFLAGS = -LIBS_CFLAGS = $$(pkg-config --cflags libevdev) -LIBS_LDFLAGS = $$(pkg-config --libs libevdev) -ludev -lm -lsha2 +LIBS_CFLAGS = +LIBS_LDFLAGS = -ludev -lm -lsha2 INPUT_EVENT_CODES_H = /usr/include/linux/input-event-codes.h diff --git a/libgamepad.h b/libgamepad.h index 5cee653..7a046a3 100644 --- a/libgamepad.h +++ b/libgamepad.h @@ -12,8 +12,6 @@ #include #include -#include - #if defined(__GNUC__) # define LIBGAMEPAD_PURE__ __attribute__((__pure__)) @@ -144,7 +142,7 @@ enum libgamepad_game_controller { */ LIBGAMEPAD_CONTROLLER_OTHER, - /* TODO force feedback capabilities */ + /* TODO Document force feedback capabilities */ /** * Amazon Luna Controller @@ -1327,18 +1325,57 @@ enum libgamepad_type { * Gamepad input type */ enum libgamepad_input_type { + /** + * Signals thatevents where dropped because + * the application did not read fast enough + * + * When received, the next call to `libgamepad_next_event` + * will drain the event queue and generate synchronisation + * events (which have the timestamp set to zero) to bring + * the applications state of the buttons/keys and absolute + * axes up to date + */ + LIBGAMEPAD_EVENT_DROP, + + /** + * Signals that all previous events, from the + * last `LIBGAMEPAD_EVENT_END` or `LIBGAMEPAD_EVENT_DROP` + * where part of the same report from the input device + * + * This event will always be sent, however it may have + * been dropped when a `LIBGAMEPAD_EVENT_DROP` is sent + */ + LIBGAMEPAD_EVENT_END, + /** * Button/key + * + * Expectation when `LIBGAMEPAD_EVENT_DROP` has been + * received: a full press and release, or release and + * press, may be missing the if read interval is longer + * than such an event would span */ LIBGAMEPAD_BUTTON, /** * Absolute axis + * + * Expectation when `LIBGAMEPAD_EVENT_DROP` has been + * received: less smooth transition between two states, + * and quick movements in one direction and back may + * be missing, but only such movements that are sorter + * than the read duration */ LIBGAMEPAD_ABSOLUTE_AXIS, /** * Relative axis + * + * Expectation when `LIBGAMEPAD_EVENT_DROP` has been + * received: movement details are lost and calculate + * position is off (it is possible that the driver + * sends synchronisations event to adjust for the + * position) */ LIBGAMEPAD_RELATIVE_AXIS }; @@ -1349,29 +1386,30 @@ enum libgamepad_input_type { */ struct libgamepad_input_event { /** - * The affect input type + * The affected input type */ enum libgamepad_input_type type; /** - * Whether the event was polled in a - * synchronisation read - */ - short int sync_event; - - /** - * The button/key or axis affect by the event + * The button/key or axis affected by the event + * + * Not set if `.type` is `LIBGAMEPAD_EVENT_DROP` */ uint16_t code; /** * The new value on the button/key or axis, * or the delta if on a relative axis + * + * Not set if `.type` is `LIBGAMEPAD_EVENT_DROP` */ int32_t value; /** * Event timestamp + * + * Set to zero if not supported by the driver + * or if the event generated by the library */ struct timeval time; }; @@ -1453,6 +1491,13 @@ struct libgamepad_device { */ int fd; + /** + * Specifies whether the library shall automatically call + * `libgamepad_generate_sync_events` if events are dropped + * or the event queue is manually drained + */ + int auto_sync; + /** * Bus type the device is connect via, see BUS_-prefixed * constants in @@ -1663,6 +1708,11 @@ int libgamepad_get_attachment_event(LIBGAMEPAD_ATTACHMENT_MONITOR *, char **, si /** * Open a subdevice * + * This function may temporarily modify the file status + * flags (which affects all duplicate file descriptors + * share the same open file descriptor) of `dirfd` if + * `path` is `NULL` or empty + * * @param devicep Output parameter for the device information and handles; * deallocated with `libgamepad_close_device` * @param dirfd File descriptor to path that `path` is relative to @@ -1678,40 +1728,83 @@ int libgamepad_get_attachment_event(LIBGAMEPAD_ATTACHMENT_MONITOR *, char **, si * @return 0 on success, -1 on failure * * This function may fail for any reason specified for - * malloc(3), openat(3) or libevdev_new_from_fd(3) + * realloc(3), openat(3), ioctl(3), or read(3) */ int libgamepad_open_device(struct libgamepad_device *, int, const char *, int); /** * Close a subdevice * - * @param device Device information and handles, may be `NULL` + * @param device Device information and handles, may be `NULL` + * @return Normally 0, -1 if there was a deferred error */ -void libgamepad_close_device(struct libgamepad_device *); +int libgamepad_close_device(struct libgamepad_device *); /** * Get the next event on a subdevice * - * NB! events may be queued internally (by libevdev), therefore - * an application must either call this function in a loop in - * its own thread, or have the device opened in non-blocking - * mode and even the poll the device's file descriptor for - * read-readyness and when it is ready clal this function in - * a loop until it sets `errno` to `EAGAIN`. + * This function may temporarily modify the file status + * flags of the open file decriptor it uses to access + * the device * * @param device Device to read an event from - * @param eventp Output parameter for the event - * @return 1 if an event was returned, - * 0 if no event was returned, - * -1 on failure + * @param events Output buffer for the events (will be in order) + * @param max The number of elements `events` fit + * (may be 0 for error condition checking) + * @return The number of returned events (may be more or + * less than actually read), -1 on failure * * This function may fail for any reason specified for - * libevdev_next_event(3), including: + * realloc(3) and read(3), including: * - EAGAIN no more data currently available (non-blocking mode) * - EINTR system call was interrupted, try again + * (may be deferred to `libgamepad_close_device`) * - ENODEV device has been unplugged or access has been revoked */ -int libgamepad_next_event(struct libgamepad_device *, struct libgamepad_input_event *); +ssize_t libgamepad_next_event(struct libgamepad_device *, struct libgamepad_input_event *, size_t); + +/** + * Remove all events on a subdevice queued by kernel + * and queue synchronisation events so that the application + * will have (after reading them) up to date button/key and + * absolute axis states + * + * This function is normally used internally, but can be + * useful after calling `libgamepad_set_clock` + * + * This function may temporarily modify the file status + * flags of the open file decriptor it uses to access + * the device + * + * @param device Device to drain queue for + * @return 0 on success, -1 on failure + * + * This function may fail for any reason specified for + * realloc(3) and read(3), including: + * - ENODEV device has been unplugged or access has been revoked + * except for EINTR which is deferred to the next call to + * `libgamepad_next_event` or `libgamepad_close_device` + * (whichever comes first) for the same device, and EAGAIN + */ +int libgamepad_drain_events(struct libgamepad_device *); + +/** + * Generates any events needed to keep the applications + * button/key and absolute axes states up to date + * + * This function is normally used internally, but can be + * useful after calling `libgamepad_drain_events` if + * automatic generation of synchronisation events have + * been disabled by modifying `device->auto_sync` + * + * This function may temporarily modify the file status + * flags of the open file decriptor it uses to access + * the device + * + * @param device Device to synchronise + * @return 0 on success, -1 on failure + */ +int libgamepad_generate_sync_events(struct libgamepad_device *); /** @@ -1733,7 +1826,7 @@ int libgamepad_next_event(struct libgamepad_device *, struct libgamepad_input_ev * @param code The button/key * @return The button/key's name, `NULL` if not found */ -const char *libgamepad_get_button_name(const struct libgamepad_device *, uint16_t); +LIBGAMEPAD_PURE__ const char *libgamepad_get_button_name(const struct libgamepad_device *, uint16_t); /** * Get the name of an absolute axis @@ -1754,7 +1847,7 @@ const char *libgamepad_get_button_name(const struct libgamepad_device *, uint16_ * @param code The axis * @return The axis' name, `NULL` if not found */ -const char *libgamepad_get_absolute_axis_name(const struct libgamepad_device *, uint16_t); +LIBGAMEPAD_PURE__ const char *libgamepad_get_absolute_axis_name(const struct libgamepad_device *, uint16_t); /** * Get the name of a relative axis @@ -1775,7 +1868,7 @@ const char *libgamepad_get_absolute_axis_name(const struct libgamepad_device *, * @param code The axis * @return The axis' name, `NULL` if not found */ -const char *libgamepad_get_relative_axis_name(const struct libgamepad_device *, uint16_t); +LIBGAMEPAD_PURE__ const char *libgamepad_get_relative_axis_name(const struct libgamepad_device *, uint16_t); /** * Get a button/key code from it's name @@ -1875,6 +1968,10 @@ libgamepad_get_device_has_ff_effect(const struct libgamepad_device *device, uint * libgamepad reads the current state when a device * is opened. * + * This information is not necessarily up to date + * before the first `LIBGAMEPAD_EVENT_END` has been + * reported + * * @param device The device to retrieve the information for * @param code The button/key * @return 1 if the button/key is pressed down, @@ -1894,12 +1991,21 @@ int libgamepad_get_button_is_pressed(struct libgamepad_device *, uint16_t); * libgamepad reads the current state when a device * is opened. * + * The value of `.value` in the returned pointer is + * not necessarily up to date before the first + * `LIBGAMEPAD_EVENT_END` has been reported + * * @param device The device to retrieve the information for * @param code The axis - * @return Information about the axis + * @return Information about the axis; this is a pointer + * stored inside `device`, some function calls + * may alter the contents of invalidate the pointer, + * `libgamepad_close_device(device)` will always + * invalidate the pointer and it is expected that + * `libgamepad_next_event` often alters `.value` */ -LIBGAMEPAD_PURE__ const struct input_absinfo *libgamepad_get_absolute_axis_info(struct libgamepad_device *, uint16_t); -/* `struct input_absinfo` is defined in */ +LIBGAMEPAD_PURE__ /* `struct input_absinfo` is defined in */ +const struct input_absinfo *libgamepad_get_absolute_axis_info(struct libgamepad_device *, uint16_t); /** @@ -1940,6 +2046,16 @@ libgamepad_ungrab(struct libgamepad_device *device) /** * Set the clock that event timestamps shall be reported in * + * If your application depends on new events using the + * selected clock (if any at all), call `libgamepad_drain_events` + * after this function + * + * Support clocks are (in Linux 5.18): + * - CLOCK_REALTIME + * - CLOCK_MONOTONIC + * - CLOCK_BOOTTIME + * As usual, CLOCK_MONOTONIC_RAW is not supported + * * @param device The device to configure * @param clockid The clock to use on event timestamps * @return 0 on success, -1 on failure diff --git a/libgamepad_close_device.c b/libgamepad_close_device.c index 273c7f8..896d22e 100644 --- a/libgamepad_close_device.c +++ b/libgamepad_close_device.c @@ -2,17 +2,20 @@ #include "common.h" -void +int libgamepad_close_device(struct libgamepad_device *device) { + int deferred_error = 0; if (device) { if (device->internals) { + deferred_error = device->internals->deferred_error; if (device->internals->close_fd) - close(device->fd); - if (device->internals->dev) - libevdev_free(device->internals->dev); + if (close(device->fd)) + if (!deferred_error) + deferred_error = errno; free(device->internals->absinfo); free(device->internals->buttons); + free(device->internals->evbuf); free(device->internals); device->internals = NULL; } @@ -28,5 +31,10 @@ libgamepad_close_device(struct libgamepad_device *device) device->name = NULL; device->unique_id = NULL; device->physical_location = NULL; + if (deferred_error) { + errno = deferred_error; + return -1; + } } + return 0; } diff --git a/libgamepad_create_attachment_monitor.c b/libgamepad_create_attachment_monitor.c index 20423b0..2776476 100644 --- a/libgamepad_create_attachment_monitor.c +++ b/libgamepad_create_attachment_monitor.c @@ -5,7 +5,7 @@ int libgamepad_create_attachment_monitor(LIBGAMEPAD_ATTACHMENT_MONITOR **monitorp) { - *monitorp = calloc(1, sizeof(*monitorp)); + *monitorp = calloc(1, sizeof(**monitorp)); if (!*monitorp) return -1; diff --git a/libgamepad_drain__.c b/libgamepad_drain__.c new file mode 100644 index 0000000..38e898e --- /dev/null +++ b/libgamepad_drain__.c @@ -0,0 +1,55 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + +#define DEFER_EINTR(ERROR_CONDITION)\ + DEFER_EINTR_(device, ERROR_CONDITION, fail) + + +int +libgamepad_drain__(struct libgamepad_device *device) +{ + struct input_event buf[4 * 8]; + int flags, saved_errno = errno; + ssize_t r; + + if (device->internals->deferred_error) { + errno = device->internals->deferred_error; + device->internals->deferred_error = 0; + return -1; + } + + flags = fcntl(device->fd, F_GETFL); + if (flags < 0) + return -1; + + if (!(flags & O_NONBLOCK)) + if (fcntl(device->fd, F_GETFL, flags | O_NONBLOCK) < 0) + goto fail; + + for (;;) { + r = read(device->fd, buf, sizeof(buf)); + if (r < (ssize_t)sizeof(buf)) { + if (r > 0) + break; + else if (!r) + goto nodev; + else if (errno == EAGAIN) + break; + else if (errno == EINTR) + device->internals->deferred_error = EINTR; + else + goto fail; + } + } + + if (!(flags & O_NONBLOCK)) + DEFER_EINTR(fcntl(device->fd, F_SETFL, flags) < 0); + + errno = saved_errno; + return 0; + +nodev: + errno = ENODEV; +fail: + return -1; +} diff --git a/libgamepad_drain_events.c b/libgamepad_drain_events.c new file mode 100644 index 0000000..b68ecad --- /dev/null +++ b/libgamepad_drain_events.c @@ -0,0 +1,18 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +int +libgamepad_drain_events(struct libgamepad_device *device) +{ + if (libgamepad_drain__(device)) + return -1; + device->internals->ev_queued = 0; + + if (device->auto_sync) { + return libgamepad_generate_sync_events(device); + } else { + device->internals->require_sync = 0; + return 0; + } +} diff --git a/libgamepad_generate_sync_events.c b/libgamepad_generate_sync_events.c new file mode 100644 index 0000000..97a2beb --- /dev/null +++ b/libgamepad_generate_sync_events.c @@ -0,0 +1,94 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + +#define DEFER_EINTR(ERROR_CONDITION, GOTO_ON_FAILURE)\ + DEFER_EINTR_(device, ERROR_CONDITION, GOTO_ON_FAILURE) + + +static int +require_evbuf_size(struct libgamepad_device *device, size_t n) +{ + void *new; + if (n < device->internals->evbuf_size) + return 0; + if (n > SIZE_MAX / sizeof(*device->internals->evbuf)) { + errno = ENOMEM; + return -1; + } + new = realloc(device->internals->evbuf, n * sizeof(*device->internals->evbuf)); + if (!new) + return -1; + device->internals->evbuf = new; + device->internals->evbuf_size = n; + return 0; +} + + +int +libgamepad_generate_sync_events(struct libgamepad_device *device) +{ + unsigned long int bits[ELEMS_REQUIRED(ELEMSOF(device->button_map), long int)]; + struct input_absinfo abs; + size_t queued = device->internals->ev_queued; + int16_t j; + unsigned int i, max; + int r, saved_errno = errno; + + if (device->internals->deferred_error) { + errno = device->internals->deferred_error; + device->internals->deferred_error = 0; + return -1; + } + + if (device->nbuttons) { + DEFER_EINTR((r = ioctl(device->fd, EVIOCGKEY(sizeof(bits)), bits)) < 0, fail); + max = (unsigned int)r * 8; + for (i = 0; i < max && i < ELEMSOF(device->button_map); i++) { + j = device->button_map[i]; + if (j >= 0 && GETBIT(bits, i) != GETBIT(device->internals->buttons, (uint16_t)j)) { + if (require_evbuf_size(device, queued + 1)) + goto fail; + device->internals->evbuf[queued].input_event_sec = 0; + device->internals->evbuf[queued].input_event_usec = 0; + device->internals->evbuf[queued].type = EV_KEY; + device->internals->evbuf[queued].code = (uint16_t)i; + device->internals->evbuf[queued].value = (int32_t)GETBIT(bits, i); + queued += 1; + } + } + } + + for (i = 0; i < device->nabsolute_axes; i++) { + DEFER_EINTR(ioctl(device->fd, EVIOCGABS((unsigned int)device->absolute_axes[i]), &abs) < 0, fail); + if (device->internals->absinfo[i].value != abs.value) { + if (require_evbuf_size(device, queued + 1)) + goto fail; + device->internals->evbuf[queued].input_event_sec = 0; + device->internals->evbuf[queued].input_event_usec = 0; + device->internals->evbuf[queued].type = EV_ABS; + device->internals->evbuf[queued].code = device->absolute_axes[i]; + device->internals->evbuf[queued].value = abs.value; + queued += 1; + } + } + /* TODO synchronise multitouch state */ + + if (queued > device->internals->ev_queued) { + if (require_evbuf_size(device, queued + 1)) + goto fail; + device->internals->evbuf[queued].input_event_sec = 0; + device->internals->evbuf[queued].input_event_usec = 0; + device->internals->evbuf[queued].type = EV_SYN; + device->internals->evbuf[queued].code = SYN_REPORT; + device->internals->evbuf[queued].value = 0; + queued += 1; + } + + device->internals->require_sync = 0; + device->internals->ev_queued = queued; + errno = saved_errno; + return 0; + +fail: + return -1; +} diff --git a/libgamepad_get_absolute_axis_name.c b/libgamepad_get_absolute_axis_name.c index 79f216c..eddb4a2 100644 --- a/libgamepad_get_absolute_axis_name.c +++ b/libgamepad_get_absolute_axis_name.c @@ -3,7 +3,7 @@ const char * -libgamepad_get_absolute_axis_name(const struct libgamepad_device *device, uint16_t code) /* will not be pure */ +libgamepad_get_absolute_axis_name(const struct libgamepad_device *device, uint16_t code) { if (device) return NULL; diff --git a/libgamepad_get_button_name.c b/libgamepad_get_button_name.c index 031e528..37ad9ac 100644 --- a/libgamepad_get_button_name.c +++ b/libgamepad_get_button_name.c @@ -3,7 +3,7 @@ const char * -libgamepad_get_button_name(const struct libgamepad_device *device, uint16_t code) /* will not be pure */ +libgamepad_get_button_name(const struct libgamepad_device *device, uint16_t code) { if (device) return NULL; diff --git a/libgamepad_get_relative_axis_name.c b/libgamepad_get_relative_axis_name.c index 5a16945..3e67bbd 100644 --- a/libgamepad_get_relative_axis_name.c +++ b/libgamepad_get_relative_axis_name.c @@ -3,7 +3,7 @@ const char * -libgamepad_get_relative_axis_name(const struct libgamepad_device *device, uint16_t code) /* will not be pure */ +libgamepad_get_relative_axis_name(const struct libgamepad_device *device, uint16_t code) { if (device) return NULL; diff --git a/libgamepad_next_event.c b/libgamepad_next_event.c index c74d641..b9d8986 100644 --- a/libgamepad_next_event.c +++ b/libgamepad_next_event.c @@ -2,56 +2,125 @@ #include "common.h" -int -libgamepad_next_event(struct libgamepad_device *device, struct libgamepad_input_event *eventp) +static int +update_button(struct libgamepad_device *device, uint16_t code, int32_t value) { - int r; - struct input_event ev; + int16_t index; + if (code > ELEMSOF(device->button_map)) + return 0; + index = device->button_map[code]; + if (index < 0 || GETBIT(device->internals->buttons, (size_t)index) == value) + return 1; + XORBIT(device->internals->buttons, (size_t)index); + return 1; +} + + +static int +update_absolute_axis(struct libgamepad_device *device, uint16_t code, int32_t value) +{ + int16_t index; + if (code > ELEMSOF(device->absolute_axis_map)) + return 0; + index = device->absolute_axis_map[code]; + if (index < 0 || device->internals->absinfo[index].value == value) + return 0; + device->internals->absinfo[index].value = value; + return 1; +} + + +ssize_t +libgamepad_next_event(struct libgamepad_device *device, struct libgamepad_input_event *events, size_t max) +{ + size_t i, dequeued = 0; + void *new; + ssize_t r; + struct input_event *ev; + + if (device->internals->deferred_error) { + errno = device->internals->deferred_error; + device->internals->deferred_error = 0; + return -1; + } + + if (device->internals->require_sync) + if (libgamepad_drain_events(device)) + return -1; - if (!device->internals->require_sync) { - r = libevdev_next_event(device->internals->dev, LIBEVDEV_READ_FLAG_NORMAL, &ev); - if (r < 0) { - errno = -r; + if (max > device->internals->evbuf_size) { + if (max > SIZE_MAX / sizeof(*device->internals->evbuf)) { + errno = ENOMEM; return -1; } - } else { - r = libevdev_next_event(device->internals->dev, LIBEVDEV_READ_FLAG_SYNC, &ev); - if (r == -EAGAIN) { - device->internals->require_sync = 0; /* yes, this is how it is document */ - return 0; - } else if (r < 0) { - errno = -r; + new = realloc(device->internals->evbuf, max * sizeof(*device->internals->evbuf)); + if (!new) + return -1; + device->internals->evbuf = new; + device->internals->evbuf_size = max; + } + + dequeued = device->internals->ev_queued < max ? device->internals->ev_queued : max; + if (!dequeued || dequeued < max) { + r = read(device->fd, &device->internals->evbuf[dequeued], (max - dequeued) * sizeof(*device->internals->evbuf)); + if (r <= 0) { + if (!r && (max - dequeued)) + errno = ENODEV; + return -1; + } else if ((size_t)r % sizeof(*device->internals->evbuf)) { + errno = EBADF; return -1; } - if (r != LIBEVDEV_READ_STATUS_SYNC) /* just to be safe */ - device->internals->require_sync = 0; + } else { + r = 0; } + r += (ssize_t)(dequeued * sizeof(*device->internals->evbuf)); - eventp->sync_event = (r == LIBEVDEV_READ_STATUS_SYNC); - eventp->code = ev.code; - eventp->value = ev.value; - eventp->time.tv_sec = ev.input_event_sec; - eventp->time.tv_usec = ev.input_event_usec; + i = 0; + for (ev = device->internals->evbuf; r; r -= (ssize_t)sizeof(*device->internals->evbuf), ev++) { + events[i].code = ev->code; + events[i].value = ev->value; + events[i].time.tv_sec = ev->input_event_sec; + events[i].time.tv_usec = ev->input_event_usec; - switch (ev.type) { - case EV_KEY: - eventp->type = LIBGAMEPAD_BUTTON; - return 1; + switch (ev->type) { + case EV_SYN: + events[i].code = 0; + events[i].value = 0; + if (ev->code == SYN_REPORT) { + events[i++].type = LIBGAMEPAD_EVENT_END; + } else if (ev->code == SYN_DROPPED) { + device->internals->require_sync = device->auto_sync; + events[i++].type = LIBGAMEPAD_EVENT_DROP; + goto out; + } + break; - case EV_ABS: - eventp->type = LIBGAMEPAD_ABSOLUTE_AXIS; - return 1; + case EV_KEY: + ev->value = !!ev->value; + if (update_button(device, ev->code, ev->value)) + events[i++].type = LIBGAMEPAD_BUTTON; + break; - case EV_REL: - eventp->type = LIBGAMEPAD_RELATIVE_AXIS; - return 1; + case EV_REL: + events[i++].type = LIBGAMEPAD_RELATIVE_AXIS; + break; - case EV_SYN: - if (ev.code == SYN_DROPPED) - device->internals->require_sync = 1; - return 0; + case EV_ABS: + if (update_absolute_axis(device, ev->code, ev->value)) + events[i++].type = LIBGAMEPAD_ABSOLUTE_AXIS; + break; - default: - return 0; + default: + break; + } + } + +out: + if (dequeued) { + device->internals->ev_queued -= dequeued; + memmove(&device->internals->evbuf[0], &device->internals->evbuf[dequeued], + device->internals->ev_queued * sizeof(*device->internals->evbuf)); } + return (ssize_t)i; } diff --git a/libgamepad_open_device.c b/libgamepad_open_device.c index 22163a4..27c5600 100644 --- a/libgamepad_open_device.c +++ b/libgamepad_open_device.c @@ -1,10 +1,12 @@ /* See LICENSE file for copyright and license details. */ #include "common.h" -#define ELEMS_REQUIRED(BITS, TYPE) ((BITS + BITSOF(TYPE) - 1) / BITSOF(TYPE)) #define MAX2(A, B) ((A) > (B) ? (A) : (B)) #define MAX4(A, B, C, D) (MAX2(MAX2(A, B), MAX2(C, D))) +#define DEFER_EINTR(ERROR_CONDITION, GOTO_ON_FAILURE)\ + DEFER_EINTR_(devicep, ERROR_CONDITION, GOTO_ON_FAILURE) + static void store_uint(uint64_t value, char *buf) @@ -128,7 +130,7 @@ libgamepad_open_device(struct libgamepad_device *devicep, int dirfd, const char ELEMSOF(devicep->absolute_axis_map), ELEMSOF(devicep->relative_axis_map), ELEMSOF(devicep->force_feedback_support)), long int)]; - int err, r; + int r, saved_errno = errno; int16_t j; uint16_t n; unsigned int i, max; @@ -139,6 +141,7 @@ libgamepad_open_device(struct libgamepad_device *devicep, int dirfd, const char memset(devicep, 0, sizeof(*devicep)); devicep->fd = dirfd; + devicep->auto_sync = 1; devicep->internals = calloc(1, sizeof(*devicep->internals)); if (!devicep->internals) @@ -153,28 +156,17 @@ libgamepad_open_device(struct libgamepad_device *devicep, int dirfd, const char } - if (ioctl(devicep->fd, EVIOCGID, &id) < 0) - goto fail; + DEFER_EINTR(ioctl(devicep->fd, EVIOCGID, &id) < 0, fail); devicep->bus_type = (unsigned int)id.bustype; devicep->vendor = (unsigned int)id.vendor; devicep->product = (unsigned int)id.product; devicep->version = (unsigned int)id.version; - err = libevdev_new_from_fd(devicep->fd, &devicep->internals->dev); - if (err < 0) { - libgamepad_close_device(devicep); - errno = -err; - return -1; - } - - #define GET_STRING(EVIOCMACRO, OUTPUT)\ do {\ for (;;) {\ - r = ioctl(devicep->fd, EVIOCMACRO(bufsize), buf);\ - if (r < 0)\ - goto fail_free_buf;\ + DEFER_EINTR((r = ioctl(devicep->fd, EVIOCMACRO(bufsize), buf)) < 0, fail_free_buf);\ if ((size_t)r < bufsize)\ break;\ new = realloc(buf, bufsize += 32);\ @@ -196,9 +188,7 @@ libgamepad_open_device(struct libgamepad_device *devicep, int dirfd, const char #define CREATE_MAPS(EVENTS, SINGULAR, PLURAL)\ do {\ /* Code-to-index map */\ - r = ioctl(devicep->fd, EVIOCGBIT(EVENTS, sizeof(bits)), bits);\ - if (r < 0)\ - goto fail;\ + DEFER_EINTR((r = ioctl(devicep->fd, EVIOCGBIT(EVENTS, sizeof(bits)), bits)) < 0, fail);\ max = (unsigned int)r * 8;\ devicep->n##PLURAL = 0;\ for (i = 0; i < ELEMSOF(devicep->SINGULAR##_map); i++) {\ @@ -217,7 +207,6 @@ libgamepad_open_device(struct libgamepad_device *devicep, int dirfd, const char devicep->PLURAL[n++] = (uint16_t)i;\ }\ } while (0) - CREATE_MAPS(EV_KEY, button, buttons); CREATE_MAPS(EV_ABS, absolute_axis, absolute_axes); CREATE_MAPS(EV_REL, relative_axis, relative_axes); @@ -228,18 +217,23 @@ libgamepad_open_device(struct libgamepad_device *devicep, int dirfd, const char if (!devicep->internals->absinfo) goto fail; } - for (i = 0; i < devicep->nabsolute_axes; i++) - if (ioctl(devicep->fd, EVIOCGABS((unsigned int)devicep->absolute_axes[i]), &devicep->internals->absinfo[i]) < 0) - goto fail; + for (i = 0; i < devicep->nabsolute_axes; i++) { + DEFER_EINTR(ioctl(devicep->fd, EVIOCGABS((unsigned int)devicep->absolute_axes[i]), + &devicep->internals->absinfo[i]) < 0, fail); + if (devicep->internals->absinfo[i].minimum == devicep->internals->absinfo[i].maximum) { + if (devicep->absolute_axes[i] == ABS_MT_TRACKING_ID) { + devicep->internals->absinfo[i].minimum = -1; + devicep->internals->absinfo[i].maximum = 0xFFFF; + } + } + } if (devicep->nbuttons) { devicep->internals->buttons = calloc((devicep->nbuttons + 7) / 8, sizeof(*devicep->internals->buttons)); if (!devicep->internals->buttons) goto fail; - r = ioctl(devicep->fd, EVIOCGKEY(sizeof(bits)), bits); - if (r < 0) - goto fail; + DEFER_EINTR((r = ioctl(devicep->fd, EVIOCGKEY(sizeof(bits)), bits)) < 0, fail); max = (unsigned int)r * 8; for (i = 0; i < max && i < ELEMSOF(devicep->button_map); i++) { j = devicep->button_map[i]; @@ -249,9 +243,7 @@ libgamepad_open_device(struct libgamepad_device *devicep, int dirfd, const char } - r = ioctl(devicep->fd, EVIOCGBIT(EV_FF, sizeof(bits)), bits); - if (r < 0) - goto fail; + DEFER_EINTR((r = ioctl(devicep->fd, EVIOCGBIT(EV_FF, sizeof(bits)), bits)) < 0, fail); max = (unsigned int)r * 8; for (i = 0; i < BITSOF(devicep->force_feedback_support); i++) if (i < max && ((bits[i / BITSOF(*bits)] >> (i % BITSOF(*bits))) & 1)) @@ -261,13 +253,12 @@ libgamepad_open_device(struct libgamepad_device *devicep, int dirfd, const char if (get_fingerprint(devicep, devicep->fingerprint, devicep->fingerprint_unique)) goto fail; - /* TODO get actual KEY/ABS state */ - + errno = saved_errno; return 0; fail_free_buf: free(buf); fail: - libgamepad_close_device(devicep); + libgamepad_close_device(devicep); /* sets `errno` to `EINTR` if it was deferred */ return -1; } diff --git a/test-input.c b/test-input.c index d74e684..9e2dc37 100644 --- a/test-input.c +++ b/test-input.c @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -31,7 +32,7 @@ int main(int argc, char *argv[]) { struct libgamepad_input_event event; - int r; + ssize_t r; if (argc != 2) { fprintf(stderr, "Please provide the path to the subdevice as the only command line argument\n"); @@ -54,7 +55,7 @@ main(int argc, char *argv[]) } for (;;) { - r = libgamepad_next_event(&gamepad, &event); + r = libgamepad_next_event(&gamepad, &event, 1); if (r <= 0) { if (!r || errno == EINTR) continue; @@ -62,16 +63,17 @@ main(int argc, char *argv[]) return 1; } printf("[%lli.%06li] ", (long long int)event.time.tv_sec, event.time.tv_usec); - if (event.sync_event) - printf("[sync] "); - if (event.type == LIBGAMEPAD_BUTTON) { - printf("%s ", libgamepad_get_button_name(NULL, event.code)); - } else if (event.type == LIBGAMEPAD_ABSOLUTE_AXIS) { - printf("%s ", libgamepad_get_absolute_axis_name(NULL, event.code)); - } else { - assert(event.type == LIBGAMEPAD_RELATIVE_AXIS); - printf("%s ", libgamepad_get_relative_axis_name(NULL, event.code)); - } - printf("%lli\n", (long long int)event.value); + if (event.type == LIBGAMEPAD_BUTTON) + printf("%s %ji\n", libgamepad_get_button_name(NULL, event.code), (intmax_t)event.value); + else if (event.type == LIBGAMEPAD_ABSOLUTE_AXIS) + printf("%s %ji\n", libgamepad_get_absolute_axis_name(NULL, event.code), (intmax_t)event.value); + else if (event.type == LIBGAMEPAD_RELATIVE_AXIS) + printf("%s %ji\n", libgamepad_get_relative_axis_name(NULL, event.code), (intmax_t)event.value); + else if (event.type == LIBGAMEPAD_EVENT_DROP) + printf("event drop\n"); + else if (event.type == LIBGAMEPAD_EVENT_END) + printf("event conclusion\n"); + else + printf("unknown event\n"); } } diff --git a/test-visual.c b/test-visual.c index 9743006..bd3a610 100644 --- a/test-visual.c +++ b/test-visual.c @@ -39,7 +39,7 @@ draw_axis(char buffer[], const struct abs_axis *axis, size_t len) x = (size_t)(axis->value - axis->min) * len; x += (size_t)(axis->max - axis->min) / 2; x /= (size_t)(axis->max - axis->min); - memmove(&buffer[x + sizeof("\033[m") - 1], &buffer[x], len - x); + memmove(&buffer[x + sizeof("\033[m") - 1], &buffer[x], len + 1 - x); memcpy(&buffer[x], "\033[m", sizeof("\033[m") - 1); return buffer; } @@ -63,7 +63,7 @@ main(int argc, char *argv[]) ssize_t saxis_len; size_t axis_len, req_axis_len; size_t max_max_len = 0, max_min_len = 0; - int r; + ssize_t r; if (argc != 2) { fprintf(stderr, "Please provide the path to the subdevice as the only command line argument\n"); @@ -152,7 +152,7 @@ main(int argc, char *argv[]) for (;;) { /* TODO use nonblocking; listen for window resize */ - r = libgamepad_next_event(&gamepad, &event); + r = libgamepad_next_event(&gamepad, &event, 1); if (r <= 0) { if (!r || errno == EINTR) continue; -- cgit v1.2.3-70-g09d2