Added joystick vibration support for most common controllers (resolves issue #159).

Added Joystick:setVibration(left, right), Joystick:getVibration, and Joystick:isVibrationSupported.
left and right are numbers between 0 and 1 which correspond to the strength of the gamepad's left and right rumble motors, if it has them.
This commit is contained in:
Alex Szpakowski
2013-10-05 01:54:41 -03:00
parent 0e6786988e
commit 8b7f151a4d
9 changed files with 279 additions and 5 deletions
+1 -1
View File
@@ -31,7 +31,7 @@ namespace
{
std::string getDriveRoot(const std::string &input)
{
for (int i = 0; i < input.size(); ++i)
for (size_t i = 0; i < input.size(); ++i)
if (input[i] == '/' || input[i] == '\\')
return input.substr(0, i+1);
// Something's horribly wrong
+5
View File
@@ -156,6 +156,11 @@ public:
virtual int getInstanceID() const = 0;
virtual int getID() const = 0;
virtual bool isVibrationSupported() = 0;
virtual bool setVibration(float left, float right) = 0;
virtual bool setVibration() = 0;
virtual void getVibration(float &left, float &right) const = 0;
static bool getConstant(const char *in, Hat &out);
static bool getConstant(Hat in, const char *&out);
+182
View File
@@ -18,7 +18,13 @@
* 3. This notice may not be removed or altered from any source distribution.
**/
// LOVE
#include "common/config.h"
#include "Joystick.h"
#include "common/int.h"
// C++
#include <algorithm>
namespace love
{
@@ -30,16 +36,20 @@ namespace sdl
Joystick::Joystick(int id)
: joyhandle(0)
, controller(0)
, haptic(0)
, instanceid(-1)
, id(id)
, vibration()
{
}
Joystick::Joystick(int id, int joyindex)
: joyhandle(0)
, controller(0)
, haptic(0)
, instanceid(-1)
, id(id)
, vibration()
{
open(joyindex);
}
@@ -84,6 +94,12 @@ bool Joystick::open(int deviceindex)
void Joystick::close()
{
if (haptic)
{
SDL_HapticRumbleStop(haptic);
SDL_HapticClose(haptic);
}
if (controller)
SDL_GameControllerClose(controller);
@@ -92,7 +108,9 @@ void Joystick::close()
joyhandle = 0;
controller = 0;
haptic = 0;
instanceid = -1;
vibration = Vibration();
}
bool Joystick::isConnected() const
@@ -255,6 +273,170 @@ int Joystick::getID() const
return id;
}
bool Joystick::checkCreateHaptic()
{
if (!isConnected())
return false;
if (!SDL_WasInit(SDL_INIT_HAPTIC) && SDL_InitSubSystem(SDL_INIT_HAPTIC) < 0)
return false;
if (haptic && SDL_HapticIndex(haptic) != -1)
return true;
if (haptic)
{
SDL_HapticClose(haptic);
haptic = 0;
}
haptic = SDL_HapticOpenFromJoystick(joyhandle);
vibration = Vibration();
return haptic != 0;
}
bool Joystick::isVibrationSupported()
{
if (!checkCreateHaptic())
return false;
unsigned int features = SDL_HapticQuery(haptic);
if ((features & SDL_HAPTIC_LEFTRIGHT) != 0)
return true;
// Some gamepad drivers only support left/right motors via a custom effect.
if (isGamepad() && (features & SDL_HAPTIC_CUSTOM) != 0)
return true;
// Check SDL's simple rumble as a last resort.
if (SDL_HapticRumbleSupported(haptic) == 1)
return true;
return false;
}
bool Joystick::runVibrationEffect()
{
if (vibration.id != -1)
{
if (SDL_HapticUpdateEffect(haptic, vibration.id, &vibration.effect) == 0)
{
if (SDL_HapticRunEffect(haptic, vibration.id, 1) != -1)
return true;
}
// If the effect fails to update, we should destroy and re-create it.
SDL_HapticDestroyEffect(haptic, vibration.id);
vibration.id = -1;
}
vibration.id = SDL_HapticNewEffect(haptic, &vibration.effect);
if (vibration.id != -1 && SDL_HapticRunEffect(haptic, vibration.id, 1) != -1)
return true;
return false;
}
bool Joystick::setVibration(float left, float right)
{
// TODO: support non-infinite durations? The working Tattiebogle Xbox
// controller driver in OS X seems to ignore durations under 1 second.
left = std::min(std::max(left, 0.0f), 1.0f);
right = std::min(std::max(right, 0.0f), 1.0f);
if (left == 0.0f && right == 0.0f)
return setVibration();
if (!checkCreateHaptic())
return false;
bool success = false;
unsigned int features = SDL_HapticQuery(haptic);
if ((features & SDL_HAPTIC_LEFTRIGHT) != 0)
{
memset(&vibration.effect, 0, sizeof(SDL_HapticEffect));
vibration.effect.type = SDL_HAPTIC_LEFTRIGHT;
// SDL's Xinput code has an int. overflow bug with SDL_HAPTIC_INFINITY.
const Uint32 long_time = 1000 * 60 * 60 * 2;
vibration.effect.leftright.length = long_time;
vibration.effect.leftright.large_magnitude = Uint16(left * LOVE_UINT16_MAX);
vibration.effect.leftright.small_magnitude = Uint16(right * LOVE_UINT16_MAX);
success = runVibrationEffect();
}
// Some gamepad drivers only give support for controlling the motors via
// a custom FF effect.
if (!success && isGamepad() && (features & SDL_HAPTIC_CUSTOM) != 0)
{
// NOTE: this may cause issues with drivers which support custom effects
// but aren't similar to https://github.com/d235j/360Controller .
// Custom effect data is clamped to 0x7FFF in SDL.
vibration.data[0] = vibration.data[2] = Uint16(left * 0x7FFF);
vibration.data[1] = vibration.data[3] = Uint16(right * 0x7FFF);
memset(&vibration.effect, 0, sizeof(SDL_HapticEffect));
vibration.effect.type = SDL_HAPTIC_CUSTOM;
vibration.effect.custom.length = SDL_HAPTIC_INFINITY;
vibration.effect.custom.channels = 2;
vibration.effect.custom.period = 10;
vibration.effect.custom.samples = 2;
vibration.effect.custom.data = vibration.data;
success = runVibrationEffect();
}
// Fall back to a simple rumble if all else fails. SDL's simple rumble API
// only supports a single strength value.
if (!success && SDL_HapticRumbleInit(haptic) == 0)
{
float strength = std::max(left, right);
int played = SDL_HapticRumblePlay(haptic, strength, SDL_HAPTIC_INFINITY);
success = (played == 0);
}
if (success)
{
vibration.left = left;
vibration.right = right;
}
return success;
}
bool Joystick::setVibration()
{
bool success = true;
if (SDL_WasInit(SDL_INIT_HAPTIC) && haptic && SDL_HapticIndex(haptic) != -1)
{
// Stop all playing effects on the haptic device.
// FIXME: We should only stop the vibration effect, in case we use the
// Haptic API for other things in the future.
success = (SDL_HapticStopAll(haptic) == 0);
}
if (success)
vibration.left = vibration.right = 0.0f;
return success;
}
void Joystick::getVibration(float &left, float &right) const
{
left = vibration.left;
right = vibration.right;
}
bool Joystick::getConstant(Uint8 in, Joystick::Hat &out)
{
return hats.find(in, out);
+23 -2
View File
@@ -26,8 +26,7 @@
#include "common/EnumMap.h"
// SDL
#include <SDL_joystick.h>
#include <SDL_gamecontroller.h>
#include <SDL.h>
namespace love
{
@@ -74,6 +73,11 @@ public:
int getInstanceID() const;
int getID() const;
bool isVibrationSupported();
bool setVibration(float left, float right);
bool setVibration();
void getVibration(float &left, float &right) const;
static bool getConstant(Hat in, Uint8 &out);
static bool getConstant(Uint8 in, Hat &out);
@@ -87,8 +91,12 @@ private:
Joystick() {}
bool checkCreateHaptic();
bool runVibrationEffect();
SDL_Joystick *joyhandle;
SDL_GameController *controller;
SDL_Haptic *haptic;
SDL_JoystickID instanceid;
std::string pguid;
@@ -96,6 +104,19 @@ private:
std::string name;
struct Vibration
{
float left, right;
SDL_HapticEffect effect;
Uint16 data[4];
int id;
Vibration()
: left(0.0f), right(0.0f), effect(), data(), id(-1)
{}
} vibration;
static EnumMap<Hat, Uint8, Joystick::HAT_MAX_ENUM>::Entry hatEntries[];
static EnumMap<Hat, Uint8, Joystick::HAT_MAX_ENUM> hats;
@@ -63,6 +63,9 @@ JoystickModule::~JoystickModule()
(*it)->release();
}
if (SDL_WasInit(SDL_INIT_HAPTIC) != 0)
SDL_QuitSubSystem(SDL_INIT_HAPTIC);
SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
}
@@ -186,6 +186,44 @@ int w_Joystick_isGamepadDown(lua_State *L)
return 1;
}
int w_Joystick_isVibrationSupported(lua_State *L)
{
Joystick *j = luax_checkjoystick(L, 1);
luax_pushboolean(L, j->isVibrationSupported());
return 1;
}
int w_Joystick_setVibration(lua_State *L)
{
Joystick *j = luax_checkjoystick(L, 1);
bool success = false;
if (lua_isnoneornil(L, 2))
{
// Disable joystick vibration if no argument is given.
success = j->setVibration();
}
else
{
float left = (float) luaL_checknumber(L, 2);
float right = (float) luaL_optnumber(L, 3, left);
success = j->setVibration(left, right);
}
luax_pushboolean(L, success);
return 1;
}
int w_Joystick_getVibration(lua_State *L)
{
Joystick *j = luax_checkjoystick(L, 1);
float left, right;
j->getVibration(left, right);
lua_pushnumber(L, left);
lua_pushnumber(L, right);
return 2;
}
// List of functions to wrap.
static const luaL_Reg functions[] =
{
@@ -200,10 +238,15 @@ static const luaL_Reg functions[] =
{ "getAxes", w_Joystick_getAxes },
{ "getHat", w_Joystick_getHat },
{ "isDown", w_Joystick_isDown },
{ "isGamepad", w_Joystick_isGamepad },
{ "getGamepadAxis", w_Joystick_getGamepadAxis },
{ "isGamepadDown", w_Joystick_isGamepadDown },
{ "isVibrationSupported", w_Joystick_isVibrationSupported },
{ "setVibration", w_Joystick_setVibration },
{ "getVibration", w_Joystick_getVibration },
// From wrap_JoystickModule.
{ "getConnectedIndex", w_getIndex },
{ "getGamepadMapping", w_getGamepadMapping },
+3
View File
@@ -48,6 +48,9 @@ int w_Joystick_isDown(lua_State *L);
int w_Joystick_isGamepad(lua_State *L);
int w_Joystick_getGamepadAxis(lua_State *L);
int w_Joystick_isGamepadDown(lua_State *L);
int w_Joystick_isVibrationSupported(lua_State *L);
int w_Joystick_setVibration(lua_State *L);
int w_Joystick_getVibration(lua_State *L);
extern "C" int luaopen_joystick(lua_State *L);
} // sdl
+7 -1
View File
@@ -1502,11 +1502,17 @@ function love.errhand(msg)
end
end
-- Load.
-- Reset state.
if love.mouse then
love.mouse.setVisible(true)
love.mouse.setGrabbed(false)
end
if love.joystick then
-- Stop all joystick vibrations.
for i,v in ipairs(love.joystick.getJoysticks()) do
v:setVibration()
end
end
if love.audio then love.audio.stop() end
love.graphics.reset()
love.graphics.setBackgroundColor(89, 157, 220)
+12 -1
View File
@@ -5169,7 +5169,7 @@ const unsigned char boot_lua[] =
0x09, 0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x0a,
0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
0x09, 0x65, 0x6e, 0x64, 0x0a,
0x09, 0x2d, 0x2d, 0x20, 0x4c, 0x6f, 0x61, 0x64, 0x2e, 0x0a,
0x09, 0x2d, 0x2d, 0x20, 0x52, 0x65, 0x73, 0x65, 0x74, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2e, 0x0a,
0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x6d, 0x6f, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65,
0x6e, 0x0a,
0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x6d, 0x6f, 0x75, 0x73, 0x65, 0x2e, 0x73, 0x65, 0x74, 0x56, 0x69,
@@ -5177,6 +5177,17 @@ const unsigned char boot_lua[] =
0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x6d, 0x6f, 0x75, 0x73, 0x65, 0x2e, 0x73, 0x65, 0x74, 0x47, 0x72,
0x61, 0x62, 0x62, 0x65, 0x64, 0x28, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x0a,
0x09, 0x65, 0x6e, 0x64, 0x0a,
0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x6a, 0x6f, 0x79, 0x73, 0x74, 0x69, 0x63, 0x6b, 0x20,
0x74, 0x68, 0x65, 0x6e, 0x0a,
0x09, 0x09, 0x2d, 0x2d, 0x20, 0x53, 0x74, 0x6f, 0x70, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x6a, 0x6f, 0x79, 0x73,
0x74, 0x69, 0x63, 0x6b, 0x20, 0x76, 0x69, 0x62, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x0a,
0x09, 0x09, 0x66, 0x6f, 0x72, 0x20, 0x69, 0x2c, 0x76, 0x20, 0x69, 0x6e, 0x20, 0x69, 0x70, 0x61, 0x69, 0x72,
0x73, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x6a, 0x6f, 0x79, 0x73, 0x74, 0x69, 0x63, 0x6b, 0x2e, 0x67, 0x65,
0x74, 0x4a, 0x6f, 0x79, 0x73, 0x74, 0x69, 0x63, 0x6b, 0x73, 0x28, 0x29, 0x29, 0x20, 0x64, 0x6f, 0x0a,
0x09, 0x09, 0x09, 0x76, 0x3a, 0x73, 0x65, 0x74, 0x56, 0x69, 0x62, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28,
0x29, 0x0a,
0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
0x09, 0x65, 0x6e, 0x64, 0x0a,
0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x20, 0x74, 0x68, 0x65,
0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x2e, 0x73, 0x74, 0x6f, 0x70, 0x28,
0x29, 0x20, 0x65, 0x6e, 0x64, 0x0a,