xid-sdl: Enable left/right force feedback with SDL2

This commit is contained in:
Darren 2019-02-09 15:41:41 -05:00 committed by mborgerson
parent 851e478319
commit 7fca76a2d9
1 changed files with 113 additions and 45 deletions

View File

@ -28,7 +28,6 @@
#include <SDL2/SDL.h>
//#define FORCE_FEEDBACK
#define UPDATE
//#define DEBUG_XID
@ -60,6 +59,11 @@ enum {
STR_SERIALNUMBER,
};
typedef enum HapticEmulationMode {
EMU_NONE,
EMU_HAPTIC_LEFT_RIGHT
} HapticEmulationMode;
static const USBDescStrings desc_strings = {
[STR_MANUFACTURER] = "QEMU",
[STR_PRODUCT] = "Microsoft Gamepad",
@ -105,11 +109,11 @@ typedef struct USBXIDState {
XIDGamepadOutputReport out_state_capabilities;
uint8_t device_index;
SDL_GameController *sdl_gamepad;
#ifdef FORCE_FEEDBACK
SDL_Haptic *sdl_haptic;
int sdl_haptic_effect_id;
#endif
HapticEmulationMode haptic_emulation_mode;
} USBXIDState;
static const USBDescIface desc_iface_xbox_gamepad = {
@ -194,36 +198,49 @@ static const XIDDesc desc_xid_xbox_gamepad = {
#define BUTTON_MASK(button) (1 << ((button) - GAMEPAD_DPAD_UP))
static void update_output(USBXIDState *s) {
#ifdef FORCE_FEEDBACK
if (s->sdl_haptic == NULL) { return; }
SDL_HapticLeftRight effect = {
.type = SDL_HAPTIC_LEFTRIGHT,
.length = SDL_HAPTIC_INFINITY,
/* FIXME: Might be left/right inverted */
.large_magnitude = s->out_state.right_actuator_strength,
.small_magnitude = s->out_state.left_actuator_strength
};
if (s->sdl_haptic_effect_id == -1) {
int effect_id = SDL_HapticNewEffect(s->sdl_haptic,
(SDL_HapticEffect*)&effect);
if (effect_id == -1) {
fprintf(stderr, "Failed to upload haptic effect!\n");
SDL_HapticClose(s->sdl_haptic);
s->sdl_haptic = NULL;
return;
}
SDL_HapticRunEffect(s->sdl_haptic, effect_id, 1);
s->sdl_haptic_effect_id = effect_id;
} else {
SDL_HapticUpdateEffect(s->sdl_haptic, s->sdl_haptic_effect_id,
(SDL_HapticEffect*)&effect);
static int haptic_effect_prepare(SDL_HapticEffect *effect,
HapticEmulationMode mode,
uint16_t large_magnitude,
uint16_t small_magnitude)
{
switch (mode) {
case EMU_HAPTIC_LEFT_RIGHT:
effect->leftright = (SDL_HapticLeftRight){
.type = SDL_HAPTIC_LEFTRIGHT,
.length = SDL_HAPTIC_INFINITY,
/* Xbox rumble returns in a range of 0x0000 -> 0xFFFF,
* SDL requires the range to be within 0x0000 -> 0x7FFF
*/
.large_magnitude = large_magnitude >> 1,
.small_magnitude = small_magnitude >> 1,
};
break;
default:
fprintf(stderr, "Haptic Emulation Mode Not Supported!\n");
assert(false);
}
#endif
return 0;
}
static void update_output(USBXIDState *s)
{
if (s->sdl_haptic == NULL) {
return;
}
SDL_HapticEffect effect;
int result = haptic_effect_prepare(&effect,
s->haptic_emulation_mode,
s->out_state.right_actuator_strength,
s->out_state.left_actuator_strength);
if (result < 0) {
fprintf(stderr, "Updating haptic effect failed!\n");
return;
}
SDL_HapticUpdateEffect(s->sdl_haptic, s->sdl_haptic_effect_id, &effect);
}
static void update_input(USBXIDState *s)
@ -440,11 +457,9 @@ static void usb_xid_handle_destroy(USBDevice *dev)
{
USBXIDState *s = DO_UPCAST(USBXIDState, dev, dev);
DPRINTF("xid handle_destroy\n");
#ifdef FORCE_FEEDBACK
if (s->sdl_haptic) {
SDL_HapticClose(s->sdl_haptic);
}
#endif
SDL_JoystickClose(s->sdl_gamepad);
}
#endif
@ -464,6 +479,66 @@ static void usb_xid_class_initfn(ObjectClass *klass, void *data)
uc->handle_attach = usb_desc_attach;
}
static HapticEmulationMode select_haptic_emulation_mode(SDL_Haptic *haptic)
{
unsigned int query_result = SDL_HapticQuery(haptic);
if (query_result & SDL_HAPTIC_LEFTRIGHT) {
return EMU_HAPTIC_LEFT_RIGHT;
}
fprintf(stderr, "No supported haptic mode found (SDL_HapticQuery result: 0x%x). Haptic disabled\n", query_result);
return EMU_NONE;
}
static int haptic_init(USBXIDState *s)
{
if (SDL_HapticOpened(s->device_index)) {
fprintf(stderr, "Joystick haptic effect already opened, haptic effect disabled\n");
return -1;
}
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(s->sdl_gamepad);
s->sdl_haptic = SDL_HapticOpenFromJoystick(joystick);
if (s->sdl_haptic == NULL) {
fprintf(stderr, "Joystick doesn't support haptic effects\n");
return -1;
}
s->haptic_emulation_mode = select_haptic_emulation_mode(s->sdl_haptic);
if (s->haptic_emulation_mode == EMU_NONE) {
SDL_HapticClose(s->sdl_haptic);
s->sdl_haptic = NULL;
fprintf(stderr, "Joystick doesn't support required haptic effects\n");
return -1;
}
SDL_HapticEffect effect;
haptic_effect_prepare(&effect, s->haptic_emulation_mode, 0, 0);
int effect_id = SDL_HapticNewEffect(s->sdl_haptic, &effect);
if (effect_id < 0) {
const char *error = SDL_GetError();
fprintf(stderr, "Failed to create haptic effect! SDL Error: %s\n", error);
SDL_HapticClose(s->sdl_haptic);
s->sdl_haptic = NULL;
return -1;
}
int run_result = SDL_HapticRunEffect(s->sdl_haptic, effect_id, 1);
if (run_result < 0) {
const char *error = SDL_GetError();
fprintf(stderr, "Failed to run haptic effect! SDL Error: %s\n", error);
SDL_HapticClose(s->sdl_haptic);
s->sdl_haptic = NULL;
return -1;
}
s->sdl_haptic_effect_id = effect_id;
return 0;
}
static void usb_xbox_gamepad_realize(USBDevice *dev, Error **errp)
{
USBXIDState *s = USB_XID(dev);
@ -500,7 +575,7 @@ static void usb_xbox_gamepad_realize(USBDevice *dev, Error **errp)
exit(1);
}
const char* name = SDL_GameControllerName(s->sdl_gamepad);
const char *name = SDL_GameControllerName(s->sdl_gamepad);
printf("Found game controller %d (%s)\n", s->device_index, name);
#ifndef UPDATE
@ -511,18 +586,11 @@ static void usb_xbox_gamepad_realize(USBDevice *dev, Error **errp)
SDL_JoystickEventState(SDL_ENABLE);
#endif
#ifdef FORCE_FEEDBACK
s->sdl_haptic = SDL_HapticOpenFromJoystick(s->sdl_gamepad);
if (s->sdl_haptic == NULL) {
fprintf(stderr, "Joystick doesn't support haptic\n");
if (!SDL_InitSubSystem(SDL_INIT_HAPTIC)) {
haptic_init(s);
} else {
if ((SDL_HapticQuery(s->sdl_haptic) & SDL_HAPTIC_LEFTRIGHT) == 0) {
fprintf(stderr, "Joystick doesn't support necessary haptic effect\n");
SDL_HapticClose(s->sdl_haptic);
s->sdl_haptic = NULL;
}
fprintf(stderr, "SDL failed to initialize haptic feedback subsystem\n");
}
#endif
}
static Property xid_sdl_properties[] = {