From 329274e6c90124824c0c7a507820a2648f589bfc Mon Sep 17 00:00:00 2001 From: nirenjan Date: Wed, 8 Jul 2020 00:31:07 -0700 Subject: [PATCH] Add initial version of libx52io --- Doxyfile.in | 1 + configure.ac | 6 +- lib/Makefile.am | 2 +- lib/libx52/doc/main.dox | 1 + lib/libx52io/Makefile.am | 32 ++++ lib/libx52io/io_common.h | 32 ++++ lib/libx52io/io_core.c | 102 +++++++++++ lib/libx52io/libx52io.h | 366 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 540 insertions(+), 2 deletions(-) create mode 100644 lib/libx52io/Makefile.am create mode 100644 lib/libx52io/io_common.h create mode 100644 lib/libx52io/io_core.c create mode 100644 lib/libx52io/libx52io.h diff --git a/Doxyfile.in b/Doxyfile.in index 8873dcc..5782d6f 100644 --- a/Doxyfile.in +++ b/Doxyfile.in @@ -858,6 +858,7 @@ INPUT_ENCODING = UTF-8 FILE_PATTERNS = \ libx52.h \ + libx52io.h \ libx52util.h \ x52_cli.c \ *.dox diff --git a/configure.ac b/configure.ac index 54192f3..492a053 100644 --- a/configure.ac +++ b/configure.ac @@ -47,7 +47,10 @@ AC_SUBST([X52_INCLUDE], ["-I \$(top_srcdir)/lib/libx52"]) # Check for hidapi. This uses a different pkg-config file on Linux vs other # hosts, so check accordingly AM_COND_IF([LINUX], [hidapi_backend=hidapi-hidraw], [hidapi_backend=hidapi]) -AX_PKG_CHECK_MODULES([HIDAPI], [${hidapi_backend}], [], [have_hidapi=yes], [have_hidapi=no]) +AX_PKG_CHECK_MODULES([HIDAPI], [${hidapi_backend}]) +AC_SUBST([HIDAPI_CFLAGS]) +AC_SUBST([HIDAPI_LDFLAGS]) +AC_SUBST([HIDAPI_LIBS]) # Check for pthreads ACX_PTHREAD @@ -102,6 +105,7 @@ AC_CONFIG_FILES([ po/Makefile.in lib/libx52/libx52.pc lib/libusbx52/Makefile lib/libx52util/Makefile + lib/libx52io/Makefile udev/Makefile utils/Makefile utils/cli/Makefile diff --git a/lib/Makefile.am b/lib/Makefile.am index 03a20ff..e6c3dca 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -4,5 +4,5 @@ # # SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0 -SUBDIRS = libx52 libx52util libusbx52 +SUBDIRS = libx52 libx52util libusbx52 libx52io diff --git a/lib/libx52/doc/main.dox b/lib/libx52/doc/main.dox index 8f5db8a..599263b 100644 --- a/lib/libx52/doc/main.dox +++ b/lib/libx52/doc/main.dox @@ -34,6 +34,7 @@ See the documentation for the following files for a complete list of all functions. - libx52.h +- libx52io.h - libx52util.h */ diff --git a/lib/libx52io/Makefile.am b/lib/libx52io/Makefile.am new file mode 100644 index 0000000..194ed2a --- /dev/null +++ b/lib/libx52io/Makefile.am @@ -0,0 +1,32 @@ +# Automake for libx52io +# +# Copyright (C) 2012-2020 Nirenjan Krishnan (nirenjan@nirenjan.org) +# +# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0 + +lib_LTLIBRARIES = libx52io.la + +# X52 IO library +# This library handles the HID parsing of the X52 USB reports +# Libtool Version Info +# See: https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html +libx52io_v_CUR=0 +libx52io_v_AGE=0 +libx52io_v_REV=0 +libx52io_la_SOURCES = io_core.c +libx52io_la_CFLAGS = @HIDAPI_CFLAGS@ -DLOCALEDIR=\"$(localedir)\" -I $(top_srcdir) $(WARN_CFLAGS) +libx52io_la_LDFLAGS = \ + -export-symbols-regex '^libx52io_' \ + -version-info $(libx52io_v_CUR):$(libx52io_v_REV):$(libx52io_v_AGE) @HIDAPI_LIBS@ \ + $(WARN_LDFLAGS) +libx52io_la_LIBADD = @LTLIBINTL@ + +# Header files that need to be copied +x52includedir = $(includedir)/x52pro +x52include_HEADERS = libx52io.h + +# pkg-config files +# pkgconfig_DATA = libx52io.pc + +# Extra files that need to be in the distribution +EXTRA_DIST = libx52io.h io_common.h diff --git a/lib/libx52io/io_common.h b/lib/libx52io/io_common.h new file mode 100644 index 0000000..c0e30d4 --- /dev/null +++ b/lib/libx52io/io_common.h @@ -0,0 +1,32 @@ +/* + * Saitek X52 IO driver - common definitions + * + * Copyright (C) 2012-2020 Nirenjan Krishnan (nirenjan@nirenjan.org) + * + * SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0 + */ + +#ifndef IO_COMMON_H +#define IO_COMMON_H + +#include +#include "libx52io.h" +#include "hidapi.h" + +// Function handler for parsing reports +typedef int (*x52_parse_report)(unsigned char *data, int length); + +struct libx52io_context { + hid_device *handle; + + int32_t axis_min[LIBX52IO_AXIS_MAX]; + int32_t axis_max[LIBX52IO_AXIS_MAX]; + + int16_t pid; + x52_parse_report parser; +}; + +void _x52io_set_axis_range(libx52io_context *ctx); +void _x52io_set_report_parser(libx52io_context *ctx); + +#endif // !defined IO_COMMON_H diff --git a/lib/libx52io/io_core.c b/lib/libx52io/io_core.c new file mode 100644 index 0000000..5570e98 --- /dev/null +++ b/lib/libx52io/io_core.c @@ -0,0 +1,102 @@ +/* + * Saitek X52 IO driver + * + * Copyright (C) 2012-2020 Nirenjan Krishnan (nirenjan@nirenjan.org) + * + * SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0 + */ + +#include +#include +#include "io_common.h" + +int libx52io_init(libx52io_context **ctx) +{ + libx52io_context *tmp; + + if (ctx == NULL) { + return LIBX52IO_ERROR_INVALID; + } + + if (hid_init()) { + return LIBX52IO_ERROR_INIT_FAILURE; + } + + // Allocate a context + tmp = calloc(1, sizeof(*tmp)); + if (tmp == NULL) { + return LIBX52IO_ERROR_INIT_FAILURE; + } + + *ctx = tmp; + return LIBX52IO_SUCCESS; +} + +void libx52io_exit(libx52io_context *ctx) +{ + // Close any open handles, free context + if (ctx == NULL) { + return; + } + + libx52io_close(ctx); + hid_exit(); +} + +int libx52io_close(libx52io_context *ctx) +{ + if (ctx == NULL) { + return LIBX52IO_ERROR_INVALID; + } + + if (ctx->handle != NULL) { + hid_close(ctx->handle); + memset(ctx, 0, sizeof(*ctx)); + } + + return LIBX52IO_SUCCESS; +} + +int libx52io_open(libx52io_context *ctx) +{ + struct hid_device_info *devs, *cur_dev; + int rc = LIBX52IO_SUCCESS; + + if (ctx == NULL) { + return LIBX52IO_ERROR_INVALID; + } + + /* Close any already open handles */ + libx52io_close(ctx); + + /* Enumerate all Saitek HID devices */ + devs = hid_enumerate(0x06a3, 0); + cur_dev = devs; + while (cur_dev) { + switch (cur_dev->product_id) { + case 0x0255: + case 0x075c: + case 0x0762: + ctx->handle = hid_open_path(cur_dev->path); + if (ctx->handle == NULL) { + rc = LIBX52IO_ERROR_CONN; + goto finally; + } + + ctx->pid = cur_dev->product_id; + /* _x52io_set_axis_range(ctx); */ + /* _x52io_set_report_parser(ctx); */ + + break; + + default: + break; + } + + cur_dev = cur_dev->next; + } + +finally: + hid_free_enumeration(devs); + return rc; +} diff --git a/lib/libx52io/libx52io.h b/lib/libx52io/libx52io.h new file mode 100644 index 0000000..e82219d --- /dev/null +++ b/lib/libx52io/libx52io.h @@ -0,0 +1,366 @@ +/* + * Saitek X52 IO driver + * + * Copyright (C) 2012-2020 Nirenjan Krishnan (nirenjan@nirenjan.org) + * + * SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0 + */ + +/** + * @file libx52io.h + * @brief Functions, structures and enumerations for the Saitek X52 IO driver + * library. + * + * This file contains the type, enum and function prototypes for the Saitek X52 + * IO driver library. These functions allow an application to connect to a + * supported X52/X52Pro joystick and read the state of the buttons and axes. + * + * @author Nirenjan Krishnan (nirenjan@nirenjan.org) + */ +#ifndef LIBX52IO_H +#define LIBX52IO_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup libx52io IO Library APIs + * + * These functions allow an application to connect to a supported X52/X52Pro + * joystick and read the state of the buttons and axes. + * + * @{ + */ + +/** + * @brief Opaque structure used by libx52io + */ +struct libx52io_context; + +/** + * @brief Device context structure used by libx52io + * + * All libx52io API functions require the application to pass in a pointer to + * a valid device context structure. A pointer can be obtained by calling + * \ref libx52io_init + */ +typedef struct libx52io_context libx52io_context; + +/** + * @brief libx52 IO error codes + * + * Error codes returned by libx52io + */ +typedef enum { + /** No error, indicates success */ + LIBX52IO_SUCCESS, + + /** Initialization failure */ + LIBX52IO_ERROR_INIT_FAILURE, + + /** No compatible device found */ + LIBX52IO_ERROR_NO_DEVICE, + + /** Invalid arguments for function */ + LIBX52IO_ERROR_INVALID, + + /** Connection error */ + LIBX52IO_ERROR_CONN, + + /** Read error from device */ + LIBX52IO_ERROR_IO, + + /** Other (unknown) error */ + LIBX52_ERROR_UNKNOWN +} libx52io_error_code; + +/** + * @brief X52 Axis definitions + */ +typedef enum { + /** Stick X axis */ + LIBX52IO_AXIS_X, + + /** Stick Y axis */ + LIBX52IO_AXIS_Y, + + /** Stick twist axis */ + LIBX52IO_AXIS_RZ, + + /** Throttle axis */ + LIBX52IO_AXIS_Z, + + /** Throttle Rotary X */ + LIBX52IO_AXIS_RX, + + /** Throttle Rotary Y */ + LIBX52IO_AXIS_RY, + + /** Throttle Slider */ + LIBX52IO_AXIS_SLIDER, + + /** Thumbstick X */ + LIBX52IO_AXIS_THUMBX, + + /** Thumbstick Y */ + LIBX52IO_AXIS_THUMBY, + + LIBX52IO_AXIS_MAX +} libx52io_axis; + +/** + * @brief X52 Button definitions + */ +typedef enum { + /** Primary trigger */ + LIBX52IO_BTN_TRIGGER, + + /** Secondary trigger */ + LIBX52IO_BTN_TRIGGER_2, + + /** Fire button */ + LIBX52IO_BTN_FIRE, + + /** Pinky trigger */ + LIBX52IO_BTN_PINKY, + + /** A button, on stick */ + LIBX52IO_BTN_A, + + /** B button, on stick */ + LIBX52IO_BTN_B, + + /** C button, on stick */ + LIBX52IO_BTN_C, + + /** D button, on throttle */ + LIBX52IO_BTN_D, + + /** E button, on throttle */ + LIBX52IO_BTN_E, + + /** Toggle 1 up */ + LIBX52IO_BTN_T1_UP, + + /** Toggle 1 down */ + LIBX52IO_BTN_T1_DN, + + /** Toggle 2 up */ + LIBX52IO_BTN_T2_UP, + + /** Toggle 2 down */ + LIBX52IO_BTN_T2_DN, + + /** Toggle 3 up */ + LIBX52IO_BTN_T3_UP, + + /** Toggle 3 down */ + LIBX52IO_BTN_T3_DN, + + /** POV 1 Up, on stick */ + LIBX52IO_BTN_POV_1_N, + + /** POV 1 Right, on stick */ + LIBX52IO_BTN_POV_1_E, + + /** POV 1 Down, on stick */ + LIBX52IO_BTN_POV_1_S, + + /** POV 1 Left, on stick */ + LIBX52IO_BTN_POV_1_W, + + /** POV 2 Up, on throttle */ + LIBX52IO_BTN_POV_2_N, + + /** POV 2 Right, on throttle */ + LIBX52IO_BTN_POV_2_E, + + /** POV 2 Down, on throttle */ + LIBX52IO_BTN_POV_2_S, + + /** POV 2 Left, on throttle */ + LIBX52IO_BTN_POV_2_W, + + /** Clutch button, on throttle */ + LIBX52IO_BTN_CLUTCH, + + /** Primary mouse button, next to thumbstick */ + LIBX52IO_BTN_MOUSE_PRIMARY, + + /** Secondary mouse button, press scroll wheel on throttle */ + LIBX52IO_BTN_MOUSE_SECONDARY, + + /** Scroll wheel up, on throttle */ + LIBX52IO_BTN_MOUSE_SCROLL_UP, + + /** Scroll wheel down, on throttle */ + LIBX52IO_BTN_MOUSE_SCROLL_DN, + + /** Function button */ + LIBX52IO_BTN_FUNCTION, + + /** Start/Stop button */ + LIBX52IO_BTN_START_STOP, + + /** Reset button */ + LIBX52IO_BTN_RESET, + + /** Page Up button, X52 Pro only */ + LIBX52IO_BTN_PG_UP, + + /** Page Down button, X52 Pro only */ + LIBX52IO_BTN_PG_DN, + + /** Up button, X52 Pro only */ + LIBX52IO_BTN_UP, + + /** Down button, X52 Pro only */ + LIBX52IO_BTN_DN, + + /** Select button, X52 Pro only */ + LIBX52IO_BTN_SELECT, + + LIBX52IO_BUTTON_MAX +} libx52io_button; + +/** + * @brief X52 HID Report + * + * This structure holds a parsed HID report + */ +struct libx52io_report { + /** Axis values */ + int32_t axis_value[LIBX52IO_AXIS_MAX]; + + /** Button values, true is pressed */ + bool button[LIBX52IO_BUTTON_MAX]; + + /** Current mode - 1, 2 or 3 */ + uint8_t mode; + + /** Hat position 0-8 */ + uint8_t hat; +}; + +/** + * @brief X52 HID Report + * + * This structure holds a parsed HID report + */ +typedef struct libx52io_report libx52io_report; + +/** + * @brief Initialize the IO library + * + * This function initializes the libx52io library, sets up any internal data + * structures to access the joystick, and returns a \ref libx52io_context + * pointer in the output parameter. All calls to libx52io use the returned + * pointer to control the device. + * + * @par Example + * @code + * int rc; + * libx52io_context *ctx; + * rc = libx52io_init(&ctx); + * if (rc != LIBX52IO_SUCCESS) { + * // Error handling omitted for brevity + * } + * + * // Save ctx for use later + * @endcode + * + * @param[out] ctx Pointer to a \ref libx52io_context *. This function will + * allocate a device context and return the pointer to the context in this variable. + * + * @returns \c libx52io_error_code indicating status + */ +int libx52io_init(libx52io_context **ctx); + +/** + * @brief Exit the library and free up any resources used + * + * This function releases any resources allocated by \ref libx52io_init and + * terminates the library. Using the freed device now is invalid and can + * cause errors. + * + * @param[in] ctx Pointer to the device context + * @returns None + */ +void libx52io_exit(libx52io_context *ctx); + +/** + * @brief Open a connection to a supported joystick + * + * This function scans for and opens a connection to a supported X52/X52Pro + * joystick. If no supported joystick is found, it will return \ref + * LIBX52IO_ERROR_NO_DEVICE. + * + * @param[in] ctx Pointer to the device context + * + * @returns + * - \ref LIBX52IO_SUCCESS on successful opening + * - \ref LIBX52IO_ERROR_INVALID if the context pointer is not valid + * - \ref LIBX52IO_ERROR_NO_DEVICE if no supported joystick is found + * - \ref LIBX52IO_ERROR_CONN if the connection fails + */ +int libx52io_open(libx52io_context *ctx); + +/** + * @brief Close an existing connection to a supported joystick + * + * This function closes any existing connection to a joystick. It is acceptable + * to call this function if no connection exists. + * + * @param[in] ctx Pointer to the device context + * + * @returns + * - \ref LIBX52IO_SUCCESS on closing, or if the connection is already closed. + * - \ref LIBX52IO_ERROR_INVALID if the context pointer is not valid + */ +int libx52io_close(libx52io_context *ctx); + +/** + * @brief Read and parse a HID report + * + * This function reads and parses a HID report from a connected joystick. This + * function will block until some data is available from the joystick. + * + * @param[in] ctx Pointer to the device context + * @param[out] report Pointer to save the decoded HID report + * + * @returns + * - \ref LIBX52IO_SUCCESS on read and parse success + * - \ref LIBX52IO_ERROR_INVALID if the context or report pointers are not valid + * - \ref LIBX52IO_ERROR_NO_DEVICE if the device is disconnected + */ +int libx52io_read(libx52io_context *ctx, libx52io_report *report); + +/** + * @brief Retrieve the range of an axis + * + * This saves the minimum and maximum values of the requested axis in the output + * parameters. This will only be valid if the device is connected. + * + * @param[in] ctx Pointer to the device context + * @param[in] axis Axis identifier - see \ref libx52io_axis + * @param[out] min Pointer to save the axis minimum value + * @param[out] max Pointer to save the axis maximum value + * + * @returns + * - \ref LIBX52IO_SUCCESS on read and parse success + * - \ref LIBX52IO_ERROR_INVALID if the context or output pointers are not + * valid, or the requested axis is not a valid axis identifier + * - \ref LIBX52IO_ERROR_NO_DEVICE if the device is disconnected + */ +int libx52io_get_axis_range(libx52io_context *ctx, libx52io_axis axis, int32_t *min, int32_t *max); +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif // !defined LIBX52IO_H