From 41ae3f04fd22d22c552742458344591061822884 Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Mon, 25 Jul 2022 12:35:05 +0200 Subject: m + add force feedback support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- libgamepad.h | 542 +++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 493 insertions(+), 49 deletions(-) (limited to 'libgamepad.h') diff --git a/libgamepad.h b/libgamepad.h index 627589a..665e012 100644 --- a/libgamepad.h +++ b/libgamepad.h @@ -2,11 +2,15 @@ #ifndef LIBGAMEPAD_H #define LIBGAMEPAD_H +#include +#include #include #include #include #include +#include #include +#include #include @@ -254,6 +258,21 @@ struct libgamepad_device { */ size_t nrelative_axes; + /** + * 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; + /** * Maps from button/key codes to button indices, * non-present buttons/keys map to -1, other @@ -276,20 +295,14 @@ struct libgamepad_device { int16_t relative_axis_map[REL_CNT]; /** - * Map from button/key indices to button/key codes + * Bitmap of supported force feedback effects */ - uint16_t *buttons; + uint8_t force_feedback_support[(FF_CNT + 7) / 8]; +}; - /** - * 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; -}; +/* FOR INTERNAL USE */ +void libgamepad_construct_force_feedback_effect__(struct ff_effect *, const struct ff_effect *, double, uint16_t, uint16_t); /** @@ -422,7 +435,7 @@ void libgamepad_close_device(struct libgamepad_device *); * 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 + * - ENODEV device has been unplugged or access has been revoked */ int libgamepad_next_event(struct libgamepad_device *, struct libgamepad_input_event *); @@ -434,9 +447,9 @@ int libgamepad_next_event(struct libgamepad_device *, struct libgamepad_input_ev * @return The button/key's name */ inline const char * -libgamepad_get_button_name(unsigned int code) +libgamepad_get_button_name(uint16_t code) { - return libevdev_event_code_get_name(EV_KEY, code); + return libevdev_event_code_get_name(EV_KEY, (unsigned int)code); } /** @@ -446,9 +459,9 @@ libgamepad_get_button_name(unsigned int code) * @return The axis' name */ inline const char * -libgamepad_get_absolute_axis_name(unsigned int code) +libgamepad_get_absolute_axis_name(uint16_t code) { - return libevdev_event_code_get_name(EV_ABS, code); + return libevdev_event_code_get_name(EV_ABS, (unsigned int)code); } /** @@ -458,9 +471,9 @@ libgamepad_get_absolute_axis_name(unsigned int code) * @return The axis' name */ inline const char * -libgamepad_get_relative_axis_name(unsigned int code) +libgamepad_get_relative_axis_name(uint16_t code) { - return libevdev_event_code_get_name(EV_REL, code); + return libevdev_event_code_get_name(EV_REL, (unsigned int)code); } /** @@ -472,9 +485,9 @@ libgamepad_get_relative_axis_name(unsigned int code) * 0 otherwise */ inline int -libgamepad_get_button_is_pressed(struct libgamepad_device *device, unsigned int code) +libgamepad_get_button_is_pressed(struct libgamepad_device *device, uint16_t code) { - return libevdev_get_event_value(device->dev, EV_KEY, code); + return libevdev_get_event_value(device->dev, EV_KEY, (unsigned int)code); } /** @@ -485,9 +498,9 @@ libgamepad_get_button_is_pressed(struct libgamepad_device *device, unsigned int * @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) +libgamepad_get_absolute_axis_info(struct libgamepad_device *device, uint16_t code) { - return libevdev_get_abs_info(device->dev, code); + return libevdev_get_abs_info(device->dev, (unsigned int)code); } @@ -502,23 +515,15 @@ libgamepad_get_absolute_axis_info(struct libgamepad_device *device, unsigned int * @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 + * via another open file descriptor (duplicates 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; + return ioctl(device->fd, EVIOCGRAB, (void *)1); } /** @@ -526,19 +531,11 @@ libgamepad_grab(struct libgamepad_device *device) * * @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; + return ioctl(device->fd, EVIOCGRAB, (void *)0); } @@ -548,18 +545,465 @@ libgamepad_ungrab(struct libgamepad_device *device) * @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; + return ioctl(device->fd, EVIOCSCLOCKID, &clockid); +} + + +/** + * Install a force feedback effect that can be played back later + * + * @param device The device to configure + * @param effect The effect to install; will be edited, specifically the ID + * will be set (need not be set before calling this function) + * @return 0 on success, -1 on failure + */ +inline int /* `struct ff_effect` is defined in */ +libgamepad_install_force_feedback_effect(struct libgamepad_device *device, struct ff_effect *effect) +{ + effect->id = -1; + return ioctl(device->fd, EVIOCSFF, effect); +} + +/** + * Reconfigure an already installed force feedback effect + * + * @param device The device to configure + * @param effect The effect to update, the ID must be set to the ID of a + * previously installed effect + * @return 0 on success, -1 on failure + */ +inline int +libgamepad_update_force_feedback_effect(struct libgamepad_device *device, struct ff_effect *effect) +{ + return ioctl(device->fd, EVIOCSFF, effect); +} + +/** + * Uninstall an installed force feedback effect + * + * @param device The device to configure + * @param effect The effect to remove, the ID must be set to the ID of a + * previously installed effect and will be reset + * @return 0 on success, -1 on failure + */ +inline int +libgamepad_uninstall_force_feedback_effect(struct libgamepad_device *device, struct ff_effect *effect) +{ + int ret = ioctl(device->fd, EVIOCRMFF, effect); + effect->id = -1; + return ret; +} + +/** + * Get the number of force feedback effects that can be played concurrently + * + * @param device The device to configure + * @return The number of maximum number concurrent effects, -1 on failure + */ +inline int +libgamepad_get_force_feedback_max_concurrency(struct libgamepad_device *device) +{ + int num; + return ioctl(device->fd, EVIOCGEFFECTS, &num) ? -1 : num; +} + +/** + * Play a force feedback effect + * + * @param device The device to play the force feedback effect on + * @param effect The force feedback effect to play, must already be installed + * with `libgamepad_install_force_feedback_effect` + * @return 0 on success, -1 on failure + */ +inline int +libgamepad_play_force_feedback_effect(struct libgamepad_device *device, const struct ff_effect *effect) +{ + struct input_event message; + message.type = EV_FF; + message.code = (uint16_t)effect->id; + message.value = 1; + return write(device->fd, &message, sizeof(message)) < 0 ? -1 : 0; +} + +/** + * Stop playing a force feedback effect + * + * @param device The device playing the force feedback effect on + * @param effect The force feedback effect to play, must already be installed + * with `libgamepad_install_force_feedback_effect` + * @return 0 on success, -1 on failure + */ +inline int +libgamepad_stop_force_feedback_effect(struct libgamepad_device *device, const struct ff_effect *effect) +{ + struct input_event message; + message.type = EV_FF; + message.code = (uint16_t)effect->id; + message.value = 0; + return write(device->fd, &message, sizeof(message)) < 0 ? -1 : 0; +} + +/** + * Check if a force feedback effect or waveform is supported + * + * @param device The device to check force feedback support on + * @param effect The force feedback effect or waveform + * @return 1 if the effect/waveform is supported, 0 otherwise + */ +inline int +libgamepad_is_force_feedback_effect_supported(struct libgamepad_device *device, uint16_t effect) +{ + return effect < sizeof(device->force_feedback_support) * 8 && + ((device->force_feedback_support[effect / 8] >> (effect % 8)) & 1); +} + +/** + * Set the force feedback master gain on a device + * + * Requires support for `FF_GAIN` (check with + * `libgamepad_is_force_feedback_effect_supported`) + * + * @param device The device to configure + * @param gain The master gain, shall be a value in [0, 0xFFFF] + * @return 0 on success, -1 on failure + */ +inline int +libgamepad_set_force_feedback_master_gain(struct libgamepad_device *device, uint16_t gain) +{ + struct input_event message; + message.type = EV_FF; + message.code = FF_GAIN; + message.value = (int16_t)gain; + return write(device->fd, &message, sizeof(message)) < 0 ? -1 : 0; +} + +/** + * Set the autocenter force feedback on a device + * + * Requires support for `FF_AUTOCENTER` (check with + * `libgamepad_is_force_feedback_effect_supported`) + * + * @param device The device to configure + * @param autocenter Autocenter strength, shall be a value in [0, 0xFFFF], select 0 to + * disable (you can also use `libgamepad_disable_force_feedback_autocenter`) + * @return 0 on success, -1 on failure + */ +inline int +libgamepad_set_force_feedback_autocenter(struct libgamepad_device *device, uint16_t autocenter) +{ + struct input_event message; + message.type = EV_FF; + message.code = FF_AUTOCENTER; + message.value = (int16_t)autocenter; + return write(device->fd, &message, sizeof(message)) < 0 ? -1 : 0; +} + +/** + * Disable the autocenter force feedback on a device + * + * Requires support for `FF_AUTOCENTER` (check with + * `libgamepad_is_force_feedback_effect_supported`) + * + * @param device device to configure + * @return 0 on success, -1 on failure + */ +inline int +libgamepad_disable_force_feedback_autocenter(struct libgamepad_device *device) +{ + struct input_event message; + message.type = EV_FF; + message.code = FF_AUTOCENTER; + message.value = 0; + return write(device->fd, &message, sizeof(message)) < 0 ? -1 : 0; +} + +/** + * Construct a rumble force-feedback effect + * + * Requires support for `FF_RUMBLE` (check with + * `libgamepad_is_force_feedback_effect_supported`) + * + * This function will set the following values to + * zero if `from` is `NULL`, see + * for more details: + * - effectp->trigger.button + * - effectp->trigger.interval + * - effectp->replay.delay + * + * @param effectp Output parameter for the effect + * @param from Effect to reconfigure, `NULL` to create a new effect + * @param direction The direction of the force feedback (if directional), where + * values are measure clockwise and both 0 and 1 are upwards + * @param length The number of milliseconds the effect is played + * @param effect Effect specific details: + * - .strong_magnitude Heavy motor strength, unsigned 16-bit integer + * - .weak_magnitude Light motor strength, unsigned 16-bit integer + */ +inline void +libgamepad_construct_rumble_force_feedback_effect(struct ff_effect *effectp, const struct ff_effect *from, + double direction, uint16_t length, const struct ff_rumble_effect *effect) +{ + libgamepad_construct_force_feedback_effect__(effectp, from, direction, length, FF_RUMBLE); + memcpy(&effectp->u.rumble, effect, sizeof(effectp->u.rumble)); +} + +/** + * Construct a constant force-feedback effect + * + * Requires support for `FF_CONSTANT` (check with + * `libgamepad_is_force_feedback_effect_supported`) + * + * This function will set the following values to + * zero if `from` is `NULL`, see + * for more details: + * - effectp->trigger.button + * - effectp->trigger.interval + * - effectp->replay.delay + * + * @param effectp Output parameter for the effect + * @param from Effect to reconfigure, `NULL` to create a new effect + * @param direction The direction of the force feedback (if directional), where + * values are measure clockwise and both 0 and 1 are upwards + * @param length The number of milliseconds the effect is played + * @param effect Effect specific details: + * - .level Strength, signed 16-bit integer + * - .envelope.attack_strength Strength at beginning, unsigned 16-bit integer + * - .envelope.fade_strength Strength at end, unsigned 16-bit integer + * - .envelope.attack_length The number of milliseconds the strength shall + * transition from `.envelope.attack_strength` + * to `.level`, unsigned 16-bit integer + * - .envelope.fade_length The number of milliseconds the strength shall + * transition to `.envelope.fade_strength` + * from `.level`, unsigned 16-bit integer + */ +inline void +libgamepad_construct_constant_force_feedback_effect(struct ff_effect *effectp, const struct ff_effect *from, + double direction, uint16_t length, const struct ff_constant_effect *effect) +{ + libgamepad_construct_force_feedback_effect__(effectp, from, direction, length, FF_CONSTANT); + memcpy(&effectp->u.constant, effect, sizeof(effectp->u.constant)); +} + +/** + * Construct a ramp force-feedback effect + * + * Requires support for `FF_RAMP` (check with + * `libgamepad_is_force_feedback_effect_supported`) + * + * This function will set the following values to + * zero if `from` is `NULL`, see + * for more details: + * - effectp->trigger.button + * - effectp->trigger.interval + * - effectp->replay.delay + * + * @param effectp Output parameter for the effect + * @param from Effect to reconfigure, `NULL` to create a new effect + * @param direction The direction of the force feedback (if directional), where + * values are measure clockwise and both 0 and 1 are upwards + * @param length The number of milliseconds the effect is played + * @param effect Effect specific details: + * - .start_level Strength at beginning, signed 16-bit integer + * - .end_level Strength at end, signed 16-bit integer + * - .envelope.attack_strength Strength at beginning, unsigned 16-bit integer + * - .envelope.fade_strength Strength at end, unsigned 16-bit integer + * - .envelope.attack_length The number of milliseconds the strength shall + * transition from `.envelope.attack_strength`, + * unsigned 16-bit integer + * - .envelope.fade_length The number of milliseconds the strength shall + * transition to `.envelope.fade_strength`, + * unsigned 16-bit integer + */ +inline void +libgamepad_construct_ramp_force_feedback_effect(struct ff_effect *effectp, const struct ff_effect *from, + double direction, uint16_t length, const struct ff_ramp_effect *effect) +{ + libgamepad_construct_force_feedback_effect__(effectp, from, direction, length, FF_RUMBLE); + memcpy(&effectp->u.ramp, effect, sizeof(effectp->u.ramp)); +} + +/** + * Construct a periodic force-feedback effect + * + * Requires support for `FF_PERIODIC` and the selected waveform + * (check with `libgamepad_is_force_feedback_effect_supported`) + * + * This function will set the following values to + * zero if `from` is `NULL`, see + * for more details: + * - effectp->trigger.button + * - effectp->trigger.interval + * - effectp->replay.delay + * + * @param effectp Output parameter for the effect + * @param from Effect to reconfigure, `NULL` to create a new effect + * @param direction The direction of the force feedback (if directional), where + * values are measure clockwise and both 0 and 1 are upwards + * @param length The number of milliseconds the effect is played + * @param effect Effect specific details: + * - .waveform `FF_SQUARE`, `FF_TRIANGLE`, `FF_SINE`, `FF_SAW_UP`, + * `FF_SAW_DOWN`, or `FF_CUSTOM` + * - .period The period of the wave, in milliseconds (unsigned 16-bit integer) + * - .magnitude The peak value, signed 16-bit integer + * - .offset The rough mean value, signed 16-bit integer + * - .phase Horiztonal wave-shift, in milliseconds (unsigned 16-bit integer) + * - .envelope.attack_strength Strength at beginning, unsigned 16-bit integer + * - .envelope.fade_strength Strength at end, unsigned 16-bit integer + * - .envelope.attack_length The number of milliseconds the strength shall + * transition from `.envelope.attack_strength`, + * unsigned 16-bit integer + * - .envelope.fade_length The number of milliseconds the strength shall + * transition to `.envelope.fade_strength`, + * unsigned 16-bit integer + * - .custom_len Set to zero unless you are using (`FF_CUSTOM`) + * - .custom_data Set to `NULL` unless you are using (`FF_CUSTOM`) + */ +inline void +libgamepad_construct_periodic_force_feedback_effect(struct ff_effect *effectp, const struct ff_effect *from, + double direction, uint16_t length, const struct ff_periodic_effect *effect) +{ + libgamepad_construct_force_feedback_effect__(effectp, from, direction, length, FF_PERIODIC); + memcpy(&effectp->u.periodic, effect, sizeof(effectp->u.periodic)); +} + +/** + * Construct a spring force-feedback effect + * + * Requires support for `FF_SPRING` (check with + * `libgamepad_is_force_feedback_effect_supported`) + * + * This function will set the following values to + * zero if `from` is `NULL`, see + * for more details: + * - effectp->trigger.button + * - effectp->trigger.interval + * - effectp->replay.delay + * + * @param effectp Output parameter for the effect + * @param from Effect to reconfigure, `NULL` to create a new effect + * @param direction The direction of the force feedback (if directional), where + * values are measure clockwise and both 0 and 1 are upwards + * @param length The number of milliseconds the effect is played + * @param effect Effect specific details (one of each axis): + * - .right_saturation Maximum level at end of axis, unsigned 16-bit integer + * - .left_saturation Maximum level at beginning of axis, unsigned 16-bit integer + * - .right_coeff Growth coefficient after the dead zone, signed 16-bit integer + * - .left_coeff Growth coefficient before the dead zone, signed 16-bit integer + * - .deadband Size of the dead zone + * - .center The position of the center of the dead zone + */ +inline void +libgamepad_construct_spring_force_feedback_effect(struct ff_effect *effectp, const struct ff_effect *from, + double direction, uint16_t length, const struct ff_condition_effect effect[2]) +{ + libgamepad_construct_force_feedback_effect__(effectp, from, direction, length, FF_SPRING); + memcpy(effectp->u.condition, effect, sizeof(effectp->u.condition)); +} + +/** + * Construct a friction force-feedback effect + * + * Requires support for `FF_FRICTION` (check with + * `libgamepad_is_force_feedback_effect_supported`) + * + * This function will set the following values to + * zero if `from` is `NULL`, see + * for more details: + * - effectp->trigger.button + * - effectp->trigger.interval + * - effectp->replay.delay + * + * @param effectp Output parameter for the effect + * @param from Effect to reconfigure, `NULL` to create a new effect + * @param direction The direction of the force feedback (if directional), where + * values are measure clockwise and both 0 and 1 are upwards + * @param length The number of milliseconds the effect is played + * @param effect Effect specific details (one of each axis): + * - .right_saturation Maximum level at end of axis, unsigned 16-bit integer + * - .left_saturation Maximum level at beginning of axis, unsigned 16-bit integer + * - .right_coeff Growth coefficient after the dead zone, signed 16-bit integer + * - .left_coeff Growth coefficient before the dead zone, signed 16-bit integer + * - .deadband Size of the dead zone + * - .center The position of the center of the dead zone + */ +inline void +libgamepad_construct_friction_force_feedback_effect(struct ff_effect *effectp, const struct ff_effect *from, + double direction, uint16_t length, const struct ff_condition_effect effect[2]) +{ + libgamepad_construct_force_feedback_effect__(effectp, from, direction, length, FF_FRICTION); + memcpy(effectp->u.condition, effect, sizeof(effectp->u.condition)); +} + +/** + * Construct a damper force-feedback effect + * + * Requires support for `FF_DAMPER` (check with + * `libgamepad_is_force_feedback_effect_supported`) + * + * This function will set the following values to + * zero if `from` is `NULL`, see + * for more details: + * - effectp->trigger.button + * - effectp->trigger.interval + * - effectp->replay.delay + * + * @param effectp Output parameter for the effect + * @param from Effect to reconfigure, `NULL` to create a new effect + * @param direction The direction of the force feedback (if directional), where + * values are measure clockwise and both 0 and 1 are upwards + * @param length The number of milliseconds the effect is played + * @param effect Effect specific details (one of each axis): + * - .right_saturation Maximum level at end of axis, unsigned 16-bit integer + * - .left_saturation Maximum level at beginning of axis, unsigned 16-bit integer + * - .right_coeff Growth coefficient after the dead zone, signed 16-bit integer + * - .left_coeff Growth coefficient before the dead zone, signed 16-bit integer + * - .deadband Size of the dead zone + * - .center The position of the center of the dead zone + */ +inline void +libgamepad_construct_damper_force_feedback_effect(struct ff_effect *effectp, const struct ff_effect *from, + double direction, uint16_t length, const struct ff_condition_effect effect[2]) +{ + libgamepad_construct_force_feedback_effect__(effectp, from, direction, length, FF_DAMPER); + memcpy(effectp->u.condition, effect, sizeof(effectp->u.condition)); +} + +/** + * Construct a inertia force-feedback effect + * + * Requires support for `FF_INERTIA` (check with + * `libgamepad_is_force_feedback_effect_supported`) + * + * This function will set the following values to + * zero if `from` is `NULL`, see + * for more details: + * - effectp->trigger.button + * - effectp->trigger.interval + * - effectp->replay.delay + * + * @param effectp Output parameter for the effect + * @param from Effect to reconfigure, `NULL` to create a new effect + * @param direction The direction of the force feedback (if directional), where + * values are measure clockwise and both 0 and 1 are upwards + * @param length The number of milliseconds the effect is played + * @param effect Effect specific details (one of each axis): + * - .right_saturation Maximum level at end of axis, unsigned 16-bit integer + * - .left_saturation Maximum level at beginning of axis, unsigned 16-bit integer + * - .right_coeff Growth coefficient after the dead zone, signed 16-bit integer + * - .left_coeff Growth coefficient before the dead zone, signed 16-bit integer + * - .deadband Size of the dead zone + * - .center The position of the center of the dead zone + */ +inline void +libgamepad_construct_inertia_force_feedback_effect(struct ff_effect *effectp, const struct ff_effect *from, + double direction, uint16_t length, const struct ff_condition_effect effect[2]) +{ + libgamepad_construct_force_feedback_effect__(effectp, from, direction, length, FF_INERTIA); + memcpy(effectp->u.condition, effect, sizeof(effectp->u.condition)); } -- cgit v1.2.3-70-g09d2