diff --git a/Source/Core/InputCommon/ControlReference/ControlReference.cpp b/Source/Core/InputCommon/ControlReference/ControlReference.cpp index b3355418e2..3132baf07b 100644 --- a/Source/Core/InputCommon/ControlReference/ControlReference.cpp +++ b/Source/Core/InputCommon/ControlReference/ControlReference.cpp @@ -2,6 +2,10 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. +#include "InputCommon/ControlReference/ControlReference.h" + +#include + #include "Common/Thread.h" // For InputGateOn() // This is a bad layering violation, but it's the cleanest @@ -9,11 +13,13 @@ #include "Core/ConfigManager.h" #include "Core/Host.h" -#include "InputCommon/ControlReference/ControlReference.h" - using namespace ciface::ExpressionParser; +namespace +{ +// Compared to an input's current state (ideally 1.0) minus abs(initial_state) (ideally 0.0). constexpr ControlState INPUT_DETECT_THRESHOLD = 0.55; +} // namespace bool ControlReference::InputGateOn() { @@ -110,56 +116,53 @@ ControlState OutputReference::State(const ControlState state) return 0.0; } -// -// InputReference :: Detect -// -// Wait for input on all binded devices -// supports not detecting inputs that were held down at the time of Detect start, -// which is useful for those crazy flightsticks that have certain buttons that are always held down -// or some crazy axes or something -// upon input, return pointer to detected Control -// else return nullptr -// +// Wait for input on a particular device. +// Inputs are considered if they are first seen in a neutral state. +// This is useful for crazy flightsticks that have certain buttons that are always held down +// and also properly handles detection when using "FullAnalogSurface" inputs. +// Upon input, return a pointer to the detected Control, else return nullptr. ciface::Core::Device::Control* InputReference::Detect(const unsigned int ms, ciface::Core::Device* const device) { - unsigned int time = 0; - std::vector states(device->Inputs().size()); - - if (device->Inputs().empty()) - return nullptr; - - // get starting state of all inputs, - // so we can ignore those that were activated at time of Detect start - std::vector::const_iterator i = device->Inputs().begin(), - e = device->Inputs().end(); - for (std::vector::iterator state = states.begin(); i != e; ++i) - *state++ = ((*i)->GetState() > (1 - INPUT_DETECT_THRESHOLD)); - - while (time < ms) + struct InputState { - device->UpdateInput(); - i = device->Inputs().begin(); - for (std::vector::iterator state = states.begin(); i != e; ++i, ++state) - { - // detected an input - if ((*i)->IsDetectable() && (*i)->GetState() > INPUT_DETECT_THRESHOLD) - { - // input was released at some point during Detect call - // return the detected input - if (false == *state) - return *i; - } - else if ((*i)->GetState() < (1 - INPUT_DETECT_THRESHOLD)) - { - *state = false; - } - } - Common::SleepCurrentThread(10); - time += 10; + ciface::Core::Device::Input& input; + ControlState initial_state; + }; + + std::vector input_states; + for (auto* input : device->Inputs()) + { + // Don't detect things like absolute cursor position. + if (!input->IsDetectable()) + continue; + + // Undesirable axes will have negative values here when trying to map a "FullAnalogSurface". + input_states.push_back({*input, input->GetState()}); } - // no input was detected + if (input_states.empty()) + return nullptr; + + unsigned int time = 0; + while (time < ms) + { + Common::SleepCurrentThread(10); + time += 10; + + device->UpdateInput(); + for (auto& input_state : input_states) + { + // We want an input that was initially 0.0 and currently 1.0. + const auto detection_score = + (input_state.input.GetState() - std::abs(input_state.initial_state)); + + if (detection_score > INPUT_DETECT_THRESHOLD) + return &input_state.input; + } + } + + // No input was detected. :'( return nullptr; } diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index ba3f78170d..a73b1c94cf 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -217,7 +217,19 @@ public: std::shared_ptr m_device; explicit ControlExpression(ControlQualifier qualifier_) : qualifier(qualifier_) {} - ControlState GetValue() const override { return control ? control->ToInput()->GetState() : 0.0; } + ControlState GetValue() const override + { + if (!control) + return 0.0; + + // Note: Inputs may return negative values in situations where opposing directions are + // activated. We clamp off the negative values here. + + // FYI: Clamping values greater than 1.0 is purposely not done to support unbounded values in + // the future. (e.g. raw accelerometer/gyro data) + + return std::max(0.0, control->ToInput()->GetState()); + } void SetValue(ControlState value) override { if (control) diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp b/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp index 21e39bc416..400f9b078c 100644 --- a/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp +++ b/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.cpp @@ -259,7 +259,7 @@ std::string Joystick::Hat::GetName() const ControlState Joystick::Axis::GetState() const { - return std::max(0.0, ControlState(m_axis - m_base) / m_range); + return ControlState(m_axis - m_base) / m_range; } ControlState Joystick::Button::GetState() const diff --git a/Source/Core/InputCommon/ControllerInterface/Device.cpp b/Source/Core/InputCommon/ControllerInterface/Device.cpp index b8d73d9ab8..efe1372711 100644 --- a/Source/Core/InputCommon/ControllerInterface/Device.cpp +++ b/Source/Core/InputCommon/ControllerInterface/Device.cpp @@ -4,6 +4,7 @@ #include "InputCommon/ControllerInterface/Device.h" +#include #include #include #include @@ -68,6 +69,11 @@ Device::Output* Device::FindOutput(const std::string& name) const return nullptr; } +ControlState Device::FullAnalogSurface::GetState() const +{ + return (1 + std::max(0.0, m_high.GetState()) - std::max(0.0, m_low.GetState())) / 2; +} + // // DeviceQualifier :: ToString // @@ -214,5 +220,5 @@ bool DeviceContainer::HasConnectedDevice(const DeviceQualifier& qualifier) const const auto device = FindDevice(qualifier); return device != nullptr && device->IsValid(); } -} -} +} // namespace Core +} // namespace ciface diff --git a/Source/Core/InputCommon/ControllerInterface/Device.h b/Source/Core/InputCommon/ControllerInterface/Device.h index 7b36da934a..0ee2b31444 100644 --- a/Source/Core/InputCommon/ControllerInterface/Device.h +++ b/Source/Core/InputCommon/ControllerInterface/Device.h @@ -55,9 +55,22 @@ public: class Input : public Control { public: - // things like absolute axes/ absolute mouse position will override this + // Things like absolute axes/ absolute mouse position should override this to prevent + // undesirable behavior in our mapping logic. virtual bool IsDetectable() { return true; } + + // Implementations should return a value from 0.0 to 1.0 across their normal range. + // One input should be provided for each "direction". (e.g. 2 for each axis) + // If possible, negative values may be returned in situations where an opposing input is + // activated. (e.g. When an underlying axis, X, is currently negative, "Axis X-", will return a + // positive value and "Axis X+" may return a negative value.) + // Doing so is solely to allow our input detection logic to better detect false positives. + // This is necessary when making use of "FullAnalogSurface" as multiple inputs will be seen + // increasing from 0.0 to 1.0 as a user tries to map just one. The negative values provide a + // view of the underlying axis. (Negative values are clamped off before they reach + // expression-parser or controller-emu) virtual ControlState GetState() const = 0; + Input* ToInput() override { return this; } }; @@ -96,11 +109,7 @@ protected: { public: FullAnalogSurface(Input* low, Input* high) : m_low(*low), m_high(*high) {} - ControlState GetState() const override - { - return (1 + m_high.GetState() - m_low.GetState()) / 2; - } - + ControlState GetState() const override; std::string GetName() const override { return m_low.GetName() + *m_high.GetName().rbegin(); } private: diff --git a/Source/Core/InputCommon/ControllerInterface/XInput/XInput.cpp b/Source/Core/InputCommon/ControllerInterface/XInput/XInput.cpp index 0b5cbad358..8c518335a0 100644 --- a/Source/Core/InputCommon/ControllerInterface/XInput/XInput.cpp +++ b/Source/Core/InputCommon/ControllerInterface/XInput/XInput.cpp @@ -228,7 +228,7 @@ ControlState Device::Trigger::GetState() const ControlState Device::Axis::GetState() const { - return std::max(0.0, ControlState(m_axis) / m_range); + return ControlState(m_axis) / m_range; } void Device::Motor::SetState(ControlState state) @@ -236,5 +236,5 @@ void Device::Motor::SetState(ControlState state) m_motor = (WORD)(state * m_range); m_parent->UpdateMotors(); } -} -} +} // namespace XInput +} // namespace ciface diff --git a/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp index 1cec2333ef..d1fa694fae 100644 --- a/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp +++ b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp @@ -343,7 +343,7 @@ ControlState evdevDevice::Axis::GetState() const int value = 0; libevdev_fetch_event_value(m_dev, EV_ABS, m_code, &value); - return std::max(0.0, ControlState(value - m_base) / m_range); + return ControlState(value - m_base) / m_range; } evdevDevice::Effect::Effect(int fd) : m_fd(fd)