From d49393b339c47733c2eda207b8ba03d61db0a5a3 Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Sat, 23 Jul 2022 14:39:44 +0200 Subject: First commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- .gitignore | 16 + LICENSE | 15 + Makefile | 98 ++++++ TODO | 7 + common.h | 24 ++ config.mk | 11 + libgamepad.h | 566 ++++++++++++++++++++++++++++++++ libgamepad_close_device.c | 22 ++ libgamepad_close_superdevice.c | 20 ++ libgamepad_create_attachment_monitor.c | 33 ++ libgamepad_destroy_attachment_monitor.c | 15 + libgamepad_get_absolute_axis_info.c | 5 + libgamepad_get_absolute_axis_name.c | 5 + libgamepad_get_attachment_event.c | 60 ++++ libgamepad_get_button_is_pressed.c | 5 + libgamepad_get_button_name.c | 5 + libgamepad_get_relative_axis_name.c | 5 + libgamepad_grab.c | 5 + libgamepad_list_superdevices.c | 77 +++++ libgamepad_next_event.c | 57 ++++ libgamepad_open_device.c | 98 ++++++ libgamepad_open_superdevice.c | 149 +++++++++ libgamepad_set_clock.c | 5 + libgamepad_ungrab.c | 5 + mk/linux.mk | 6 + mk/macos.mk | 6 + mk/windows.mk | 6 + test-attachments.c | 50 +++ test-details.c | 101 ++++++ test-input.c | 77 +++++ test-list.c | 42 +++ 31 files changed, 1596 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 TODO create mode 100644 common.h create mode 100644 config.mk create mode 100644 libgamepad.h create mode 100644 libgamepad_close_device.c create mode 100644 libgamepad_close_superdevice.c create mode 100644 libgamepad_create_attachment_monitor.c create mode 100644 libgamepad_destroy_attachment_monitor.c create mode 100644 libgamepad_get_absolute_axis_info.c create mode 100644 libgamepad_get_absolute_axis_name.c create mode 100644 libgamepad_get_attachment_event.c create mode 100644 libgamepad_get_button_is_pressed.c create mode 100644 libgamepad_get_button_name.c create mode 100644 libgamepad_get_relative_axis_name.c create mode 100644 libgamepad_grab.c create mode 100644 libgamepad_list_superdevices.c create mode 100644 libgamepad_next_event.c create mode 100644 libgamepad_open_device.c create mode 100644 libgamepad_open_superdevice.c create mode 100644 libgamepad_set_clock.c create mode 100644 libgamepad_ungrab.c create mode 100644 mk/linux.mk create mode 100644 mk/macos.mk create mode 100644 mk/windows.mk create mode 100644 test-attachments.c create mode 100644 test-details.c create mode 100644 test-input.c create mode 100644 test-list.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..228cddc --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +*\#* +*~ +*.o +*.a +*.lo +*.su +*.so +*.so.* +*.dll +*.dylib +*.gch +*.gcov +*.gcno +*.gcda +/test-* +!/test-*.c diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d0aa103 --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ +ISC License + +© 2022 Mattias Andrée + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0ccc316 --- /dev/null +++ b/Makefile @@ -0,0 +1,98 @@ +.POSIX: + +CONFIGFILE = config.mk +include $(CONFIGFILE) + +OS = linux +# Linux: linux +# Mac OS: macos +# Windows: windows +include mk/$(OS).mk + + +LIB_MAJOR = 1 +LIB_MINOR = 0 +LIB_VERSION = $(LIB_MAJOR).$(LIB_MINOR) +LIB_NAME = gamepad + + +OBJ =\ + libgamepad_close_device.o\ + libgamepad_close_superdevice.o\ + libgamepad_create_attachment_monitor.o\ + libgamepad_destroy_attachment_monitor.o\ + libgamepad_get_absolute_axis_info.o\ + libgamepad_get_absolute_axis_name.o\ + libgamepad_get_attachment_event.o\ + libgamepad_get_button_is_pressed.o\ + libgamepad_get_button_name.o\ + libgamepad_get_relative_axis_name.o\ + libgamepad_grab.o\ + libgamepad_list_superdevices.o\ + libgamepad_next_event.o\ + libgamepad_open_device.o\ + libgamepad_open_superdevice.o\ + libgamepad_set_clock.o\ + libgamepad_ungrab.o + +HDR =\ + common.h\ + libgamepad.h + +TESTS =\ + test-attachments\ + test-details\ + test-input\ + test-list + +LOBJ = $(OBJ:.o=.lo) + + +all: libgamepad.a libgamepad.$(LIBEXT) $(TESTS) +$(OBJ): $(HDR) +$(LOBJ): $(HDR) +$(TESTS:=.o): $(HDR) +$(TESTS): libgamepad.a + +.c.o: + $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) $(LIBS_CFLAGS) + +.c.lo: + $(CC) -fPIC -c -o $@ $< $(CFLAGS) $(CPPFLAGS) $(LIBS_CFLAGS) + +.o: + $(CC) -o $@ $< libgamepad.a $(LDFLAGS) $(LIBS_LDFLAGS) + +libgamepad.a: $(OBJ) + @rm -f -- $@ + $(AR) rc $@ $(OBJ) + $(AR) ts $@ > /dev/null + +libgamepad.$(LIBEXT): $(LOBJ) + $(CC) $(LIBFLAGS) -o $@ $(LOBJ) $(LDFLAGS) $(LIBS_LDFLAGS) + +install: libgamepad.a libgamepad.$(LIBEXT) + mkdir -p -- "$(DESTDIR)$(PREFIX)/lib" + mkdir -p -- "$(DESTDIR)$(PREFIX)/include" + cp -- libgamepad.a "$(DESTDIR)$(PREFIX)/lib/" + cp -- libgamepad.$(LIBEXT) "$(DESTDIR)$(PREFIX)/lib/libgamepad.$(LIBMINOREXT)" + $(FIX_INSTALL_NAME) "$(DESTDIR)$(PREFIX)/lib/libgamepad.$(LIBMINOREXT)" + ln -sf -- libgamepad.$(LIBMINOREXT) "$(DESTDIR)$(PREFIX)/lib/libgamepad.$(LIBMAJOREXT)" + ln -sf -- libgamepad.$(LIBMAJOREXT) "$(DESTDIR)$(PREFIX)/lib/libgamepad.$(LIBEXT)" + cp -- libgamepad.h "$(DESTDIR)$(PREFIX)/include/" + +uninstall: + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libgamepad.a" + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libgamepad.$(LIBMAJOREXT)" + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libgamepad.$(LIBMINOREXT)" + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libgamepad.$(LIBEXT)" + -rm -f -- "$(DESTDIR)$(PREFIX)/include/libgamepad.h" + +clean: + -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.dll *.dylib + -rm -f -- *.gch *.gcov *.gcno *.gcda *.$(LIBEXT) $(TESTS) + +.SUFFIXES: +.SUFFIXES: .lo .o .c + +.PHONY: all install uninstall clean diff --git a/TODO b/TODO new file mode 100644 index 0000000..dc7dd9d --- /dev/null +++ b/TODO @@ -0,0 +1,7 @@ +Add support for quirks +Add support for battery +Add support for leds +Add support for force feedback +Add support for audio +Add man pages +Add readme file diff --git a/common.h b/common.h new file mode 100644 index 0000000..09f6193 --- /dev/null +++ b/common.h @@ -0,0 +1,24 @@ +/* See LICENSE file for copyright and license details. */ +#include "libgamepad.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +struct libgamepad_attachment_monitor { + struct udev *udev; + struct udev_monitor *monitor; +}; diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..77acc18 --- /dev/null +++ b/config.mk @@ -0,0 +1,11 @@ +PREFIX = /usr +MANPREFIX = $(PREFIX)/share/man + +CC = c99 + +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 diff --git a/libgamepad.h b/libgamepad.h new file mode 100644 index 0000000..627589a --- /dev/null +++ b/libgamepad.h @@ -0,0 +1,566 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef LIBGAMEPAD_H +#define LIBGAMEPAD_H + +#include +#include +#include +#include +#include + +#include + + +/** + * Opaque structure for monitoring device attachment changes + */ +typedef struct libgamepad_attachment_monitor LIBGAMEPAD_ATTACHMENT_MONITOR; + + +/** + * Device attachment event type + */ +enum libgamepad_attachment_event_type { + /** + * Device has been added + */ + LIBGAMEPAD_ADDED, + + /** + * Device has been removed + */ + LIBGAMEPAD_REMOVED +}; + + +/** + * Device type + * + * A device can have any number of these applied to + * it, it may also have unknown types beyond these + */ +enum libgamepad_type { + /** + * Gamepad, joystick, steering wheel, or similar + */ + LIBGAMEPAD_GAMEPAD = 0x0001, + + /** + * Computer mouse + */ + LIBGAMEPAD_MOUSE = 0x0002 +}; + + +/** + * Gamepad input type + */ +enum libgamepad_input_type { + /** + * Button/key + */ + LIBGAMEPAD_BUTTON, + + /** + * Absolute axis + */ + LIBGAMEPAD_ABSOLUTE_AXIS, + + /** + * Relative axis + */ + LIBGAMEPAD_RELATIVE_AXIS +}; + + +/** + * Subdevice input event structure + */ +struct libgamepad_input_event { + /** + * The affect 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 + */ + uint16_t code; + + /** + * The new value on the button/key or axis, + * or the delta if on a relative axis + */ + int32_t value; + + /** + * Event timestamp + */ + struct timeval time; +}; + + +/** + * Subdevice on a device + * + * For example a modern gamepad may be split into + * a gamepad, computer mouse, and motion sensor + * + * Structure is always deallocated by the library + * using free(3) + */ +struct libgamepad_subdevice { + /** + * Device type + */ + enum libgamepad_type type; + + /** + * Device path + */ + char path[]; +}; + + +/** + * Physical device + * + * Contents of structure is deallocated with + * `libgamepad_close_superdevice` + */ +struct libgamepad_superdevice { + /** + * Device path in /sys + */ + char *syspath; + + /** + * Number of subdevices + */ + size_t ndevices; + + /** + * Subdevices + */ + struct libgamepad_subdevice **devices; + + /** + * Number of LEDs + */ + size_t nleds; + + /** + * Paths to LEDs + */ + char **leds; + + /** + * Number of power supplies (batteries) + */ + size_t npower_supplies; + + /** + * Paths of power supplies (batteries) + */ + char **power_supplies; +}; + + +/** + * Subdevice structure for detailed information listening on input events + */ +struct libgamepad_device { + /** + * File descriptor to the device, the application + * may use it to poll for read-readyness + */ + int fd; + + /** + * Bus type the device is connect via, see BUS_-prefixed + * constants in + */ + int bus_type; + + /** + * Vendor ID for the device (sub- or superdevice) + */ + int vendor; + + /** + * Product ID for the device (sub- or superdevice) + */ + int product; + + /** + * Product version ID for the device (sub- or superdevice) + */ + int version; + + /** + * FOR INTERNAL USE + * + * Specifies whether `.fd` shall be closed with the device + */ + int close_fd; + + /** + * FOR INTERNAL USE + * + * Whether the device must be synchronised + */ + int require_sync; + + /** + * Human-readable device (sub- or superdevice) name + */ + const char *name; + + /** + * ID that is supposted to be unique to the device + * (sub- or superdevice) + */ + const char *unique_id; + + /** + * The location if the device + */ + const char *physical_location; + + /** + * libevdev instance for the device + */ + struct libevdev *dev; + + /** + * Number of (digital) buttons/keys present + * on the device + */ + size_t nbuttons; + + /** + * Number of absolute axes present on the device + */ + size_t nabsolute_axes; + + /** + * Number of relative axes present on the device + */ + size_t nrelative_axes; + + /** + * Maps from button/key codes to button indices, + * non-present buttons/keys map to -1, other + * values are in [0, `.nbuttons`[. + */ + int16_t button_map[KEY_CNT]; + + /** + * Maps from absolute axis codes to absolute axis + * indices, non-present axes map to -1, other + * values are in [0, `.nabsolute_axes`[. + */ + int16_t absolute_axis_map[ABS_CNT]; + + /** + * Maps from relative axis codes to absolute axis + * indices, non-present axes map to -1, other + * values are in [0, `.nrelative_axes`[. + */ + int16_t relative_axis_map[REL_CNT]; + + /** + * Map from button/key indices to button/key codes + */ + uint16_t *buttons; + + /** + * Map from absolute axis indices to absolute axis codes + */ + uint16_t *absolute_axes; + + /** + * Map from relative axis indices to relative axis codes + */ + uint16_t *relative_axes; +}; + + +/** + * Get a list of all available physical devices + * + * @param devicesp Output parameter for the list of devices + * @param ndevicesp Output parameter for the number of listed devices + * @return 0 on success, -1 on failure + * + * This function may fail for any reason specified for + * realloc(3), open(3), fdopendir(3), or readdir(3) + */ +int libgamepad_list_superdevices(char ***, size_t *); + +/** + * Get information about a physical device + * + * @param devicep Output parameter for the device information, + * allocated memory shall be deallocated with + * `libgamepad_close_superdevice(devicep)` + * @param syspath The path of the device, in /sys + * @return 0 on success, -1 on failure + * + * This function may fail for any reason specified for + * realloc(3), openat(3), fdopendir(3), or readdir(3) + */ +int libgamepad_open_superdevice(struct libgamepad_superdevice *, const char *); + +/** + * Deallocate the contents of a `struct libgamepad_superdevice` + * + * @param device The structure whose nested memory allocations + * shall be deallocated, may be `NULL` + */ +void libgamepad_close_superdevice(struct libgamepad_superdevice *); + + +/** + * Create a device attachment monitor + * + * The user shall poll the returned file descriptor + * for read-readiness, and whenever it is ready, + * call `libgamepad_get_attachment_event` + * + * @param monitorp Output parameter for the monitor, shall deallocated with + * `libgamepad_destroy_attachment_monitor` when no longer used + * @return A file descriptor on successful completion, -1 on failure + * + * This function may fail for any reason specified by + * malloc(3), udev_new(3), udev_monitor_new_from_netlink(3), or + * udev_monitor_enable_receiving(3) + */ +int libgamepad_create_attachment_monitor(LIBGAMEPAD_ATTACHMENT_MONITOR **); + +/** + * Deallocate a device attachment monitor + * + * @param monitor The monitor to deallocate; the file descriptor returned + * with it will also be closed and become invalid, may be `NULL` + */ +void libgamepad_destroy_attachment_monitor(LIBGAMEPAD_ATTACHMENT_MONITOR *); + +/** + * Get the next device attachment event + * + * @param monitor Monitor created with `libgamepad_create_attachment_monitor` + * @param syspathp Pointer to output buffer for the device's path in /sys; + * the buffer may be reallocated by the function + * @param sizep Pointer to the allocation size of `*syspath`; may be + * updated by the function + * @param typep Output parameter for the attachment event type; in the + * event that this value is set to `LIBGAMEPAD_REMOVED`, the + * device is not necessarily on supported by this library + * @return 1 on success, 0 if the received event was suppressed + * by the library, -1 on failure + * + * This function may fail for any reason specified for + * realloc(3) or udev_monitor_receive_device(3); notable, + * this function may set `errno` to `EAGAIN` or `EINTR` + */ +int libgamepad_get_attachment_event(LIBGAMEPAD_ATTACHMENT_MONITOR *, char **, size_t *, enum libgamepad_attachment_event_type *); + + +/** + * Open a subdevice + * + * @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 + * (unless it is an absolute path), or `AT_FDCWD` for the + * current working directory + * @param path The path to the device, if `NULL` or empty, `dirfd` + * will be used as the file descriptor to the device, + * in which case the application must keep it open until + * the device is closed, and then close it manually, and + * `mode` is ignored + * @param mode Mode to open the device in, normally `O_RDONLY`, + * `O_RDONLY|O_NONBLOCK`, `O_RDWR`, or `O_RDWR|O_NONBLOCK` + * @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) + */ +int libgamepad_open_device(struct libgamepad_device *, int, const char *, int); + +/** + * Close a subdevice + * + * @param device Device information and handles, may be `NULL` + */ +void 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`. + * + * @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 + * + * This function may fail for any reason specified for + * libevdev_next_event(3), including: + * - EAGAIN no more data currently available (non-blocking mode) + * - EINTR system call was interrupted, try again + * - ENODEV device has been unplugged + */ +int libgamepad_next_event(struct libgamepad_device *, struct libgamepad_input_event *); + + +/** + * Get the name of a button/key + * + * @param code The button/key + * @return The button/key's name + */ +inline const char * +libgamepad_get_button_name(unsigned int code) +{ + return libevdev_event_code_get_name(EV_KEY, code); +} + +/** + * Get the name of an absolute axis + * + * @param code The axis + * @return The axis' name + */ +inline const char * +libgamepad_get_absolute_axis_name(unsigned int code) +{ + return libevdev_event_code_get_name(EV_ABS, code); +} + +/** + * Get the name of a relative axis + * + * @param code The axis + * @return The axis' name + */ +inline const char * +libgamepad_get_relative_axis_name(unsigned int code) +{ + return libevdev_event_code_get_name(EV_REL, code); +} + +/** + * Get whether a button/key is pressed down or not + * + * @param device The device to retrieve the information for + * @param code The button/key + * @return 1 if the button/key is pressed down, + * 0 otherwise + */ +inline int +libgamepad_get_button_is_pressed(struct libgamepad_device *device, unsigned int code) +{ + return libevdev_get_event_value(device->dev, EV_KEY, code); +} + +/** + * Get information about an absolute axis + * + * @param device The device to retrieve the information for + * @param code The axis + * @return Information about the axis + */ +inline const struct input_absinfo * /* `struct input_absinfo` is defined in */ +libgamepad_get_absolute_axis_info(struct libgamepad_device *device, unsigned int code) +{ + return libevdev_get_abs_info(device->dev, code); +} + + +/** + * Grab a subdevice, if not already grabbed + * + * This is supposed to block out clients from + * retrieving events for the device, but it + * does not necessarily stop them from reading + * the device state + * + * @param device The device to grab + * @return 0 on success, -1 on failure + * + * May fail for any reason specified for libevdev_grab(3) + * with LIBEVDEV_GRAB applied + * + * In the event that the device is already grabbed by + * via another file descriptor (duplicates do not share + * grab), this function will return -1 and set `errno` + * to `EBUSY` + */ +inline int +libgamepad_grab(struct libgamepad_device *device) +{ + int err = libevdev_grab(device->dev, LIBEVDEV_GRAB); + if (err < 0) { + errno = -err; + return -1; + } + return 0; +} + +/** + * Ungrab a subdevice, unless not currently grabbed + * + * @param device The device to ungrab + * @return 0 on success, -1 on failure + * + * May fail for any reason specified for libevdev_grab(3) + * with LIBEVDEV_UNGRAB applied + */ +inline int +libgamepad_ungrab(struct libgamepad_device *device) +{ + int err = libevdev_grab(device->dev, LIBEVDEV_UNGRAB); + if (err < 0) { + errno = -err; + return -1; + } + return 0; +} + + +/** + * Set the clock that event timestamps shall be reported in + * + * @param device The device to configure + * @param clockid The clock to use on event timestamps + * @return 0 on success, -1 on failure + * + * May fail for any reason specified for libevdev_set_clock_id(3) + */ +inline int +libgamepad_set_clock(struct libgamepad_device *device, clockid_t clockid) +{ + int err = libevdev_set_clock_id(device->dev, clockid); + if (err < 0) { + errno = -err; + return -1; + } + return 0; +} + + +#endif diff --git a/libgamepad_close_device.c b/libgamepad_close_device.c new file mode 100644 index 0000000..edb8361 --- /dev/null +++ b/libgamepad_close_device.c @@ -0,0 +1,22 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +void +libgamepad_close_device(struct libgamepad_device *device) +{ + if (device) { + if (device->close_fd) + close(device->fd); + if (device->dev) + libevdev_free(device->dev); + free(device->buttons); + free(device->absolute_axes); + free(device->relative_axes); + device->close_fd = 0; + device->dev = NULL; + device->buttons = NULL; + device->absolute_axes = NULL; + device->relative_axes = NULL; + } +} diff --git a/libgamepad_close_superdevice.c b/libgamepad_close_superdevice.c new file mode 100644 index 0000000..dc212bd --- /dev/null +++ b/libgamepad_close_superdevice.c @@ -0,0 +1,20 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +void +libgamepad_close_superdevice(struct libgamepad_superdevice *device) +{ + if (device) { + free(device->syspath); + while (device->ndevices) + free(device->devices[--device->ndevices]); + free(device->devices); + while (device->nleds) + free(device->leds[--device->nleds]); + free(device->leds); + while (device->npower_supplies) + free(device->power_supplies[--device->npower_supplies]); + free(device->power_supplies); + } +} diff --git a/libgamepad_create_attachment_monitor.c b/libgamepad_create_attachment_monitor.c new file mode 100644 index 0000000..20423b0 --- /dev/null +++ b/libgamepad_create_attachment_monitor.c @@ -0,0 +1,33 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +int +libgamepad_create_attachment_monitor(LIBGAMEPAD_ATTACHMENT_MONITOR **monitorp) +{ + *monitorp = calloc(1, sizeof(*monitorp)); + if (!*monitorp) + return -1; + + (*monitorp)->udev = udev_new(); + if (!(*monitorp)->udev) + goto fail; + + (*monitorp)->monitor = udev_monitor_new_from_netlink((*monitorp)->udev, "udev"); + if (!(*monitorp)->monitor) + goto fail_have_udev; + + if (udev_monitor_enable_receiving((*monitorp)->monitor)) + goto fail_have_monitor; + + return udev_monitor_get_fd((*monitorp)->monitor); + +fail_have_monitor: + udev_monitor_unref((*monitorp)->monitor); +fail_have_udev: + udev_unref((*monitorp)->udev); +fail: + free(*monitorp); + *monitorp = NULL; + return -1; +} diff --git a/libgamepad_destroy_attachment_monitor.c b/libgamepad_destroy_attachment_monitor.c new file mode 100644 index 0000000..bdddb83 --- /dev/null +++ b/libgamepad_destroy_attachment_monitor.c @@ -0,0 +1,15 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +void +libgamepad_destroy_attachment_monitor(LIBGAMEPAD_ATTACHMENT_MONITOR *monitor) +{ + if (monitor) { + if (monitor->monitor) + udev_monitor_unref(monitor->monitor); + if (monitor->udev) + udev_unref(monitor->udev); + free(monitor); + } +} diff --git a/libgamepad_get_absolute_axis_info.c b/libgamepad_get_absolute_axis_info.c new file mode 100644 index 0000000..8527281 --- /dev/null +++ b/libgamepad_get_absolute_axis_info.c @@ -0,0 +1,5 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +extern inline const struct input_absinfo *libgamepad_get_absolute_axis_info(struct libgamepad_device *, unsigned int); diff --git a/libgamepad_get_absolute_axis_name.c b/libgamepad_get_absolute_axis_name.c new file mode 100644 index 0000000..3a103f4 --- /dev/null +++ b/libgamepad_get_absolute_axis_name.c @@ -0,0 +1,5 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +extern inline const char *libgamepad_get_absolute_axis_name(unsigned int); diff --git a/libgamepad_get_attachment_event.c b/libgamepad_get_attachment_event.c new file mode 100644 index 0000000..4462356 --- /dev/null +++ b/libgamepad_get_attachment_event.c @@ -0,0 +1,60 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +int +libgamepad_get_attachment_event(LIBGAMEPAD_ATTACHMENT_MONITOR *monitor, char **syspathp, size_t *sizep, + enum libgamepad_attachment_event_type *typep) +{ + struct udev_device *device; + const char *action; + const char *syspath; + size_t len; + void *new; + char *syspath_end; + int saved_errno; + + device = udev_monitor_receive_device(monitor->monitor); + if (!device) + return -1; + + action = udev_device_get_action(device); + if (!strcmp(action, "bind")) + *typep = LIBGAMEPAD_ADDED; + else if (!strcmp(action, "unbind")) + *typep = LIBGAMEPAD_REMOVED; + else + goto suppress; + + syspath = udev_device_get_syspath(device); + if (!syspath || !*syspath) + goto suppress; + len = strlen(syspath) + sizeof("/hidraw"); + if (len > *sizep) { + new = realloc(*syspathp, len); + if (!new) { + udev_device_unref(device); + return -1; + } + *syspathp = new; + *sizep = len; + } + syspath_end = stpcpy(*syspathp, syspath); + + if (*typep == LIBGAMEPAD_ADDED) { + stpcpy(syspath_end, "/hidraw"); + saved_errno = errno; + if (access(*syspathp, F_OK)) { + errno = saved_errno; + goto suppress; + } + *syspath_end = '\0'; + } + + udev_device_unref(device); + return 1; + +suppress: + udev_device_unref(device); + return 0; +} diff --git a/libgamepad_get_button_is_pressed.c b/libgamepad_get_button_is_pressed.c new file mode 100644 index 0000000..46a028e --- /dev/null +++ b/libgamepad_get_button_is_pressed.c @@ -0,0 +1,5 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +extern inline int libgamepad_get_button_is_pressed(struct libgamepad_device *, unsigned int); diff --git a/libgamepad_get_button_name.c b/libgamepad_get_button_name.c new file mode 100644 index 0000000..9ff424d --- /dev/null +++ b/libgamepad_get_button_name.c @@ -0,0 +1,5 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +extern inline const char *libgamepad_get_button_name(unsigned int); diff --git a/libgamepad_get_relative_axis_name.c b/libgamepad_get_relative_axis_name.c new file mode 100644 index 0000000..3867216 --- /dev/null +++ b/libgamepad_get_relative_axis_name.c @@ -0,0 +1,5 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +extern inline const char *libgamepad_get_relative_axis_name(unsigned int); diff --git a/libgamepad_grab.c b/libgamepad_grab.c new file mode 100644 index 0000000..a6a6a41 --- /dev/null +++ b/libgamepad_grab.c @@ -0,0 +1,5 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +extern inline int libgamepad_grab(struct libgamepad_device *); diff --git a/libgamepad_list_superdevices.c b/libgamepad_list_superdevices.c new file mode 100644 index 0000000..f8c7c4d --- /dev/null +++ b/libgamepad_list_superdevices.c @@ -0,0 +1,77 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +int +libgamepad_list_superdevices(char ***devicesp, size_t *ndevicesp) +{ + char path[PATH_MAX], target[PATH_MAX]; /* TODO do not use PATH_MAX */ + int dirfd; + DIR *dir; + struct dirent *f; + ssize_t r; + char **paths = NULL; + size_t n = 0, i; + void *new; + int saved_errno = errno; + + *devicesp = NULL; + *ndevicesp = 0; + + dirfd = open("/sys/class/input/", O_DIRECTORY); + if (dirfd < 0) + return -1; + + dir = fdopendir(dirfd); + if (!dir) { + close(dirfd); + return -1; + } + + while (errno = 0, (f = readdir(dir))) { + if (!strncmp(f->d_name, "event", 5) && isdigit(f->d_name[5])) { + stpcpy(stpcpy(stpcpy(path, "/sys/class/input/"), f->d_name), "/device/device"); + dirfd = open(path, O_PATH); + if (dirfd < 0) + continue; + sprintf(path, "/dev/fd/%i", dirfd); + r = readlink(path, target, sizeof(target) - 1); + close(dirfd); + if (r < 0) + continue; + target[r] = '\0'; + if (paths) + for (i = 0; i < n; i++) + if (!strcmp(paths[i], target)) + goto next; + stpcpy(stpcpy(path, target), "/input"); + if (access(path, F_OK)) + continue; + new = realloc(paths, (n + 1) * sizeof(*paths)); + if (!new) + goto fail; + paths = new; + paths[n] = strdup(target); + if (!paths[n]) + goto fail; + n += 1; + } + next:; + } + + if (errno) + goto fail; + closedir(dir); + + errno = saved_errno; + *devicesp = paths; + *ndevicesp = n; + return 0; + +fail: + for (i = 0; i < n; i++) + free(paths[i]); + free(paths); + closedir(dir); + return -1; +} diff --git a/libgamepad_next_event.c b/libgamepad_next_event.c new file mode 100644 index 0000000..acc23ea --- /dev/null +++ b/libgamepad_next_event.c @@ -0,0 +1,57 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +int +libgamepad_next_event(struct libgamepad_device *device, struct libgamepad_input_event *eventp) +{ + int r; + struct input_event ev; + + if (!device->require_sync) { + r = libevdev_next_event(device->dev, LIBEVDEV_READ_FLAG_NORMAL, &ev); + if (r < 0) { + errno = -r; + return -1; + } + } else { + r = libevdev_next_event(device->dev, LIBEVDEV_READ_FLAG_SYNC, &ev); + if (r == -EAGAIN) { + device->require_sync = 0; /* yes, this is how it is document */ + return 0; + } else if (r < 0) { + errno = -r; + return -1; + } + if (r != LIBEVDEV_READ_STATUS_SYNC) /* just to be safe */ + device->require_sync = 0; + } + + 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; + + switch (ev.type) { + case EV_KEY: + eventp->type = LIBGAMEPAD_BUTTON; + return 1; + + case EV_ABS: + eventp->type = LIBGAMEPAD_ABSOLUTE_AXIS; + return 1; + + case EV_REL: + eventp->type = LIBGAMEPAD_RELATIVE_AXIS; + return 1; + + case EV_SYN: + if (ev.code == SYN_DROPPED) + device->require_sync = 1; + return 0; + + default: + return 0; + } +} diff --git a/libgamepad_open_device.c b/libgamepad_open_device.c new file mode 100644 index 0000000..5b32658 --- /dev/null +++ b/libgamepad_open_device.c @@ -0,0 +1,98 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +int +libgamepad_open_device(struct libgamepad_device *devicep, int dirfd, const char *path, int mode) +{ + int err; + unsigned int i; + uint16_t n; + + devicep->fd = dirfd; + devicep->close_fd = 0; + devicep->require_sync = 0; + devicep->dev = NULL; + devicep->buttons = NULL; + devicep->absolute_axes = NULL; + devicep->relative_axes = NULL; + + if (path && *path) { + devicep->fd = openat(dirfd, path, mode); + if (devicep->fd < 0) + return -1; + devicep->close_fd = 1; + } + + err = -libevdev_new_from_fd(devicep->fd, &devicep->dev); + if (err > 0) { + if (devicep->close_fd) { + close(devicep->fd); + devicep->close_fd = 0; + } + errno = err; + return -1; + } + + devicep->name = libevdev_get_name(devicep->dev); + devicep->unique_id = libevdev_get_uniq(devicep->dev); + devicep->physical_location = libevdev_get_phys(devicep->dev); + devicep->bus_type = libevdev_get_id_bustype(devicep->dev); + devicep->vendor = libevdev_get_id_vendor(devicep->dev); + devicep->product = libevdev_get_id_product(devicep->dev); + devicep->version = libevdev_get_id_version(devicep->dev); + + devicep->nbuttons = 0; + for (i = 0; i < KEY_CNT; i++) { + devicep->button_map[i] = -1; + if (libevdev_has_event_code(devicep->dev, EV_KEY, i)) + devicep->button_map[i] = (int16_t)devicep->nbuttons++; + } + + devicep->nabsolute_axes = 0; + for (i = 0; i < ABS_CNT; i++) { + devicep->absolute_axis_map[i] = -1; + if (libevdev_has_event_code(devicep->dev, EV_ABS, i)) + devicep->absolute_axis_map[i] = (int16_t)devicep->nabsolute_axes++; + } + + devicep->nrelative_axes = 0; + for (i = 0; i < REL_CNT; i++) { + devicep->relative_axis_map[i] = -1; + if (libevdev_has_event_code(devicep->dev, EV_REL, i)) + devicep->relative_axis_map[i] = (int16_t)devicep->nrelative_axes++; + } + + if (devicep->nbuttons) { + devicep->buttons = calloc(devicep->nbuttons, sizeof(*devicep->buttons)); + if (!devicep->buttons) + goto fail; + for (i = 0, n = 0; i < KEY_CNT; i++) + if (devicep->button_map[i] >= 0) + devicep->buttons[n++] = (uint16_t)i; + } + + if (devicep->nabsolute_axes) { + devicep->absolute_axes = calloc(devicep->nabsolute_axes, sizeof(*devicep->absolute_axes)); + if (!devicep->absolute_axes) + goto fail; + for (i = 0, n = 0; i < ABS_CNT; i++) + if (devicep->absolute_axis_map[i] >= 0) + devicep->absolute_axes[n++] = (uint16_t)i; + } + + if (devicep->nrelative_axes) { + devicep->relative_axes = calloc(devicep->nrelative_axes, sizeof(*devicep->relative_axes)); + if (!devicep->relative_axes) + goto fail; + for (i = 0, n = 0; i < REL_CNT; i++) + if (devicep->relative_axis_map[i] >= 0) + devicep->relative_axes[n++] = (uint16_t)i; + } + + return 0; + +fail: + libgamepad_close_device(devicep); + return -1; +} diff --git a/libgamepad_open_superdevice.c b/libgamepad_open_superdevice.c new file mode 100644 index 0000000..b6b975f --- /dev/null +++ b/libgamepad_open_superdevice.c @@ -0,0 +1,149 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +int +libgamepad_open_superdevice(struct libgamepad_superdevice *devicep, const char *syspath) +{ + struct libgamepad_subdevice *subdev; + int dirfd = -1, dirfd2, dirfd3; + DIR *dir = NULL, *dir2 = NULL; + struct dirent *f, *f2; + enum libgamepad_type type; + size_t first = 0, len; + void *new; + int saved_errno; + + devicep->ndevices = 0; + devicep->devices = NULL; + devicep->nleds = 0; + devicep->leds = NULL; + devicep->npower_supplies = 0; + devicep->power_supplies = NULL; + + devicep->syspath = strdup(syspath); + if (!devicep->syspath) + return -1; + + dirfd = open(syspath, O_DIRECTORY); + if (dirfd < 0) + goto fail; + + dirfd2 = openat(dirfd, "input", O_DIRECTORY); + if (dirfd2 < 0) + goto fail; + dir = fdopendir(dirfd2); + if (!dir) + goto fail; + while (errno = 0, (f = readdir(dir))) { + if (!strncmp(f->d_name, "input", 5) && isdigit(f->d_name[5])) { + dirfd3 = openat(dirfd2, f->d_name, O_DIRECTORY); + if (dirfd3 < 0) + goto fail; + dir2 = fdopendir(dirfd3); + if (!dir2) + goto fail; + type = 0; + while (errno = 0, (f2 = readdir(dir2))) { + if (!strncmp(f2->d_name, "event", 5) && isdigit(f2->d_name[5])) { + new = realloc(devicep->devices, (devicep->ndevices + 1) * sizeof(*devicep->devices)); + if (!new) + goto fail; + devicep->devices = new; + len = sizeof("/dev/input/") + strlen(f2->d_name); + subdev = malloc(offsetof(struct libgamepad_subdevice, path) + len); + if (!subdev) + goto fail; + stpcpy(stpcpy(subdev->path, "/dev/input/"), f2->d_name); + devicep->devices[devicep->ndevices++] = subdev; + } else if (!strncmp(f2->d_name, "js", 2) && isdigit(f2->d_name[2])) { + type |= LIBGAMEPAD_GAMEPAD; + } else if (!strncmp(f2->d_name, "mouse", 5) && isdigit(f2->d_name[5])) { + type |= LIBGAMEPAD_MOUSE; + } + } + while (first < devicep->ndevices) + devicep->devices[first++]->type = type; + closedir(dir2); + dir2 = NULL; + } + } + if (errno) + goto fail; + closedir(dir); + dir = NULL; + + dirfd2 = openat(dirfd, "leds", O_DIRECTORY); + if (dirfd2 >= 0) { + dir = fdopendir(dirfd2); + if (!dir) + goto fail; + len = strlen(syspath) + sizeof("/leds/"); + while (errno = 0, (f = readdir(dir))) { + if (f->d_name[0] != '.') { + new = realloc(devicep->leds, (devicep->nleds + 1) * sizeof(*devicep->leds)); + if (!new) + goto fail; + devicep->leds = new; + devicep->leds[devicep->nleds] = malloc(len + strlen(f->d_name)); + if (!devicep->leds[devicep->nleds]) + goto fail; + stpcpy(stpcpy(stpcpy(devicep->leds[devicep->nleds], syspath), "/leds/"), f->d_name); + devicep->nleds += 1; + } + } + if (errno) + goto fail; + closedir(dir); + dir = NULL; + } + + dirfd2 = openat(dirfd, "power_supply", O_DIRECTORY); + if (dirfd2 >= 0) { + dir = fdopendir(dirfd2); + if (!dir) + goto fail; + len = strlen(syspath) + sizeof("/power_supply/"); + while (errno = 0, (f = readdir(dir))) { + if (f->d_name[0] != '.') { + new = realloc(devicep->power_supplies, + (devicep->npower_supplies + 1) * sizeof(*devicep->power_supplies)); + if (!new) + goto fail; + devicep->power_supplies = new; + devicep->power_supplies[devicep->npower_supplies] = malloc(len + strlen(f->d_name)); + if (!devicep->power_supplies[devicep->npower_supplies]) + goto fail; + stpcpy(stpcpy(stpcpy(devicep->power_supplies[devicep->npower_supplies], syspath), + "/power_supply/"), f->d_name); + devicep->npower_supplies += 1; + } + } + if (errno) + goto fail; + closedir(dir); + dir = NULL; + } + + close(dirfd); + return 0; + +fail: + saved_errno = errno; + if (dir2) + closedir(dir2); + if (dir) + closedir(dir); + if (dirfd >= 0) + close(dirfd); + libgamepad_close_superdevice(devicep); + devicep->syspath = NULL; + devicep->ndevices = 0; + devicep->devices = NULL; + devicep->nleds = 0; + devicep->leds = NULL; + devicep->npower_supplies = 0; + devicep->power_supplies = NULL; + errno = saved_errno; + return -1; +} diff --git a/libgamepad_set_clock.c b/libgamepad_set_clock.c new file mode 100644 index 0000000..312f963 --- /dev/null +++ b/libgamepad_set_clock.c @@ -0,0 +1,5 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +extern inline int libgamepad_set_clock(struct libgamepad_device *, clockid_t); diff --git a/libgamepad_ungrab.c b/libgamepad_ungrab.c new file mode 100644 index 0000000..18d39ac --- /dev/null +++ b/libgamepad_ungrab.c @@ -0,0 +1,5 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +extern inline int libgamepad_ungrab(struct libgamepad_device *); diff --git a/mk/linux.mk b/mk/linux.mk new file mode 100644 index 0000000..ad58f69 --- /dev/null +++ b/mk/linux.mk @@ -0,0 +1,6 @@ +LIBEXT = so +LIBFLAGS = -shared -Wl,-soname,lib$(LIB_NAME).$(LIBEXT).$(LIB_MAJOR) +LIBMAJOREXT = $(LIBEXT).$(LIB_MAJOR) +LIBMINOREXT = $(LIBEXT).$(LIB_VERSION) + +FIX_INSTALL_NAME = : diff --git a/mk/macos.mk b/mk/macos.mk new file mode 100644 index 0000000..6c0d575 --- /dev/null +++ b/mk/macos.mk @@ -0,0 +1,6 @@ +LIBEXT = dylib +LIBFLAGS = -dynamiclib -Wl,-compatibility_version,$(LIB_MAJOR) -Wl,-current_version,$(LIB_VERSION) +LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT) +LIBMINOREXT = $(LIB_VERSION).$(LIBEXT) + +FIX_INSTALL_NAME = install_name_tool -id "$(PREFIX)/lib/libgamepad.$(LIBMAJOREXT)" diff --git a/mk/windows.mk b/mk/windows.mk new file mode 100644 index 0000000..ed5ec8d --- /dev/null +++ b/mk/windows.mk @@ -0,0 +1,6 @@ +LIBEXT = dll +LIBFLAGS = -shared +LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT) +LIBMINOREXT = $(LIB_VERSION).$(LIBEXT) + +FIX_INSTALL_NAME = : diff --git a/test-attachments.c b/test-attachments.c new file mode 100644 index 0000000..9f8e63c --- /dev/null +++ b/test-attachments.c @@ -0,0 +1,50 @@ +/* See LICENSE file for copyright and license details. */ +#include "libgamepad.h" + +#include +#include +#include +#include + + +int +main(void) +{ + LIBGAMEPAD_ATTACHMENT_MONITOR *monitor; + int fd, r; + fd_set fds; + char *syspath = NULL; + size_t size = 0; + enum libgamepad_attachment_event_type action; + + fd = libgamepad_create_attachment_monitor(&monitor); + if (fd < 0) { + perror("libgamepad_create_attachment_monitor"); + return 1; + } + + FD_ZERO(&fds); + for (;;) { + FD_SET(fd, &fds); + if (select(fd + 1, &fds, NULL, NULL, NULL) < 0) { + if (errno == EINTR) + continue; + perror("select"); + libgamepad_destroy_attachment_monitor(monitor); + free(syspath); + return 1; + } + + r = libgamepad_get_attachment_event(monitor, &syspath, &size, &action); + if (r <= 0) { + if (!r || errno == EINTR || errno == EAGAIN) + continue; + perror("libgamepad_get_attachment_event"); + libgamepad_destroy_attachment_monitor(monitor); + free(syspath); + return 1; + } + + printf("%s %s\n", action == LIBGAMEPAD_ADDED ? "added" : "removed", syspath); + } +} diff --git a/test-details.c b/test-details.c new file mode 100644 index 0000000..dfff98f --- /dev/null +++ b/test-details.c @@ -0,0 +1,101 @@ +/* See LICENSE file for copyright and license details. */ +#include "libgamepad.h" + +#include +#include + +#define ELEMSOF(A) (sizeof(A) / sizeof(*(A))) + + +int +main(int argc, char *argv[]) +{ + const struct input_absinfo *absinfo; + struct libgamepad_device gamepad; + size_t i, n; + + if (argc != 2) { + fprintf(stderr, "Please provide the path to the subdevice as the only command line argument\n"); + return 1; + } + + if (libgamepad_open_device(&gamepad, AT_FDCWD, argv[1], O_RDONLY)) { + perror("libgamepad_open_device"); + return 1; + } + + printf("Bus type: %i\n", gamepad.bus_type); + printf("Vendor: %i\n", gamepad.vendor); + printf("Product: %i\n", gamepad.product); + printf("Version: %i\n", gamepad.version); + printf("Name: %s\n", gamepad.name); + printf("Unique id: %s\n", gamepad.unique_id); + printf("Physical location: %s\n", gamepad.physical_location); + + printf("Buttons:\n"); + for (i = 0; i < gamepad.nbuttons; i++) { + assert(gamepad.buttons); + assert(gamepad.buttons[i] < ELEMSOF(gamepad.button_map)); + assert(gamepad.button_map[gamepad.buttons[i]] == (int16_t)i); + printf("\t%s [pressed=%i]\n", + libgamepad_get_button_name(gamepad.buttons[i]), + libgamepad_get_button_is_pressed(&gamepad, (unsigned int)gamepad.buttons[i])); + } + n = 0; + for (i = 0; i < ELEMSOF(gamepad.button_map); i++) { + assert(gamepad.button_map[i] >= -1); + assert((ssize_t)gamepad.button_map[i] < (ssize_t)gamepad.nbuttons); + if (gamepad.button_map[i] != -1) { + assert(gamepad.buttons[gamepad.button_map[i]] == (uint16_t)i); + n++; + } + } + assert(n == gamepad.nbuttons); + + printf("Absolute axes:\n"); + for (i = 0; i < gamepad.nabsolute_axes; i++) { + assert(gamepad.absolute_axes); + assert(gamepad.absolute_axes[i] < ELEMSOF(gamepad.absolute_axis_map)); + assert(gamepad.absolute_axis_map[gamepad.absolute_axes[i]] == (int16_t)i); + absinfo = libgamepad_get_absolute_axis_info(&gamepad, gamepad.absolute_axes[i]); + if (!absinfo) { + printf("\t%s\n", libgamepad_get_absolute_axis_name(gamepad.absolute_axes[i])); + } else { + printf("\t%s [value=%i, min=%i, max=%i, fuzz=%i, flat=%i, resolution=%i]\n", + libgamepad_get_absolute_axis_name(gamepad.absolute_axes[i]), + absinfo->value, absinfo->minimum, absinfo->maximum, + absinfo->fuzz, absinfo->flat, absinfo->resolution); + } + } + n = 0; + for (i = 0; i < ELEMSOF(gamepad.absolute_axis_map); i++) { + assert(gamepad.absolute_axis_map[i] >= -1); + assert((ssize_t)gamepad.absolute_axis_map[i] < (ssize_t)gamepad.nabsolute_axes); + if (gamepad.absolute_axis_map[i] != -1) { + assert(gamepad.absolute_axes[gamepad.absolute_axis_map[i]] == (uint16_t)i); + n++; + } + } + assert(n == gamepad.nabsolute_axes); + + printf("Relative axes:\n"); + for (i = 0; i < gamepad.nrelative_axes; i++) { + assert(gamepad.relative_axes); + assert(gamepad.relative_axes[i] < ELEMSOF(gamepad.relative_axis_map)); + assert(gamepad.relative_axis_map[gamepad.relative_axes[i]] == (int16_t)i); + printf("\t%s\n", libgamepad_get_relative_axis_name(gamepad.relative_axes[i])); + } + n = 0; + for (i = 0; i < ELEMSOF(gamepad.relative_axis_map); i++) { + assert(gamepad.relative_axis_map[i] >= -1); + assert((ssize_t)gamepad.relative_axis_map[i] < (ssize_t)gamepad.nrelative_axes); + if (gamepad.relative_axis_map[i] != -1) { + assert(gamepad.relative_axes[gamepad.relative_axis_map[i]] == (uint16_t)i); + n++; + } + } + assert(n == gamepad.nrelative_axes); + + libgamepad_close_device(&gamepad); + return 0; +} diff --git a/test-input.c b/test-input.c new file mode 100644 index 0000000..1fd5c4c --- /dev/null +++ b/test-input.c @@ -0,0 +1,77 @@ +/* See LICENSE file for copyright and license details. */ +#include "libgamepad.h" + +#include +#include +#include +#include + +#define GRAB_DEVICE 0 + + +static struct libgamepad_device gamepad; + + +static void +sigint_handler(int signo) +{ + (void) signo; + +#if GRAB_DEVICE + if (libgamepad_ungrab(&gamepad)) + perror("libgamepad_ungrab"); +#endif + + libgamepad_close_device(&gamepad); + exit(0); +} + + +int +main(int argc, char *argv[]) +{ + struct libgamepad_input_event event; + int r; + + if (argc != 2) { + fprintf(stderr, "Please provide the path to the subdevice as the only command line argument\n"); + return 1; + } + + if (libgamepad_open_device(&gamepad, AT_FDCWD, argv[1], O_RDONLY)) { + perror("libgamepad_open_device"); + return 1; + } + +#if GRAB_DEVICE + if (libgamepad_grab(&gamepad)) + perror("libgamepad_grab"); +#endif + + if (signal(SIGINT, sigint_handler) == SIG_ERR) { + perror("signal"); + return 1; + } + + for (;;) { + r = libgamepad_next_event(&gamepad, &event); + if (r <= 0) { + if (!r || errno == EINTR) + continue; + perror("libgamepad_next_event"); + 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(event.code)); + } else if (event.type == LIBGAMEPAD_ABSOLUTE_AXIS) { + printf("%s ", libgamepad_get_absolute_axis_name(event.code)); + } else { + assert(event.type == LIBGAMEPAD_RELATIVE_AXIS); + printf("%s ", libgamepad_get_relative_axis_name(event.code)); + } + printf("%lli\n", (long long int)event.value); + } +} diff --git a/test-list.c b/test-list.c new file mode 100644 index 0000000..f604dd9 --- /dev/null +++ b/test-list.c @@ -0,0 +1,42 @@ +/* See LICENSE file for copyright and license details. */ +#include "libgamepad.h" + +#include +#include + + +int +main(void) +{ + char **devices; + size_t ndevices, i, j; + struct libgamepad_superdevice device; + + if (libgamepad_list_superdevices(&devices, &ndevices)) { + perror("libgamepad_list_superdevices"); + return 1; + } + + for (i = 0; i < ndevices; i++) { + if (libgamepad_open_superdevice(&device, devices[i])) { + perror("libgamepad_open_superdevice"); + free(devices[i]); + continue; + } + free(devices[i]); + + printf("%s\n", device.syspath); + for (j = 0; j < device.ndevices; j++) + printf("\t%s (%x)\n", device.devices[j]->path, device.devices[j]->type); + for (j = 0; j < device.nleds; j++) + printf("\t%s (LED)\n", device.leds[j]); + for (j = 0; j < device.npower_supplies; j++) + printf("\t%s (PSU)\n", device.power_supplies[j]); + + libgamepad_close_superdevice(&device); + } + + free(devices); + + return 0; +} -- cgit v1.2.3-70-g09d2