InputManager: Add ForceFeedbackDevice interface
This commit is contained in:
parent
d7d028ac5c
commit
f9c125c1a1
|
@ -1651,7 +1651,7 @@ void FullscreenUI::DrawInputBindingButton(SettingsInterface* bsi, InputBindingIn
|
|||
if (!visible)
|
||||
return;
|
||||
|
||||
if (oneline)
|
||||
if (oneline && type != InputBindingInfo::Type::Pointer && type != InputBindingInfo::Type::Device)
|
||||
InputManager::PrettifyInputBinding(value);
|
||||
|
||||
if (show_type)
|
||||
|
@ -1677,6 +1677,9 @@ void FullscreenUI::DrawInputBindingButton(SettingsInterface* bsi, InputBindingIn
|
|||
case InputBindingInfo::Type::Macro:
|
||||
title.format(ICON_FA_PIZZA_SLICE " {}", display_name);
|
||||
break;
|
||||
case InputBindingInfo::Type::Device:
|
||||
title.format(ICON_FA_GAMEPAD " {}", display_name);
|
||||
break;
|
||||
default:
|
||||
title = display_name;
|
||||
break;
|
||||
|
|
|
@ -17,6 +17,7 @@ struct InputBindingInfo
|
|||
Motor,
|
||||
Pointer, // Absolute pointer, does not receive any events, but is queryable.
|
||||
RelativePointer, // Receive relative mouse movement events, bind_index is offset by the axis.
|
||||
Device, // Used for special-purpose device selection, e.g. force feedback.
|
||||
Macro,
|
||||
};
|
||||
|
||||
|
|
|
@ -405,7 +405,8 @@ void ControllerBindingWidget::createBindingWidgets(QWidget* parent)
|
|||
for (const Controller::ControllerBindingInfo& bi : m_controller_info->bindings)
|
||||
{
|
||||
if (bi.type == InputBindingInfo::Type::Axis || bi.type == InputBindingInfo::Type::HalfAxis ||
|
||||
bi.type == InputBindingInfo::Type::Pointer || bi.type == InputBindingInfo::Type::RelativePointer)
|
||||
bi.type == InputBindingInfo::Type::Pointer || bi.type == InputBindingInfo::Type::RelativePointer ||
|
||||
bi.type == InputBindingInfo::Type::Device)
|
||||
{
|
||||
if (!axis_gbox)
|
||||
{
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "platform_misc.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/error.h"
|
||||
#include "common/log.h"
|
||||
#include "common/string_util.h"
|
||||
|
||||
|
@ -338,6 +339,11 @@ void DInputSource::UpdateMotorState(InputBindingKey large_key, InputBindingKey s
|
|||
// not supported
|
||||
}
|
||||
|
||||
bool DInputSource::ContainsDevice(std::string_view device) const
|
||||
{
|
||||
return device.starts_with("DInput-");
|
||||
}
|
||||
|
||||
std::optional<InputBindingKey> DInputSource::ParseKeyString(std::string_view device, std::string_view binding)
|
||||
{
|
||||
if (!device.starts_with("DInput-") || binding.empty())
|
||||
|
@ -444,6 +450,12 @@ TinyString DInputSource::ConvertKeyToIcon(InputBindingKey key)
|
|||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<ForceFeedbackDevice> DInputSource::CreateForceFeedbackDevice(std::string_view device, Error* error)
|
||||
{
|
||||
Error::SetStringView(error, "Not supported on this input source.");
|
||||
return {};
|
||||
}
|
||||
|
||||
void DInputSource::CheckForStateChanges(size_t index, const DIJOYSTATE& new_state)
|
||||
{
|
||||
ControllerData& cd = m_controllers[index];
|
||||
|
|
|
@ -46,10 +46,13 @@ public:
|
|||
void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
|
||||
float small_intensity) override;
|
||||
|
||||
bool ContainsDevice(std::string_view device) const override;
|
||||
std::optional<InputBindingKey> ParseKeyString(std::string_view device, std::string_view binding) override;
|
||||
TinyString ConvertKeyToString(InputBindingKey key) override;
|
||||
TinyString ConvertKeyToIcon(InputBindingKey key) override;
|
||||
|
||||
std::unique_ptr<ForceFeedbackDevice> CreateForceFeedbackDevice(std::string_view device, Error* error) override;
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
using ComPtr = Microsoft::WRL::ComPtr<T>;
|
||||
|
|
|
@ -2,17 +2,20 @@
|
|||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#include "input_manager.h"
|
||||
#include "imgui_manager.h"
|
||||
#include "input_source.h"
|
||||
|
||||
#include "core/controller.h"
|
||||
#include "core/host.h"
|
||||
#include "core/system.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/error.h"
|
||||
#include "common/file_system.h"
|
||||
#include "common/log.h"
|
||||
#include "common/path.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/timer.h"
|
||||
#include "core/controller.h"
|
||||
#include "core/host.h"
|
||||
#include "core/system.h"
|
||||
#include "imgui_manager.h"
|
||||
#include "input_source.h"
|
||||
|
||||
#include "IconsPromptFont.h"
|
||||
|
||||
|
@ -303,7 +306,8 @@ bool InputManager::ParseBindingAndGetSource(std::string_view binding, InputBindi
|
|||
|
||||
std::string InputManager::ConvertInputBindingKeyToString(InputBindingInfo::Type binding_type, InputBindingKey key)
|
||||
{
|
||||
if (binding_type == InputBindingInfo::Type::Pointer || binding_type == InputBindingInfo::Type::RelativePointer)
|
||||
if (binding_type == InputBindingInfo::Type::Pointer || binding_type == InputBindingInfo::Type::RelativePointer ||
|
||||
binding_type == InputBindingInfo::Type::Device)
|
||||
{
|
||||
// pointer and device bindings don't have a data part
|
||||
if (key.source_type == InputSourceType::Pointer)
|
||||
|
@ -356,7 +360,8 @@ std::string InputManager::ConvertInputBindingKeysToString(InputBindingInfo::Type
|
|||
const InputBindingKey* keys, size_t num_keys)
|
||||
{
|
||||
// can't have a chord of devices/pointers
|
||||
if (binding_type == InputBindingInfo::Type::Pointer || binding_type == InputBindingInfo::Type::Pointer)
|
||||
if (binding_type == InputBindingInfo::Type::Pointer || binding_type == InputBindingInfo::Type::RelativePointer ||
|
||||
binding_type == InputBindingInfo::Type::Device)
|
||||
{
|
||||
// so only take the first
|
||||
if (num_keys > 0)
|
||||
|
@ -888,6 +893,8 @@ void InputManager::AddPadBindings(const SettingsInterface& si, const std::string
|
|||
break;
|
||||
|
||||
case InputBindingInfo::Type::Pointer:
|
||||
case InputBindingInfo::Type::Device:
|
||||
// handled in device
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -1583,6 +1590,19 @@ void InputManager::OnInputDeviceDisconnected(InputBindingKey key, std::string_vi
|
|||
Host::OnInputDeviceDisconnected(key, identifier);
|
||||
}
|
||||
|
||||
std::unique_ptr<ForceFeedbackDevice> InputManager::CreateForceFeedbackDevice(const std::string_view device,
|
||||
Error* error)
|
||||
{
|
||||
for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++)
|
||||
{
|
||||
if (s_input_sources[i] && s_input_sources[i]->ContainsDevice(device))
|
||||
return s_input_sources[i]->CreateForceFeedbackDevice(device, error);
|
||||
}
|
||||
|
||||
Error::SetStringFmt(error, "No input source matched device '{}'", device);
|
||||
return {};
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Vibration
|
||||
// ------------------------------------------------------------------------
|
||||
|
@ -2104,3 +2124,7 @@ void InputManager::ReloadSources(const SettingsInterface& si, std::unique_lock<s
|
|||
|
||||
UpdatePointerCount();
|
||||
}
|
||||
|
||||
ForceFeedbackDevice::~ForceFeedbackDevice()
|
||||
{
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "core/input_types.h"
|
||||
#include "window_info.h"
|
||||
|
||||
class Error;
|
||||
class SmallStringBase;
|
||||
|
||||
/// Class, or source of an input event.
|
||||
|
@ -170,6 +171,22 @@ enum class InputPointerAxis : u8
|
|||
/// External input source class.
|
||||
class InputSource;
|
||||
|
||||
/// Force feedback interface.
|
||||
class ForceFeedbackDevice
|
||||
{
|
||||
public:
|
||||
enum class Effect
|
||||
{
|
||||
Constant,
|
||||
};
|
||||
|
||||
virtual ~ForceFeedbackDevice();
|
||||
|
||||
virtual void SetConstantForce(s32 level) = 0;
|
||||
|
||||
virtual void DisableForce(Effect force) = 0;
|
||||
};
|
||||
|
||||
namespace InputManager {
|
||||
/// Minimum interval between vibration updates when the effect is continuous.
|
||||
static constexpr double VIBRATION_UPDATE_INTERVAL_SECONDS = 0.5; // 500ms
|
||||
|
@ -360,6 +377,9 @@ void OnInputDeviceConnected(std::string_view identifier, std::string_view device
|
|||
|
||||
/// Called when an input device is disconnected.
|
||||
void OnInputDeviceDisconnected(InputBindingKey key, std::string_view identifier);
|
||||
|
||||
/// Creates a force feedback device interface for the specified source and device.
|
||||
std::unique_ptr<ForceFeedbackDevice> CreateForceFeedbackDevice(const std::string_view device, Error* error = nullptr);
|
||||
} // namespace InputManager
|
||||
|
||||
namespace Host {
|
||||
|
|
|
@ -14,8 +14,11 @@
|
|||
#include "common/types.h"
|
||||
#include "input_manager.h"
|
||||
|
||||
class Error;
|
||||
class SettingsInterface;
|
||||
|
||||
class ForceFeedbackDevice;
|
||||
|
||||
class InputSource
|
||||
{
|
||||
public:
|
||||
|
@ -29,6 +32,7 @@ public:
|
|||
|
||||
virtual void PollEvents() = 0;
|
||||
|
||||
virtual bool ContainsDevice(std::string_view device) const = 0;
|
||||
virtual std::optional<InputBindingKey> ParseKeyString(std::string_view device, std::string_view binding) = 0;
|
||||
virtual TinyString ConvertKeyToString(InputBindingKey key) = 0;
|
||||
virtual TinyString ConvertKeyToIcon(InputBindingKey key) = 0;
|
||||
|
@ -50,6 +54,9 @@ public:
|
|||
virtual void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
|
||||
float small_intensity);
|
||||
|
||||
/// Creates a force-feedback device from this source.
|
||||
virtual std::unique_ptr<ForceFeedbackDevice> CreateForceFeedbackDevice(std::string_view device, Error* error) = 0;
|
||||
|
||||
/// Creates a key for a generic controller axis event.
|
||||
static InputBindingKey MakeGenericControllerAxisKey(InputSourceType clazz, u32 controller_index, s32 axis_index);
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "common/assert.h"
|
||||
#include "common/bitutils.h"
|
||||
#include "common/error.h"
|
||||
#include "common/file_system.h"
|
||||
#include "common/log.h"
|
||||
#include "common/path.h"
|
||||
|
@ -360,6 +361,11 @@ std::vector<std::pair<std::string, std::string>> SDLInputSource::EnumerateDevice
|
|||
return ret;
|
||||
}
|
||||
|
||||
bool SDLInputSource::ContainsDevice(std::string_view device) const
|
||||
{
|
||||
return device.starts_with("SDL-");
|
||||
}
|
||||
|
||||
std::optional<InputBindingKey> SDLInputSource::ParseKeyString(std::string_view device, std::string_view binding)
|
||||
{
|
||||
if (!device.starts_with("SDL-") || binding.empty())
|
||||
|
@ -1092,3 +1098,126 @@ std::unique_ptr<InputSource> InputSource::CreateSDLSource()
|
|||
{
|
||||
return std::make_unique<SDLInputSource>();
|
||||
}
|
||||
|
||||
std::unique_ptr<ForceFeedbackDevice> SDLInputSource::CreateForceFeedbackDevice(std::string_view device, Error* error)
|
||||
{
|
||||
SDL_Joystick* joystick = GetJoystickForDevice(device);
|
||||
if (!joystick)
|
||||
{
|
||||
Error::SetStringFmt(error, "No SDL_Joystick for {}", device);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SDL_Haptic* haptic = SDL_HapticOpenFromJoystick(joystick);
|
||||
if (!haptic)
|
||||
{
|
||||
Error::SetStringFmt(error, "Haptic is not supported on {} ({})", device, SDL_JoystickName(joystick));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::unique_ptr<SDLForceFeedbackDevice>(new SDLForceFeedbackDevice(joystick, haptic));
|
||||
}
|
||||
|
||||
SDLForceFeedbackDevice::SDLForceFeedbackDevice(SDL_Joystick* joystick, SDL_Haptic* haptic) : m_haptic(haptic)
|
||||
{
|
||||
std::memset(&m_constant_effect, 0, sizeof(m_constant_effect));
|
||||
}
|
||||
|
||||
SDLForceFeedbackDevice::~SDLForceFeedbackDevice()
|
||||
{
|
||||
if (m_haptic)
|
||||
{
|
||||
DestroyEffects();
|
||||
|
||||
SDL_HapticClose(m_haptic);
|
||||
m_haptic = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void SDLForceFeedbackDevice::CreateEffects(SDL_Joystick* joystick)
|
||||
{
|
||||
constexpr u32 length = 10000; // 10 seconds since NFS games seem to not issue new commands while rotating.
|
||||
|
||||
const unsigned int supported = SDL_HapticQuery(m_haptic);
|
||||
if (supported & SDL_HAPTIC_CONSTANT)
|
||||
{
|
||||
m_constant_effect.type = SDL_HAPTIC_CONSTANT;
|
||||
m_constant_effect.constant.direction.type = SDL_HAPTIC_STEERING_AXIS;
|
||||
m_constant_effect.constant.length = length;
|
||||
|
||||
m_constant_effect_id = SDL_HapticNewEffect(m_haptic, &m_constant_effect);
|
||||
if (m_constant_effect_id < 0)
|
||||
ERROR_LOG("SDL_HapticNewEffect() for constant failed: {}", SDL_GetError());
|
||||
}
|
||||
else
|
||||
{
|
||||
WARNING_LOG("Constant effect is not supported on '{}'", SDL_JoystickName(joystick));
|
||||
}
|
||||
}
|
||||
|
||||
void SDLForceFeedbackDevice::DestroyEffects()
|
||||
{
|
||||
if (m_constant_effect_id >= 0)
|
||||
{
|
||||
if (m_constant_effect_running)
|
||||
{
|
||||
SDL_HapticStopEffect(m_haptic, m_constant_effect_id);
|
||||
m_constant_effect_running = false;
|
||||
}
|
||||
SDL_HapticDestroyEffect(m_haptic, m_constant_effect_id);
|
||||
m_constant_effect_id = -1;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
[[maybe_unused]] static u16 ClampU16(T val)
|
||||
{
|
||||
return static_cast<u16>(std::clamp<T>(val, 0, 65535));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
[[maybe_unused]] static u16 ClampS16(T val)
|
||||
{
|
||||
return static_cast<s16>(std::clamp<T>(val, -32768, 32767));
|
||||
}
|
||||
|
||||
void SDLForceFeedbackDevice::SetConstantForce(s32 level)
|
||||
{
|
||||
if (m_constant_effect_id < 0)
|
||||
return;
|
||||
|
||||
const s16 new_level = ClampS16(level);
|
||||
if (m_constant_effect.constant.level != new_level)
|
||||
{
|
||||
m_constant_effect.constant.level = new_level;
|
||||
if (SDL_HapticUpdateEffect(m_haptic, m_constant_effect_id, &m_constant_effect) != 0)
|
||||
ERROR_LOG("SDL_HapticUpdateEffect() for constant failed: {}", SDL_GetError());
|
||||
}
|
||||
|
||||
if (!m_constant_effect_running)
|
||||
{
|
||||
if (SDL_HapticRunEffect(m_haptic, m_constant_effect_id, SDL_HAPTIC_INFINITY) == 0)
|
||||
m_constant_effect_running = true;
|
||||
else
|
||||
ERROR_LOG("SDL_HapticRunEffect() for constant failed: {}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
void SDLForceFeedbackDevice::DisableForce(Effect force)
|
||||
{
|
||||
switch (force)
|
||||
{
|
||||
case Effect::Constant:
|
||||
{
|
||||
if (m_constant_effect_running)
|
||||
{
|
||||
SDL_HapticStopEffect(m_haptic, m_constant_effect_id);
|
||||
m_constant_effect_running = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,10 +34,13 @@ public:
|
|||
void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
|
||||
float small_intensity) override;
|
||||
|
||||
bool ContainsDevice(std::string_view device) const override;
|
||||
std::optional<InputBindingKey> ParseKeyString(std::string_view device, std::string_view binding) override;
|
||||
TinyString ConvertKeyToString(InputBindingKey key) override;
|
||||
TinyString ConvertKeyToIcon(InputBindingKey key) override;
|
||||
|
||||
std::unique_ptr<ForceFeedbackDevice> CreateForceFeedbackDevice(std::string_view device, Error* error) override;
|
||||
|
||||
bool ProcessSDLEvent(const SDL_Event* event);
|
||||
|
||||
SDL_Joystick* GetJoystickForDevice(std::string_view device);
|
||||
|
@ -103,3 +106,23 @@ private:
|
|||
bool m_enable_mfi_driver = false;
|
||||
#endif
|
||||
};
|
||||
|
||||
class SDLForceFeedbackDevice : public ForceFeedbackDevice
|
||||
{
|
||||
public:
|
||||
SDLForceFeedbackDevice(SDL_Joystick* joystick, SDL_Haptic* haptic);
|
||||
~SDLForceFeedbackDevice() override;
|
||||
|
||||
void SetConstantForce(s32 level) override;
|
||||
void DisableForce(Effect force) override;
|
||||
|
||||
private:
|
||||
void CreateEffects(SDL_Joystick* joystick);
|
||||
void DestroyEffects();
|
||||
|
||||
SDL_Haptic* m_haptic = nullptr;
|
||||
|
||||
SDL_HapticEffect m_constant_effect;
|
||||
int m_constant_effect_id = -1;
|
||||
bool m_constant_effect_running = false;
|
||||
};
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "input_manager.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/error.h"
|
||||
#include "common/log.h"
|
||||
#include "common/string_util.h"
|
||||
|
||||
|
@ -90,6 +91,11 @@ void Win32RawInputSource::UpdateMotorState(InputBindingKey large_key, InputBindi
|
|||
{
|
||||
}
|
||||
|
||||
bool Win32RawInputSource::ContainsDevice(std::string_view device) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<InputBindingKey> Win32RawInputSource::ParseKeyString(std::string_view device, std::string_view binding)
|
||||
{
|
||||
return std::nullopt;
|
||||
|
@ -105,6 +111,13 @@ TinyString Win32RawInputSource::ConvertKeyToIcon(InputBindingKey key)
|
|||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<ForceFeedbackDevice> Win32RawInputSource::CreateForceFeedbackDevice(std::string_view device,
|
||||
Error* error)
|
||||
{
|
||||
Error::SetStringView(error, "Not supported on this input source.");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<InputBindingKey> Win32RawInputSource::EnumerateMotors()
|
||||
{
|
||||
return {};
|
||||
|
|
|
@ -30,10 +30,13 @@ public:
|
|||
void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
|
||||
float small_intensity) override;
|
||||
|
||||
bool ContainsDevice(std::string_view device) const override;
|
||||
std::optional<InputBindingKey> ParseKeyString(std::string_view device, std::string_view binding) override;
|
||||
TinyString ConvertKeyToString(InputBindingKey key) override;
|
||||
TinyString ConvertKeyToIcon(InputBindingKey key) override;
|
||||
|
||||
std::unique_ptr<ForceFeedbackDevice> CreateForceFeedbackDevice(std::string_view device, Error* error) override;
|
||||
|
||||
private:
|
||||
struct MouseState
|
||||
{
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "input_manager.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/error.h"
|
||||
#include "common/log.h"
|
||||
#include "common/string_util.h"
|
||||
|
||||
|
@ -251,6 +252,11 @@ std::vector<std::pair<std::string, std::string>> XInputSource::EnumerateDevices(
|
|||
return ret;
|
||||
}
|
||||
|
||||
bool XInputSource::ContainsDevice(std::string_view device) const
|
||||
{
|
||||
return device.starts_with("XInput-");
|
||||
}
|
||||
|
||||
std::optional<InputBindingKey> XInputSource::ParseKeyString(std::string_view device, std::string_view binding)
|
||||
{
|
||||
if (!device.starts_with("XInput-") || binding.empty())
|
||||
|
@ -364,6 +370,12 @@ TinyString XInputSource::ConvertKeyToIcon(InputBindingKey key)
|
|||
return ret;
|
||||
}
|
||||
|
||||
std::unique_ptr<ForceFeedbackDevice> XInputSource::CreateForceFeedbackDevice(std::string_view device, Error* error)
|
||||
{
|
||||
Error::SetStringView(error, "Not supported on this input source.");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<InputBindingKey> XInputSource::EnumerateMotors()
|
||||
{
|
||||
std::vector<InputBindingKey> ret;
|
||||
|
|
|
@ -48,10 +48,13 @@ public:
|
|||
void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
|
||||
float small_intensity) override;
|
||||
|
||||
bool ContainsDevice(std::string_view device) const override;
|
||||
std::optional<InputBindingKey> ParseKeyString(std::string_view device, std::string_view binding) override;
|
||||
TinyString ConvertKeyToString(InputBindingKey key) override;
|
||||
TinyString ConvertKeyToIcon(InputBindingKey key) override;
|
||||
|
||||
std::unique_ptr<ForceFeedbackDevice> CreateForceFeedbackDevice(std::string_view device, Error* error) override;
|
||||
|
||||
private:
|
||||
struct ControllerData
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue