diff --git a/src/modules/filesystem/physfs/Filesystem.cpp b/src/modules/filesystem/physfs/Filesystem.cpp index 2db6fb023..e59218ae3 100644 --- a/src/modules/filesystem/physfs/Filesystem.cpp +++ b/src/modules/filesystem/physfs/Filesystem.cpp @@ -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 diff --git a/src/modules/joystick/Joystick.h b/src/modules/joystick/Joystick.h index 58fec07ce..860209276 100644 --- a/src/modules/joystick/Joystick.h +++ b/src/modules/joystick/Joystick.h @@ -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); diff --git a/src/modules/joystick/sdl/Joystick.cpp b/src/modules/joystick/sdl/Joystick.cpp index b45b19713..8d1d011e4 100644 --- a/src/modules/joystick/sdl/Joystick.cpp +++ b/src/modules/joystick/sdl/Joystick.cpp @@ -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 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); diff --git a/src/modules/joystick/sdl/Joystick.h b/src/modules/joystick/sdl/Joystick.h index a63a8595a..f6a6264b6 100644 --- a/src/modules/joystick/sdl/Joystick.h +++ b/src/modules/joystick/sdl/Joystick.h @@ -26,8 +26,7 @@ #include "common/EnumMap.h" // SDL -#include -#include +#include 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::Entry hatEntries[]; static EnumMap hats; diff --git a/src/modules/joystick/sdl/JoystickModule.cpp b/src/modules/joystick/sdl/JoystickModule.cpp index a5e7a1ea8..178f8692b 100644 --- a/src/modules/joystick/sdl/JoystickModule.cpp +++ b/src/modules/joystick/sdl/JoystickModule.cpp @@ -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); } diff --git a/src/modules/joystick/sdl/wrap_Joystick.cpp b/src/modules/joystick/sdl/wrap_Joystick.cpp index c851008d6..d7ecb0389 100644 --- a/src/modules/joystick/sdl/wrap_Joystick.cpp +++ b/src/modules/joystick/sdl/wrap_Joystick.cpp @@ -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 }, diff --git a/src/modules/joystick/sdl/wrap_Joystick.h b/src/modules/joystick/sdl/wrap_Joystick.h index 86f8c70b0..6b6c34b53 100644 --- a/src/modules/joystick/sdl/wrap_Joystick.h +++ b/src/modules/joystick/sdl/wrap_Joystick.h @@ -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 diff --git a/src/scripts/boot.lua b/src/scripts/boot.lua index f6e7d10c9..b20460dfa 100644 --- a/src/scripts/boot.lua +++ b/src/scripts/boot.lua @@ -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) diff --git a/src/scripts/boot.lua.h b/src/scripts/boot.lua.h index d196f796b..c20cd10f7 100644 --- a/src/scripts/boot.lua.h +++ b/src/scripts/boot.lua.h @@ -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,