mirror of
https://github.com/WinampDesktop/winamp.git
synced 2025-06-19 05:15:46 -04:00
Support for SDL Joysticks
This enables use of non-controller peripherals, such as DirectInput steering wheels or flight sticks
This commit is contained in:
@ -128,16 +128,18 @@ void InputBindingDialog::addNewBinding(std::string new_binding)
|
||||
saveListToSettings();
|
||||
}
|
||||
|
||||
void InputBindingDialog::bindToControllerAxis(int controller_index, int axis_index, std::optional<bool> positive)
|
||||
void InputBindingDialog::bindToControllerAxis(int controller_index, int axis_index, bool inverted,
|
||||
std::optional<bool> half_axis_positive)
|
||||
{
|
||||
const char* invert_char = inverted ? "-" : "";
|
||||
const char* sign_char = "";
|
||||
if (positive)
|
||||
if (half_axis_positive)
|
||||
{
|
||||
sign_char = *positive ? "+" : "-";
|
||||
sign_char = *half_axis_positive ? "+" : "-";
|
||||
}
|
||||
|
||||
std::string binding =
|
||||
StringUtil::StdStringFromFormat("Controller%d/%sAxis%d", controller_index, sign_char, axis_index);
|
||||
StringUtil::StdStringFromFormat("Controller%d/%sAxis%d%s", controller_index, sign_char, axis_index, invert_char);
|
||||
addNewBinding(std::move(binding));
|
||||
stopListeningForInput();
|
||||
}
|
||||
@ -149,6 +151,14 @@ void InputBindingDialog::bindToControllerButton(int controller_index, int button
|
||||
stopListeningForInput();
|
||||
}
|
||||
|
||||
void InputBindingDialog::bindToControllerHat(int controller_index, int hat_index, const QString& hat_direction)
|
||||
{
|
||||
std::string binding = StringUtil::StdStringFromFormat("Controller%d/Hat%d %s", controller_index, hat_index,
|
||||
hat_direction.toLatin1().constData());
|
||||
addNewBinding(std::move(binding));
|
||||
stopListeningForInput();
|
||||
}
|
||||
|
||||
void InputBindingDialog::onAddBindingButtonClicked()
|
||||
{
|
||||
if (isListeningForInput())
|
||||
|
@ -19,8 +19,10 @@ public:
|
||||
~InputBindingDialog();
|
||||
|
||||
protected Q_SLOTS:
|
||||
void bindToControllerAxis(int controller_index, int axis_index, std::optional<bool> positive);
|
||||
void bindToControllerAxis(int controller_index, int axis_index, bool inverted,
|
||||
std::optional<bool> half_axis_positive);
|
||||
void bindToControllerButton(int controller_index, int button_index);
|
||||
void bindToControllerHat(int controller_index, int hat_index, const QString& hat_direction);
|
||||
void onAddBindingButtonClicked();
|
||||
void onRemoveBindingButtonClicked();
|
||||
void onClearBindingsButtonClicked();
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "inputbindingmonitor.h"
|
||||
#include <cmath>
|
||||
|
||||
ControllerInterface::Hook::CallbackResult
|
||||
InputButtonBindingMonitor::operator()(const ControllerInterface::Hook& ei) const
|
||||
@ -6,20 +7,32 @@ InputButtonBindingMonitor::operator()(const ControllerInterface::Hook& ei) const
|
||||
if (ei.type == ControllerInterface::Hook::Type::Axis)
|
||||
{
|
||||
// wait until it's at least half pushed so we don't get confused between axises with small movement
|
||||
if (std::abs(ei.value) < 0.5f)
|
||||
if (std::abs(std::get<float>(ei.value)) < 0.5f)
|
||||
return ControllerInterface::Hook::CallbackResult::ContinueMonitoring;
|
||||
|
||||
// TODO: this probably should consider the "last value"
|
||||
QMetaObject::invokeMethod(m_parent, "bindToControllerAxis", Q_ARG(int, ei.controller_index),
|
||||
Q_ARG(int, ei.button_or_axis_number), Q_ARG(std::optional<bool>, ei.value > 0));
|
||||
Q_ARG(int, ei.button_or_axis_number), Q_ARG(bool, false),
|
||||
Q_ARG(std::optional<bool>, std::get<float>(ei.value) > 0));
|
||||
return ControllerInterface::Hook::CallbackResult::StopMonitoring;
|
||||
}
|
||||
else if (ei.type == ControllerInterface::Hook::Type::Button && ei.value > 0.0f)
|
||||
else if (ei.type == ControllerInterface::Hook::Type::Button && std::get<float>(ei.value) > 0.0f)
|
||||
{
|
||||
QMetaObject::invokeMethod(m_parent, "bindToControllerButton", Q_ARG(int, ei.controller_index),
|
||||
Q_ARG(int, ei.button_or_axis_number));
|
||||
return ControllerInterface::Hook::CallbackResult::StopMonitoring;
|
||||
}
|
||||
else if (ei.type == ControllerInterface::Hook::Type::Hat)
|
||||
{
|
||||
const std::string_view hat_position = std::get<std::string_view>(ei.value);
|
||||
if (!hat_position.empty())
|
||||
{
|
||||
QString str = QString::fromLatin1(hat_position.data(), static_cast<int>(hat_position.size()));
|
||||
QMetaObject::invokeMethod(m_parent, "bindToControllerHat", Q_ARG(int, ei.controller_index),
|
||||
Q_ARG(int, ei.button_or_axis_number), Q_ARG(QString, std::move(str)));
|
||||
return ControllerInterface::Hook::CallbackResult::StopMonitoring;
|
||||
}
|
||||
}
|
||||
|
||||
return ControllerInterface::Hook::CallbackResult::ContinueMonitoring;
|
||||
}
|
||||
@ -28,16 +41,17 @@ ControllerInterface::Hook::CallbackResult InputAxisBindingMonitor::operator()(co
|
||||
{
|
||||
if (ei.type == ControllerInterface::Hook::Type::Axis)
|
||||
{
|
||||
// wait until it's at least half pushed so we don't get confused between axises with small movement
|
||||
if (std::abs(ei.value) < 0.5f)
|
||||
std::optional<bool> half_axis_positive, inverted;
|
||||
if (!ProcessAxisInput(ei, half_axis_positive, inverted))
|
||||
return ControllerInterface::Hook::CallbackResult::ContinueMonitoring;
|
||||
|
||||
QMetaObject::invokeMethod(m_parent, "bindToControllerAxis", Q_ARG(int, ei.controller_index),
|
||||
Q_ARG(int, ei.button_or_axis_number), Q_ARG(std::optional<bool>, std::nullopt));
|
||||
Q_ARG(int, ei.button_or_axis_number), Q_ARG(bool, inverted.value_or(false)),
|
||||
Q_ARG(std::optional<bool>, half_axis_positive));
|
||||
return ControllerInterface::Hook::CallbackResult::StopMonitoring;
|
||||
}
|
||||
else if (ei.type == ControllerInterface::Hook::Type::Button && m_axis_type == Controller::AxisType::Half &&
|
||||
ei.value > 0.0f)
|
||||
std::get<float>(ei.value) > 0.0f)
|
||||
{
|
||||
QMetaObject::invokeMethod(m_parent, "bindToControllerButton", Q_ARG(int, ei.controller_index),
|
||||
Q_ARG(int, ei.button_or_axis_number));
|
||||
@ -47,10 +61,84 @@ ControllerInterface::Hook::CallbackResult InputAxisBindingMonitor::operator()(co
|
||||
return ControllerInterface::Hook::CallbackResult::ContinueMonitoring;
|
||||
}
|
||||
|
||||
bool InputAxisBindingMonitor::ProcessAxisInput(const ControllerInterface::Hook& ei,
|
||||
std::optional<bool>& half_axis_positive,
|
||||
std::optional<bool>& inverted) const
|
||||
{
|
||||
const float value = std::get<float>(ei.value);
|
||||
|
||||
if (!ei.track_history) // Keyboard, mouse, game controller
|
||||
{
|
||||
// wait until it's at least half pushed so we don't get confused between axises with small movement
|
||||
if (std::abs(value) < 0.5f)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
else // Joystick
|
||||
{
|
||||
auto& history = m_context->m_inputs_history;
|
||||
// Reject inputs coming from multiple sources
|
||||
if (!history.empty())
|
||||
{
|
||||
const auto& item = history.front();
|
||||
if (ei.controller_index != item.controller_index || ei.button_or_axis_number != item.axis_number)
|
||||
return false;
|
||||
}
|
||||
history.push_back({ei.controller_index, ei.button_or_axis_number, value});
|
||||
return AnalyzeInputHistory(half_axis_positive, inverted);
|
||||
}
|
||||
}
|
||||
|
||||
bool InputAxisBindingMonitor::AnalyzeInputHistory(std::optional<bool>& half_axis_positive,
|
||||
std::optional<bool>& inverted) const
|
||||
{
|
||||
const auto& history = m_context->m_inputs_history;
|
||||
const auto [min, max] = std::minmax_element(
|
||||
history.begin(), history.end(), [](const auto& left, const auto& right) { return left.value < right.value; });
|
||||
|
||||
// Ignore small input magnitudes
|
||||
if (std::abs(max->value - min->value) < 0.5f)
|
||||
return false;
|
||||
|
||||
// Used heuristics:
|
||||
// * If history contains inputs with both - and + sign (ignoring 0), bind a full axis
|
||||
// * If history contains only 0 and inputs of the same sign AND maxes out at 1.0/-1.0, bind a half axis
|
||||
// * Use the direction of input changes to determine whether the axis is inverted or not
|
||||
if (std::signbit(min->value) != std::signbit(max->value))
|
||||
{
|
||||
if (min->value != 0.0f && max->value != 0.0f)
|
||||
{
|
||||
// If max value comes before the min value, invert the half axis
|
||||
if (m_axis_type == Controller::AxisType::Half)
|
||||
{
|
||||
inverted = std::distance(min, max) < 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((std::abs(min->value) > 0.99f || std::abs(max->value) > 0.99f) &&
|
||||
(std::abs(min->value) < 0.01f || std::abs(max->value) < 0.01f))
|
||||
{
|
||||
|
||||
if (m_axis_type == Controller::AxisType::Half)
|
||||
{
|
||||
half_axis_positive = max->value > 0.0f;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ControllerInterface::Hook::CallbackResult
|
||||
InputRumbleBindingMonitor::operator()(const ControllerInterface::Hook& ei) const
|
||||
{
|
||||
if (ei.type == ControllerInterface::Hook::Type::Button && ei.value > 0.0f)
|
||||
if (ei.type == ControllerInterface::Hook::Type::Button && std::get<float>(ei.value) > 0.0f)
|
||||
{
|
||||
QMetaObject::invokeMethod(m_parent, "bindToControllerRumble", Q_ARG(int, ei.controller_index));
|
||||
return ControllerInterface::Hook::CallbackResult::StopMonitoring;
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
#include "frontend-common/controller_interface.h"
|
||||
#include <QtCore/QObject>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
// NOTE: Those Monitor classes must be copyable to meet the requirements of std::function, but at the same time we want
|
||||
// copies to be opaque to the caling code and share context. Therefore, all mutable context of the monitor (if required)
|
||||
@ -29,8 +31,25 @@ public:
|
||||
ControllerInterface::Hook::CallbackResult operator()(const ControllerInterface::Hook& ei) const;
|
||||
|
||||
private:
|
||||
bool ProcessAxisInput(const ControllerInterface::Hook& ei, std::optional<bool>& half_axis_positive,
|
||||
std::optional<bool>& inverted) const;
|
||||
bool AnalyzeInputHistory(std::optional<bool>& half_axis_positive, std::optional<bool>& inverted) const;
|
||||
|
||||
struct Context
|
||||
{
|
||||
struct History
|
||||
{
|
||||
int controller_index;
|
||||
int axis_number;
|
||||
float value;
|
||||
};
|
||||
|
||||
std::vector<History> m_inputs_history;
|
||||
};
|
||||
|
||||
QObject* m_parent;
|
||||
Controller::AxisType m_axis_type;
|
||||
std::shared_ptr<Context> m_context = std::make_shared<Context>();
|
||||
};
|
||||
|
||||
class InputRumbleBindingMonitor
|
||||
|
@ -41,16 +41,18 @@ void InputBindingWidget::updateText()
|
||||
setText(QString::fromStdString(m_bindings[0]));
|
||||
}
|
||||
|
||||
void InputBindingWidget::bindToControllerAxis(int controller_index, int axis_index, std::optional<bool> positive)
|
||||
void InputBindingWidget::bindToControllerAxis(int controller_index, int axis_index, bool inverted,
|
||||
std::optional<bool> half_axis_positive)
|
||||
{
|
||||
const char* invert_char = inverted ? "-" : "";
|
||||
const char* sign_char = "";
|
||||
if (positive)
|
||||
if (half_axis_positive)
|
||||
{
|
||||
sign_char = *positive ? "+" : "-";
|
||||
sign_char = *half_axis_positive ? "+" : "-";
|
||||
}
|
||||
|
||||
m_new_binding_value =
|
||||
StringUtil::StdStringFromFormat("Controller%d/%sAxis%d", controller_index, sign_char, axis_index);
|
||||
StringUtil::StdStringFromFormat("Controller%d/%sAxis%d%s", controller_index, sign_char, axis_index, invert_char);
|
||||
setNewBinding();
|
||||
stopListeningForInput();
|
||||
}
|
||||
@ -62,6 +64,14 @@ void InputBindingWidget::bindToControllerButton(int controller_index, int button
|
||||
stopListeningForInput();
|
||||
}
|
||||
|
||||
void InputBindingWidget::bindToControllerHat(int controller_index, int hat_index, const QString& hat_direction)
|
||||
{
|
||||
m_new_binding_value = StringUtil::StdStringFromFormat("Controller%d/Hat%d %s", controller_index, hat_index,
|
||||
hat_direction.toLatin1().constData());
|
||||
setNewBinding();
|
||||
stopListeningForInput();
|
||||
}
|
||||
|
||||
void InputBindingWidget::beginRebindAll()
|
||||
{
|
||||
m_is_binding_all = true;
|
||||
|
@ -20,8 +20,10 @@ public:
|
||||
ALWAYS_INLINE void setNextWidget(InputBindingWidget* widget) { m_next_widget = widget; }
|
||||
|
||||
public Q_SLOTS:
|
||||
void bindToControllerAxis(int controller_index, int axis_index, std::optional<bool> positive);
|
||||
void bindToControllerAxis(int controller_index, int axis_index, bool inverted,
|
||||
std::optional<bool> half_axis_positive);
|
||||
void bindToControllerButton(int controller_index, int button_index);
|
||||
void bindToControllerHat(int controller_index, int hat_index, const QString& hat_direction);
|
||||
void beginRebindAll();
|
||||
void clearBinding();
|
||||
void reloadBinding();
|
||||
|
Reference in New Issue
Block a user