mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2026-09-10 12:27:01 -05:00
Implement friction effect and make code more OO.
This commit is contained in:
@@ -187,6 +187,28 @@ void FZeroAXDeluxe_IOAdapter::DoState(PointerWrap& p)
|
||||
p.Do(m_rx_reply);
|
||||
}
|
||||
|
||||
FZeroAXSteeringWheel::FZeroAXSteeringWheel()
|
||||
: m_config_changed_callback_id{CPUThreadConfigCallback::AddConfigChangedCallback(
|
||||
[this] { HandleConfigChange(); })},
|
||||
// TODO: Is this safe ?
|
||||
m_devices_changed_hook{
|
||||
g_controller_interface.RegisterDevicesChangedCallback([this] { HandleConfigChange(); })}
|
||||
{
|
||||
}
|
||||
|
||||
FZeroAXSteeringWheel::~FZeroAXSteeringWheel()
|
||||
{
|
||||
CPUThreadConfigCallback::RemoveConfigChangedCallback(m_config_changed_callback_id);
|
||||
}
|
||||
|
||||
void FZeroAXSteeringWheel::HandleConfigChange()
|
||||
{
|
||||
auto* const controller = Pad::GetConfig()->GetController(0);
|
||||
const auto wheel_device = g_controller_interface.FindDevice(controller->GetDefaultDevice());
|
||||
|
||||
m_spring_effect = wheel_device->CreateSpringEffect();
|
||||
}
|
||||
|
||||
void FZeroAXSteeringWheel::Update()
|
||||
{
|
||||
constexpr std::size_t REQUEST_SIZE = 4;
|
||||
@@ -242,19 +264,17 @@ void FZeroAXSteeringWheel::ProcessRequest(std::span<const u8> request)
|
||||
|
||||
INFO_LOG_FMT(SERIALINTERFACE_AMBB, "SteeringWheel: servo_position: {}", m_servo_position);
|
||||
|
||||
constexpr auto force_strength = 1.0;
|
||||
if (m_spring_effect != nullptr)
|
||||
{
|
||||
constexpr auto force_strength = 1.0;
|
||||
|
||||
// TODO: Is this a sensible calculation ?
|
||||
const auto center_position = ControllerEmu::MapToFloat<double>(s8(m_servo_position * 2), {});
|
||||
// TODO: Is this a sensible calculation ?
|
||||
const auto center_position = ControllerEmu::MapToFloat<double>(s8(m_servo_position * 2), {});
|
||||
|
||||
// TODO: Acquire this object in the constructor and update it on config change !
|
||||
// TODO: Also turn off the force appropriately on shutdown !
|
||||
auto* const controller = Pad::GetConfig()->GetController(0);
|
||||
const auto wheel_device = g_controller_interface.FindDevice(controller->GetDefaultDevice());
|
||||
// TODO: Should we also set a friction force ?
|
||||
|
||||
// TODO: Should we also set a friction force ?
|
||||
|
||||
wheel_device->SetCenteringForce(force_strength, center_position);
|
||||
m_spring_effect->SetForce(force_strength, center_position);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,16 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Common/HookableEvent.h"
|
||||
#include "Core/CPUThreadConfigCallback.h"
|
||||
#include "Core/HW/Triforce/IOPorts.h"
|
||||
#include "Core/HW/Triforce/SerialDevice.h"
|
||||
|
||||
namespace ciface::Core
|
||||
{
|
||||
class SpringEffect;
|
||||
}
|
||||
|
||||
namespace Triforce
|
||||
{
|
||||
|
||||
@@ -13,6 +20,9 @@ namespace Triforce
|
||||
class FZeroAXSteeringWheel final : public SerialDevice
|
||||
{
|
||||
public:
|
||||
FZeroAXSteeringWheel();
|
||||
~FZeroAXSteeringWheel() override;
|
||||
|
||||
void Update() override;
|
||||
|
||||
bool IsInitializing() const;
|
||||
@@ -22,11 +32,18 @@ public:
|
||||
void DoState(PointerWrap&) override;
|
||||
|
||||
private:
|
||||
void HandleConfigChange();
|
||||
|
||||
void ProcessRequest(std::span<const u8>);
|
||||
|
||||
u8 m_init_state = 0;
|
||||
|
||||
s8 m_servo_position = 0;
|
||||
|
||||
std::unique_ptr<ciface::Core::SpringEffect> m_spring_effect;
|
||||
|
||||
const CPUThreadConfigCallback::ConfigChangedCallbackID m_config_changed_callback_id;
|
||||
const Common::EventHook m_devices_changed_hook;
|
||||
};
|
||||
|
||||
// Used for both FZeroAX and FZeroAXMonster.
|
||||
|
||||
@@ -80,6 +80,28 @@ void MarioKartGPCommon_IOAdapter::HandleGenericOutputsChanged(std::span<const u8
|
||||
}
|
||||
}
|
||||
|
||||
MarioKartGPSteeringWheel::MarioKartGPSteeringWheel()
|
||||
: m_config_changed_callback_id{CPUThreadConfigCallback::AddConfigChangedCallback(
|
||||
[this] { HandleConfigChange(); })},
|
||||
// TODO: Is this safe ?
|
||||
m_devices_changed_hook{
|
||||
g_controller_interface.RegisterDevicesChangedCallback([this] { HandleConfigChange(); })}
|
||||
{
|
||||
}
|
||||
MarioKartGPSteeringWheel::~MarioKartGPSteeringWheel()
|
||||
{
|
||||
CPUThreadConfigCallback::RemoveConfigChangedCallback(m_config_changed_callback_id);
|
||||
}
|
||||
|
||||
void MarioKartGPSteeringWheel::HandleConfigChange()
|
||||
{
|
||||
auto* const controller = Pad::GetConfig()->GetController(0);
|
||||
const auto wheel_device = g_controller_interface.FindDevice(controller->GetDefaultDevice());
|
||||
|
||||
m_spring_effect = wheel_device->CreateSpringEffect();
|
||||
m_friction_effect = wheel_device->CreateFrictionEffect();
|
||||
}
|
||||
|
||||
void MarioKartGPSteeringWheel::Update()
|
||||
{
|
||||
constexpr std::size_t REQUEST_SIZE = 10;
|
||||
@@ -123,18 +145,23 @@ void MarioKartGPSteeringWheel::ProcessRequest(std::span<const u8> request)
|
||||
const bool should_set_force = centering_force != 0;
|
||||
if (should_set_force)
|
||||
{
|
||||
// TODO: Are these sensible calculations ?
|
||||
const auto force_strength = ControllerEmu::MapToFloat<double>(s16(centering_force), {});
|
||||
const auto center_position = ControllerEmu::MapToFloat<double>(s16(roll), {});
|
||||
if (m_spring_effect != nullptr)
|
||||
{
|
||||
// TODO: Are these sensible calculations ?
|
||||
const auto force_strength = ControllerEmu::MapToFloat<double>(s16(centering_force), {});
|
||||
const auto center_position = 0.0 - ControllerEmu::MapToFloat<double>(s16(roll), {});
|
||||
|
||||
// TODO: Acquire this object in the constructor and update it on config change !
|
||||
// TODO: Also turn off the force appropriately on shutdown !
|
||||
auto* const controller = Pad::GetConfig()->GetController(0);
|
||||
const auto wheel_device = g_controller_interface.FindDevice(controller->GetDefaultDevice());
|
||||
m_spring_effect->SetForce(force_strength, center_position);
|
||||
}
|
||||
|
||||
wheel_device->SetCenteringForce(force_strength, center_position);
|
||||
if (m_friction_effect != nullptr)
|
||||
{
|
||||
// TODO: sensible calculation ?
|
||||
const auto force_strength = ControllerEmu::MapToFloat<double>(s16(friction_force), {});
|
||||
|
||||
m_friction_effect->SetForce(force_strength);
|
||||
}
|
||||
}
|
||||
|
||||
switch (m_init_state)
|
||||
{
|
||||
case 0:
|
||||
|
||||
@@ -3,13 +3,16 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Common/HookableEvent.h"
|
||||
#include "Core/CPUThreadConfigCallback.h"
|
||||
#include "Core/HW/Triforce/IOPorts.h"
|
||||
#include "Core/HW/Triforce/SerialDevice.h"
|
||||
|
||||
namespace ciface::Core
|
||||
{
|
||||
class Device;
|
||||
}
|
||||
class SpringEffect;
|
||||
class FrictionEffect;
|
||||
} // namespace ciface::Core
|
||||
|
||||
namespace Triforce
|
||||
{
|
||||
@@ -18,6 +21,9 @@ namespace Triforce
|
||||
class MarioKartGPSteeringWheel final : public SerialDevice
|
||||
{
|
||||
public:
|
||||
MarioKartGPSteeringWheel();
|
||||
~MarioKartGPSteeringWheel() override;
|
||||
|
||||
void Update() override;
|
||||
|
||||
void Reset();
|
||||
@@ -25,9 +31,17 @@ public:
|
||||
void DoState(PointerWrap&) override;
|
||||
|
||||
private:
|
||||
void HandleConfigChange();
|
||||
|
||||
void ProcessRequest(std::span<const u8>);
|
||||
|
||||
u8 m_init_state = 0;
|
||||
|
||||
std::unique_ptr<ciface::Core::SpringEffect> m_spring_effect;
|
||||
std::unique_ptr<ciface::Core::FrictionEffect> m_friction_effect;
|
||||
|
||||
const CPUThreadConfigCallback::ConfigChangedCallbackID m_config_changed_callback_id;
|
||||
const Common::EventHook m_devices_changed_hook;
|
||||
};
|
||||
|
||||
// Used for both MarioKartGP and MarioKartGP2.
|
||||
|
||||
@@ -121,8 +121,14 @@ Device::Output* Device::FindOutput(std::string_view name) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Device::SetCenteringForce(double gain, double center_position)
|
||||
std::unique_ptr<SpringEffect> Device::CreateSpringEffect()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<FrictionEffect> Device::CreateFrictionEffect()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Device::Control::IsMatchingName(std::string_view name) const
|
||||
@@ -508,4 +514,10 @@ auto InputDetector::TakeResults() -> Results
|
||||
return std::move(m_detections);
|
||||
}
|
||||
|
||||
SpringEffect::SpringEffect() = default;
|
||||
SpringEffect::~SpringEffect() = default;
|
||||
|
||||
FrictionEffect::FrictionEffect() = default;
|
||||
FrictionEffect::~FrictionEffect() = default;
|
||||
|
||||
} // namespace ciface::Core
|
||||
|
||||
@@ -44,6 +44,35 @@ enum class DeviceRemoval
|
||||
Keep,
|
||||
};
|
||||
|
||||
// A "spring" force feedback effect used for wheel centering.
|
||||
class SpringEffect
|
||||
{
|
||||
public:
|
||||
SpringEffect();
|
||||
virtual ~SpringEffect();
|
||||
|
||||
SpringEffect(const SpringEffect&) = delete;
|
||||
SpringEffect(SpringEffect&&) = delete;
|
||||
SpringEffect& operator=(const SpringEffect&) = delete;
|
||||
SpringEffect& operator=(SpringEffect&&) = delete;
|
||||
|
||||
virtual void SetForce(double gain, double center_position) = 0;
|
||||
};
|
||||
|
||||
class FrictionEffect
|
||||
{
|
||||
public:
|
||||
FrictionEffect();
|
||||
virtual ~FrictionEffect();
|
||||
|
||||
FrictionEffect(const FrictionEffect&) = delete;
|
||||
FrictionEffect(FrictionEffect&&) = delete;
|
||||
FrictionEffect& operator=(const FrictionEffect&) = delete;
|
||||
FrictionEffect& operator=(FrictionEffect&&) = delete;
|
||||
|
||||
virtual void SetForce(double gain) = 0;
|
||||
};
|
||||
|
||||
class Device
|
||||
{
|
||||
public:
|
||||
@@ -160,7 +189,8 @@ public:
|
||||
Input* FindInput(std::string_view name) const;
|
||||
Output* FindOutput(std::string_view name) const;
|
||||
|
||||
virtual void SetCenteringForce(double gain, double center_position);
|
||||
virtual std::unique_ptr<SpringEffect> CreateSpringEffect();
|
||||
virtual std::unique_ptr<FrictionEffect> CreateFrictionEffect();
|
||||
|
||||
protected:
|
||||
void AddInput(Input* const i);
|
||||
|
||||
@@ -221,9 +221,6 @@ Gamepad::Gamepad(SDL_Gamepad* const gamepad, SDL_Joystick* const joystick)
|
||||
AddOutput(new LeftRightEffect(m_haptic, LeftRightEffect::Motor::Strong));
|
||||
AddOutput(new LeftRightEffect(m_haptic, LeftRightEffect::Motor::Weak));
|
||||
}
|
||||
|
||||
// TODO: Remove test hacks
|
||||
// SetCenteringForce(0.8, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,15 +273,27 @@ SDL_JoystickID Gamepad::GetSDLInstanceID() const
|
||||
return SDL_GetJoystickID(m_joystick);
|
||||
}
|
||||
|
||||
void Gamepad::SetCenteringForce(double gain, double center_position)
|
||||
class SpringEffect final : public Core::SpringEffect
|
||||
{
|
||||
INFO_LOG_FMT(CONTROLLERINTERFACE, "SetCenteringForce: {:.2} {:.2}", gain, center_position);
|
||||
public:
|
||||
explicit SpringEffect(SDL_Haptic* haptic) : m_haptic{haptic} {}
|
||||
|
||||
if (m_haptic == nullptr)
|
||||
return;
|
||||
~SpringEffect() override { SetForce(0.0, 0.0); }
|
||||
|
||||
if (gain != 0.0)
|
||||
void SetForce(double gain, double center_position) override
|
||||
{
|
||||
INFO_LOG_FMT(CONTROLLERINTERFACE, "SpringEffect: {:.2} {:.2}", gain, center_position);
|
||||
|
||||
if (gain == 0.0)
|
||||
{
|
||||
if (m_effect_id != -1)
|
||||
{
|
||||
SDL_DestroyHapticEffect(m_haptic, std::exchange(m_effect_id, -1));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_HapticEffect effect{
|
||||
.type = SDL_HAPTIC_SPRING,
|
||||
};
|
||||
@@ -293,9 +302,8 @@ void Gamepad::SetCenteringForce(double gain, double center_position)
|
||||
|
||||
// TODO: Why does only SDL_HAPTIC_POLAR do the correct thing for my Sidewinder joystick?
|
||||
// I would expect SDL_HAPTIC_STEERING_AXIS to be more reliable..
|
||||
condition.direction.type = SDL_HAPTIC_POLAR;
|
||||
// condition.direction.type = SDL_HAPTIC_STEERING_AXIS;
|
||||
// condition.direction.dir[0] = 1;
|
||||
// condition.direction.type = SDL_HAPTIC_POLAR;
|
||||
condition.direction.type = SDL_HAPTIC_STEERING_AXIS;
|
||||
|
||||
// Is "infinity" always supported ?
|
||||
condition.length = SDL_HAPTIC_INFINITY;
|
||||
@@ -311,30 +319,116 @@ void Gamepad::SetCenteringForce(double gain, double center_position)
|
||||
|
||||
condition.center[0] = ControllerEmu::MapFloat<s16>(center_position, 0);
|
||||
|
||||
if (m_centering_haptic_effect == -1)
|
||||
if (m_effect_id == -1)
|
||||
{
|
||||
// Create and start a new effect.
|
||||
m_centering_haptic_effect = SDL_CreateHapticEffect(m_haptic, &effect);
|
||||
if (m_centering_haptic_effect == -1)
|
||||
m_effect_id = SDL_CreateHapticEffect(m_haptic, &effect);
|
||||
if (m_effect_id == -1)
|
||||
{
|
||||
ERROR_LOG_FMT(CONTROLLERINTERFACE, "SDL_CreateHapticEffect: {}", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SDL_RunHapticEffect(m_haptic, m_centering_haptic_effect, 1))
|
||||
if (!SDL_RunHapticEffect(m_haptic, m_effect_id, 1))
|
||||
ERROR_LOG_FMT(CONTROLLERINTERFACE, "SDL_RunHapticEffect: {}", SDL_GetError());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update an already running effect.
|
||||
if (!SDL_UpdateHapticEffect(m_haptic, m_centering_haptic_effect, &effect))
|
||||
if (!SDL_UpdateHapticEffect(m_haptic, m_effect_id, &effect))
|
||||
ERROR_LOG_FMT(CONTROLLERINTERFACE, "SDL_UpdateHapticEffect: {}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
else if (m_centering_haptic_effect != -1)
|
||||
|
||||
private:
|
||||
SDL_Haptic* const m_haptic;
|
||||
int m_effect_id = -1;
|
||||
};
|
||||
|
||||
class FrictionEffect final : public Core::FrictionEffect
|
||||
{
|
||||
public:
|
||||
explicit FrictionEffect(SDL_Haptic* haptic) : m_haptic{haptic} {}
|
||||
|
||||
~FrictionEffect() override { SetForce(0.0); }
|
||||
|
||||
void SetForce(double gain) override
|
||||
{
|
||||
SDL_DestroyHapticEffect(m_haptic, std::exchange(m_centering_haptic_effect, -1));
|
||||
INFO_LOG_FMT(CONTROLLERINTERFACE, "FrictionEffect: {:.2}", gain);
|
||||
|
||||
if (gain == 0.0)
|
||||
{
|
||||
if (m_effect_id != -1)
|
||||
{
|
||||
SDL_DestroyHapticEffect(m_haptic, std::exchange(m_effect_id, -1));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_HapticEffect effect{
|
||||
.type = SDL_HAPTIC_FRICTION,
|
||||
};
|
||||
|
||||
auto& condition = effect.condition;
|
||||
|
||||
// TODO: Why does only SDL_HAPTIC_POLAR do the correct thing for my Sidewinder joystick?
|
||||
// I would expect SDL_HAPTIC_STEERING_AXIS to be more reliable..
|
||||
// condition.direction.type = SDL_HAPTIC_POLAR;
|
||||
condition.direction.type = SDL_HAPTIC_STEERING_AXIS;
|
||||
|
||||
// Is "infinity" always supported ?
|
||||
condition.length = SDL_HAPTIC_INFINITY;
|
||||
|
||||
const auto unsigned_gain = ControllerEmu::MapFloat<u16>(gain, 0);
|
||||
condition.right_sat[0] = unsigned_gain;
|
||||
condition.left_sat[0] = unsigned_gain;
|
||||
|
||||
// TODO: Is this a sensible coeff value ?
|
||||
constexpr auto coeff = std::numeric_limits<s16>::max();
|
||||
condition.right_coeff[0] = coeff;
|
||||
condition.left_coeff[0] = coeff;
|
||||
|
||||
if (m_effect_id == -1)
|
||||
{
|
||||
// Create and start a new effect.
|
||||
m_effect_id = SDL_CreateHapticEffect(m_haptic, &effect);
|
||||
if (m_effect_id == -1)
|
||||
{
|
||||
ERROR_LOG_FMT(CONTROLLERINTERFACE, "SDL_CreateHapticEffect: {}", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SDL_RunHapticEffect(m_haptic, m_effect_id, 1))
|
||||
ERROR_LOG_FMT(CONTROLLERINTERFACE, "SDL_RunHapticEffect: {}", SDL_GetError());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update an already running effect.
|
||||
if (!SDL_UpdateHapticEffect(m_haptic, m_effect_id, &effect))
|
||||
ERROR_LOG_FMT(CONTROLLERINTERFACE, "SDL_UpdateHapticEffect: {}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
SDL_Haptic* const m_haptic;
|
||||
int m_effect_id = -1;
|
||||
};
|
||||
|
||||
std::unique_ptr<Core::SpringEffect> Gamepad::CreateSpringEffect()
|
||||
{
|
||||
if (m_haptic == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return std::make_unique<SpringEffect>(m_haptic);
|
||||
}
|
||||
|
||||
std::unique_ptr<Core::FrictionEffect> Gamepad::CreateFrictionEffect()
|
||||
{
|
||||
if (m_haptic == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return std::make_unique<FrictionEffect>(m_haptic);
|
||||
}
|
||||
|
||||
std::string Gamepad::Button::GetName() const
|
||||
|
||||
@@ -342,7 +342,8 @@ public:
|
||||
return Core::DeviceRemoval::Keep;
|
||||
}
|
||||
|
||||
void SetCenteringForce(double gain, double center_position) override;
|
||||
std::unique_ptr<Core::SpringEffect> CreateSpringEffect() override;
|
||||
std::unique_ptr<Core::FrictionEffect> CreateFrictionEffect() override;
|
||||
|
||||
private:
|
||||
void UpdateRumble()
|
||||
@@ -371,8 +372,6 @@ private:
|
||||
float m_touchpad_x = 0.f;
|
||||
float m_touchpad_y = 0.f;
|
||||
float m_touchpad_pressure = 0.f;
|
||||
|
||||
int m_centering_haptic_effect = -1;
|
||||
};
|
||||
|
||||
struct SDLMotionAxis
|
||||
|
||||
@@ -589,7 +589,7 @@ bool evdevDevice::AddNode(std::string devnode, int fd, libevdev* dev)
|
||||
|
||||
static_cast<void>(!write(fd, &ie, sizeof(ie)));
|
||||
|
||||
m_ffb_wheel_fd = fd;
|
||||
m_ffb_fd = fd;
|
||||
}
|
||||
|
||||
// Constant FF effect
|
||||
@@ -729,18 +729,36 @@ bool evdevDevice::IsValid() const
|
||||
return true;
|
||||
}
|
||||
|
||||
void evdevDevice::SetCenteringForce(double gain, double center_position)
|
||||
class SpringEffect final : public Core::SpringEffect
|
||||
{
|
||||
INFO_LOG_FMT(CONTROLLERINTERFACE, "SetCenteringForce: {:.2} {:.2}", gain, center_position);
|
||||
public:
|
||||
explicit SpringEffect(int ffb_fd) : m_ffb_fd{ffb_fd} {}
|
||||
|
||||
if (m_ffb_wheel_fd == -1)
|
||||
return;
|
||||
~SpringEffect() override { SetForce(0.0, 0.0); }
|
||||
|
||||
if (gain != 0.0)
|
||||
void SetForce(double gain, double center_position) override
|
||||
{
|
||||
INFO_LOG_FMT(CONTROLLERINTERFACE, "SpringEffect: {:.2} {:.2}", gain, center_position);
|
||||
|
||||
if (gain == 0.0)
|
||||
{
|
||||
if (m_effect_id != -1)
|
||||
{
|
||||
if (ioctl(m_ffb_fd, EVIOCRMFF, m_effect_id) < 0)
|
||||
ERROR_LOG_FMT(COMMON, "EVIOCRMFF: {}", Common::LastStrerrorString());
|
||||
|
||||
// TODO: Is it correct to not remove this effect on destruction.
|
||||
// Is it enough to close the parent fd ?
|
||||
|
||||
m_effect_id = -1;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ff_effect effect{
|
||||
.type = FF_SPRING,
|
||||
.id = m_centering_effect_id,
|
||||
.id = m_effect_id,
|
||||
};
|
||||
|
||||
// <linux/input.h>
|
||||
@@ -760,36 +778,119 @@ void evdevDevice::SetCenteringForce(double gain, double center_position)
|
||||
|
||||
condition[0].center = ControllerEmu::MapFloat<s16>(center_position, 0);
|
||||
|
||||
if (ioctl(m_ffb_wheel_fd, EVIOCSFF, &effect) < 0)
|
||||
if (ioctl(m_ffb_fd, EVIOCSFF, &effect) < 0)
|
||||
{
|
||||
ERROR_LOG_FMT(COMMON, "EVIOCSFF FF_SPRING: {}", Common::LastStrerrorString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_centering_effect_id == -1)
|
||||
if (m_effect_id == -1)
|
||||
{
|
||||
m_centering_effect_id = effect.id;
|
||||
m_effect_id = effect.id;
|
||||
|
||||
const input_event play{
|
||||
.type = EV_FF,
|
||||
.code = u16(m_centering_effect_id),
|
||||
.code = u16(m_effect_id),
|
||||
.value = std::numeric_limits<s32>::max(),
|
||||
};
|
||||
|
||||
if (write(m_ffb_wheel_fd, &play, sizeof(play)) < 0)
|
||||
if (write(m_ffb_fd, &play, sizeof(play)) < 0)
|
||||
ERROR_LOG_FMT(COMMON, "EV_FF: {}", Common::LastStrerrorString());
|
||||
}
|
||||
}
|
||||
else if (m_centering_effect_id != -1)
|
||||
|
||||
private:
|
||||
const int m_ffb_fd;
|
||||
s16 m_effect_id = -1;
|
||||
};
|
||||
|
||||
class FrictionEffect final : public Core::FrictionEffect
|
||||
{
|
||||
public:
|
||||
explicit FrictionEffect(int ffb_fd) : m_ffb_fd{ffb_fd} {}
|
||||
|
||||
~FrictionEffect() override { SetForce(0.0); }
|
||||
|
||||
void SetForce(double gain) override
|
||||
{
|
||||
if (ioctl(m_ffb_wheel_fd, EVIOCRMFF, m_centering_effect_id) < 0)
|
||||
ERROR_LOG_FMT(COMMON, "EVIOCRMFF: {}", Common::LastStrerrorString());
|
||||
INFO_LOG_FMT(CONTROLLERINTERFACE, "FrictionEffect: {:.2}", gain);
|
||||
|
||||
// TODO: Is it correct to not remove this effect on destruction.
|
||||
// Is it enough to close the parent fd ?
|
||||
if (gain == 0.0)
|
||||
{
|
||||
if (m_effect_id != -1)
|
||||
{
|
||||
if (ioctl(m_ffb_fd, EVIOCRMFF, m_effect_id) < 0)
|
||||
ERROR_LOG_FMT(COMMON, "EVIOCRMFF: {}", Common::LastStrerrorString());
|
||||
|
||||
m_centering_effect_id = -1;
|
||||
// TODO: Is it correct to not remove this effect on destruction.
|
||||
// Is it enough to close the parent fd ?
|
||||
|
||||
m_effect_id = -1;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ff_effect effect{
|
||||
.type = FF_FRICTION,
|
||||
.id = m_effect_id,
|
||||
};
|
||||
|
||||
// <linux/input.h>
|
||||
// "Values above 32767 ms (0x7fff) should not be used and have unspecified results."
|
||||
effect.replay.length = 0x7fff;
|
||||
|
||||
auto& condition = effect.u.condition;
|
||||
|
||||
const auto unsigned_gain = ControllerEmu::MapFloat<u16>(gain, 0);
|
||||
condition[0].right_saturation = unsigned_gain;
|
||||
condition[0].left_saturation = unsigned_gain;
|
||||
|
||||
// TODO: Is this a sensible coeff value ?
|
||||
constexpr auto coeff = std::numeric_limits<s16>::max();
|
||||
condition[0].right_coeff = coeff;
|
||||
condition[0].left_coeff = coeff;
|
||||
|
||||
if (ioctl(m_ffb_fd, EVIOCSFF, &effect) < 0)
|
||||
{
|
||||
ERROR_LOG_FMT(COMMON, "EVIOCSFF FF_SPRING: {}", Common::LastStrerrorString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_effect_id == -1)
|
||||
{
|
||||
m_effect_id = effect.id;
|
||||
|
||||
const input_event play{
|
||||
.type = EV_FF,
|
||||
.code = u16(m_effect_id),
|
||||
.value = std::numeric_limits<s32>::max(),
|
||||
};
|
||||
|
||||
if (write(m_ffb_fd, &play, sizeof(play)) < 0)
|
||||
ERROR_LOG_FMT(COMMON, "EV_FF: {}", Common::LastStrerrorString());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const int m_ffb_fd;
|
||||
s16 m_effect_id = -1;
|
||||
};
|
||||
|
||||
std::unique_ptr<Core::SpringEffect> evdevDevice::CreateSpringEffect()
|
||||
{
|
||||
if (m_ffb_fd == -1)
|
||||
return nullptr;
|
||||
|
||||
return std::make_unique<SpringEffect>(m_ffb_fd);
|
||||
}
|
||||
|
||||
std::unique_ptr<Core::FrictionEffect> evdevDevice::CreateFrictionEffect()
|
||||
{
|
||||
if (m_ffb_fd == -1)
|
||||
return nullptr;
|
||||
|
||||
return std::make_unique<FrictionEffect>(m_ffb_fd);
|
||||
}
|
||||
|
||||
evdevDevice::Effect::Effect(int fd) : m_fd(fd)
|
||||
|
||||
@@ -88,7 +88,8 @@ public:
|
||||
std::string GetName() const override { return m_name; }
|
||||
std::string GetSource() const override { return "evdev"; }
|
||||
|
||||
void SetCenteringForce(double gain, double center_position) override;
|
||||
std::unique_ptr<Core::SpringEffect> CreateSpringEffect() override;
|
||||
std::unique_ptr<Core::FrictionEffect> CreateFrictionEffect() override;
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
@@ -104,7 +105,6 @@ private:
|
||||
|
||||
InputBackend& m_input_backend;
|
||||
|
||||
int m_ffb_wheel_fd = -1;
|
||||
s16 m_centering_effect_id = -1;
|
||||
int m_ffb_fd = -1;
|
||||
};
|
||||
} // namespace ciface::evdev
|
||||
|
||||
Reference in New Issue
Block a user