mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2026-09-10 12:27:01 -05:00
Triforce: FFB wheel connected to SDL and evdev.
This commit is contained in:
@@ -14,7 +14,10 @@
|
||||
|
||||
#include "Core/HW/GCPad.h"
|
||||
|
||||
#include "InputCommon/ControllerEmu/ControllerEmu.h"
|
||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||
#include "InputCommon/GCPadStatus.h"
|
||||
#include "InputCommon/InputConfig.h"
|
||||
|
||||
namespace Triforce
|
||||
{
|
||||
@@ -237,7 +240,22 @@ void FZeroAXSteeringWheel::ProcessRequest(std::span<const u8> request)
|
||||
// This produces a value in the range around [-56, +56].
|
||||
m_servo_position = s8(0x80 - (u8(request[1] << 7u) | request[2]));
|
||||
|
||||
DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "SteeringWheel: servo_position: {}", m_servo_position);
|
||||
INFO_LOG_FMT(SERIALINTERFACE_AMBB, "SteeringWheel: servo_position: {}", m_servo_position);
|
||||
|
||||
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: 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 ?
|
||||
|
||||
wheel_device->SetCenteringForce(force_strength, center_position);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
|
||||
#include "Core/HW/GCPad.h"
|
||||
|
||||
#include "InputCommon/ControllerEmu/ControllerEmu.h"
|
||||
#include "InputCommon/GCPadStatus.h"
|
||||
#include "InputCommon/InputConfig.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -108,12 +110,30 @@ void MarioKartGPSteeringWheel::ProcessRequest(std::span<const u8> request)
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Handle friction !
|
||||
|
||||
const u16 centering_force = Common::swap16(request.data() + 4);
|
||||
const u16 friction_force = Common::swap16(request.data() + 6);
|
||||
const u16 roll = Common::swap16(request.data() + 8);
|
||||
|
||||
DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "SteeringWheel: FFB: {:04x} {:04x} {:04x}", centering_force,
|
||||
friction_force, roll);
|
||||
INFO_LOG_FMT(SERIALINTERFACE_AMBB, "SteeringWheel: FFB: {:04x} {:04x} {:04x}", centering_force,
|
||||
friction_force, roll);
|
||||
|
||||
// TODO: Is this sensible ?
|
||||
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), {});
|
||||
|
||||
// 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());
|
||||
|
||||
wheel_device->SetCenteringForce(force_strength, center_position);
|
||||
}
|
||||
|
||||
switch (m_init_state)
|
||||
{
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
#include "Core/HW/Triforce/IOPorts.h"
|
||||
#include "Core/HW/Triforce/SerialDevice.h"
|
||||
|
||||
namespace ciface::Core
|
||||
{
|
||||
class Device;
|
||||
}
|
||||
|
||||
namespace Triforce
|
||||
{
|
||||
|
||||
|
||||
@@ -121,6 +121,10 @@ Device::Output* Device::FindOutput(std::string_view name) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Device::SetCenteringForce(double gain, double center_position)
|
||||
{
|
||||
}
|
||||
|
||||
bool Device::Control::IsMatchingName(std::string_view name) const
|
||||
{
|
||||
return GetName() == name;
|
||||
|
||||
@@ -160,6 +160,8 @@ public:
|
||||
Input* FindInput(std::string_view name) const;
|
||||
Output* FindOutput(std::string_view name) const;
|
||||
|
||||
virtual void SetCenteringForce(double gain, double center_position);
|
||||
|
||||
protected:
|
||||
void AddInput(Input* const i);
|
||||
void AddOutput(Output* const o);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/ScopeGuard.h"
|
||||
#include "InputCommon/ControllerEmu/ControllerEmu.h"
|
||||
|
||||
namespace ciface::SDL
|
||||
{
|
||||
@@ -220,6 +221,9 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,6 +276,67 @@ SDL_JoystickID Gamepad::GetSDLInstanceID() const
|
||||
return SDL_GetJoystickID(m_joystick);
|
||||
}
|
||||
|
||||
void Gamepad::SetCenteringForce(double gain, double center_position)
|
||||
{
|
||||
INFO_LOG_FMT(CONTROLLERINTERFACE, "SetCenteringForce: {:.2} {:.2}", gain, center_position);
|
||||
|
||||
if (m_haptic == nullptr)
|
||||
return;
|
||||
|
||||
if (gain != 0.0)
|
||||
{
|
||||
SDL_HapticEffect effect{
|
||||
.type = SDL_HAPTIC_SPRING,
|
||||
};
|
||||
|
||||
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;
|
||||
// condition.direction.dir[0] = 1;
|
||||
|
||||
// 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;
|
||||
|
||||
condition.center[0] = ControllerEmu::MapFloat<s16>(center_position, 0);
|
||||
|
||||
if (m_centering_haptic_effect == -1)
|
||||
{
|
||||
// Create and start a new effect.
|
||||
m_centering_haptic_effect = SDL_CreateHapticEffect(m_haptic, &effect);
|
||||
if (m_centering_haptic_effect == -1)
|
||||
{
|
||||
ERROR_LOG_FMT(CONTROLLERINTERFACE, "SDL_CreateHapticEffect: {}", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SDL_RunHapticEffect(m_haptic, m_centering_haptic_effect, 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))
|
||||
ERROR_LOG_FMT(CONTROLLERINTERFACE, "SDL_UpdateHapticEffect: {}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
else if (m_centering_haptic_effect != -1)
|
||||
{
|
||||
SDL_DestroyHapticEffect(m_haptic, std::exchange(m_centering_haptic_effect, -1));
|
||||
}
|
||||
}
|
||||
|
||||
std::string Gamepad::Button::GetName() const
|
||||
{
|
||||
const auto button = m_binding.output.button;
|
||||
|
||||
@@ -342,6 +342,8 @@ public:
|
||||
return Core::DeviceRemoval::Keep;
|
||||
}
|
||||
|
||||
void SetCenteringForce(double gain, double center_position) override;
|
||||
|
||||
private:
|
||||
void UpdateRumble()
|
||||
{
|
||||
@@ -369,6 +371,8 @@ 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
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Thread.h"
|
||||
#include "Common/WorkQueueThread.h"
|
||||
#include "InputCommon/ControllerEmu/ControllerEmu.h"
|
||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||
|
||||
namespace ciface::evdev
|
||||
@@ -587,6 +588,8 @@ bool evdevDevice::AddNode(std::string devnode, int fd, libevdev* dev)
|
||||
ie.value = 0;
|
||||
|
||||
static_cast<void>(!write(fd, &ie, sizeof(ie)));
|
||||
|
||||
m_ffb_wheel_fd = fd;
|
||||
}
|
||||
|
||||
// Constant FF effect
|
||||
@@ -726,6 +729,69 @@ bool evdevDevice::IsValid() const
|
||||
return true;
|
||||
}
|
||||
|
||||
void evdevDevice::SetCenteringForce(double gain, double center_position)
|
||||
{
|
||||
INFO_LOG_FMT(CONTROLLERINTERFACE, "SetCenteringForce: {:.2} {:.2}", gain, center_position);
|
||||
|
||||
if (m_ffb_wheel_fd == -1)
|
||||
return;
|
||||
|
||||
if (gain != 0.0)
|
||||
{
|
||||
ff_effect effect{
|
||||
.type = FF_SPRING,
|
||||
.id = m_centering_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;
|
||||
|
||||
condition[0].center = ControllerEmu::MapFloat<s16>(center_position, 0);
|
||||
|
||||
if (ioctl(m_ffb_wheel_fd, EVIOCSFF, &effect) < 0)
|
||||
{
|
||||
ERROR_LOG_FMT(COMMON, "EVIOCSFF FF_SPRING: {}", Common::LastStrerrorString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_centering_effect_id == -1)
|
||||
{
|
||||
m_centering_effect_id = effect.id;
|
||||
|
||||
const input_event play{
|
||||
.type = EV_FF,
|
||||
.code = u16(m_centering_effect_id),
|
||||
.value = std::numeric_limits<s32>::max(),
|
||||
};
|
||||
|
||||
if (write(m_ffb_wheel_fd, &play, sizeof(play)) < 0)
|
||||
ERROR_LOG_FMT(COMMON, "EV_FF: {}", Common::LastStrerrorString());
|
||||
}
|
||||
}
|
||||
else if (m_centering_effect_id != -1)
|
||||
{
|
||||
if (ioctl(m_ffb_wheel_fd, EVIOCRMFF, m_centering_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_centering_effect_id = -1;
|
||||
}
|
||||
}
|
||||
|
||||
evdevDevice::Effect::Effect(int fd) : m_fd(fd)
|
||||
{
|
||||
m_effect.id = -1;
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libevdev/libevdev.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <libevdev/libevdev.h>
|
||||
|
||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||
|
||||
namespace ciface::evdev
|
||||
@@ -87,6 +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;
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
|
||||
@@ -100,5 +103,8 @@ private:
|
||||
std::vector<Node> m_nodes;
|
||||
|
||||
InputBackend& m_input_backend;
|
||||
|
||||
int m_ffb_wheel_fd = -1;
|
||||
s16 m_centering_effect_id = -1;
|
||||
};
|
||||
} // namespace ciface::evdev
|
||||
|
||||
Reference in New Issue
Block a user