aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore16
-rw-r--r--LICENSE15
-rw-r--r--Makefile98
-rw-r--r--TODO7
-rw-r--r--common.h24
-rw-r--r--config.mk11
-rw-r--r--libgamepad.h566
-rw-r--r--libgamepad_close_device.c22
-rw-r--r--libgamepad_close_superdevice.c20
-rw-r--r--libgamepad_create_attachment_monitor.c33
-rw-r--r--libgamepad_destroy_attachment_monitor.c15
-rw-r--r--libgamepad_get_absolute_axis_info.c5
-rw-r--r--libgamepad_get_absolute_axis_name.c5
-rw-r--r--libgamepad_get_attachment_event.c60
-rw-r--r--libgamepad_get_button_is_pressed.c5
-rw-r--r--libgamepad_get_button_name.c5
-rw-r--r--libgamepad_get_relative_axis_name.c5
-rw-r--r--libgamepad_grab.c5
-rw-r--r--libgamepad_list_superdevices.c77
-rw-r--r--libgamepad_next_event.c57
-rw-r--r--libgamepad_open_device.c98
-rw-r--r--libgamepad_open_superdevice.c149
-rw-r--r--libgamepad_set_clock.c5
-rw-r--r--libgamepad_ungrab.c5
-rw-r--r--mk/linux.mk6
-rw-r--r--mk/macos.mk6
-rw-r--r--mk/windows.mk6
-rw-r--r--test-attachments.c50
-rw-r--r--test-details.c101
-rw-r--r--test-input.c77
-rw-r--r--test-list.c42
31 files changed, 1596 insertions, 0 deletions
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 <maandree@kth.se>
+
+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 <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <libevdev/libevdev.h>
+#include <libudev.h>
+
+
+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 <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <time.h>
+
+#include <libevdev/libevdev.h>
+
+
+/**
+ * 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 <linux/input.h>
+ */
+ 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 <linux/input.h> */
+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 <sys/select.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+
+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 <assert.h>
+#include <stdio.h>
+
+#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 <assert.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#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 <stdio.h>
+#include <stdlib.h>
+
+
+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;
+}