From 48b69ca01841ba724049895a83d8d88ec95d66de Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Tue, 26 Feb 2019 19:46:21 -0600 Subject: [PATCH 1/2] ControllerInterface: Input detection improvements. --- .../ControlReference/ControlReference.cpp | 81 ++++++++++--------- .../ControlReference/ExpressionParser.cpp | 14 +++- .../DInput/DInputJoystick.cpp | 2 +- .../ControllerInterface/Device.cpp | 10 ++- .../InputCommon/ControllerInterface/Device.h | 21 +++-- .../ControllerInterface/XInput/XInput.cpp | 6 +- .../ControllerInterface/evdev/evdev.cpp | 2 +- 7 files changed, 83 insertions(+), 53 deletions(-) diff --git a/Source/Core/InputCommon/ControlReference/ControlReference.cpp b/Source/Core/InputCommon/ControlReference/ControlReference.cpp index b3355418e21f..3132baf07b1d 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()); + struct InputState + { + ciface::Core::Device::Input& input; + ControlState initial_state; + }; - if (device->Inputs().empty()) - return nullptr; + 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()}); + } - // 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)); + if (input_states.empty()) + return nullptr; + unsigned int time = 0; while (time < ms) { + Common::SleepCurrentThread(10); + time += 10; + device->UpdateInput(); - i = device->Inputs().begin(); - for (std::vector::iterator state = states.begin(); i != e; ++i, ++state) + for (auto& input_state : input_states) { - // 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; - } + // 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; } - Common::SleepCurrentThread(10); - time += 10; } - // no input was detected + // No input was detected. :'( return nullptr; } diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index ba3f78170de3..a73b1c94cfa5 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -217,7 +217,19 @@ class ControlExpression : public Expression 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 21e39bc416db..400f9b078c67 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 b8d73d9ab8c3..efe1372711eb 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 7b36da934a08..0ee2b314447e 100644 --- a/Source/Core/InputCommon/ControllerInterface/Device.h +++ b/Source/Core/InputCommon/ControllerInterface/Device.h @@ -55,9 +55,22 @@ class Device 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 @@ class Device { 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 0b5cbad35825..8c518335a0eb 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 1cec2333ef9e..d1fa694fae7a 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) From c389d68186ea7fa95d763d7bb55bc09e5a45c4cd Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Wed, 27 Feb 2019 18:10:18 -0600 Subject: [PATCH 2/2] ControllerInterface/DolphinQt: Make mapping "all devices" way less hacky. --- .../DolphinQt/Config/Mapping/IOWindow.cpp | 41 +++---- .../Core/DolphinQt/Config/Mapping/IOWindow.h | 1 + .../Config/Mapping/MappingButton.cpp | 112 ++++-------------- .../DolphinQt/Config/Mapping/MappingButton.h | 1 - .../Config/Mapping/MappingCommon.cpp | 65 ++++++++-- .../DolphinQt/Config/Mapping/MappingCommon.h | 19 ++- .../ControlReference/ControlReference.cpp | 89 -------------- .../ControlReference/ControlReference.h | 6 - .../ControllerInterface/Device.cpp | 87 +++++++++++++- .../InputCommon/ControllerInterface/Device.h | 3 + 10 files changed, 192 insertions(+), 232 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp index 8bf3ab063042..5f1b32ba83fa 100644 --- a/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp @@ -136,8 +136,8 @@ void IOWindow::ConnectWidgets() connect(m_or_button, &QPushButton::clicked, [this] { AppendSelectedOption(" | "); }); connect(m_not_button, &QPushButton::clicked, [this] { AppendSelectedOption("!"); }); - connect(m_type == IOWindow::Type::Input ? m_detect_button : m_test_button, &QPushButton::clicked, - this, &IOWindow::OnDetectButtonPressed); + connect(m_detect_button, &QPushButton::clicked, this, &IOWindow::OnDetectButtonPressed); + connect(m_test_button, &QPushButton::clicked, this, &IOWindow::OnTestButtonPressed); connect(m_button_box, &QDialogButtonBox::clicked, this, &IOWindow::OnDialogButtonPressed); connect(m_devices_combo, &QComboBox::currentTextChanged, this, &IOWindow::OnDeviceChanged); @@ -180,35 +180,22 @@ void IOWindow::OnDialogButtonPressed(QAbstractButton* button) void IOWindow::OnDetectButtonPressed() { - installEventFilter(BlockUserInputFilter::Instance()); - grabKeyboard(); - grabMouse(); + const auto expression = + MappingCommon::DetectExpression(m_detect_button, g_controller_interface, {m_devq.ToString()}, + m_devq, MappingCommon::Quote::Off); - std::thread([this] { - auto* btn = m_type == IOWindow::Type::Input ? m_detect_button : m_test_button; - const auto old_label = btn->text(); - - btn->setText(QStringLiteral("...")); - - const auto expr = MappingCommon::DetectExpression( - m_reference, g_controller_interface.FindDevice(m_devq).get(), m_devq, - MappingCommon::Quote::Off); - - btn->setText(old_label); + if (expression.isEmpty()) + return; - if (!expr.isEmpty()) - { - const auto list = m_option_list->findItems(expr, Qt::MatchFixedString); + const auto list = m_option_list->findItems(expression, Qt::MatchFixedString); - if (!list.empty()) - m_option_list->setCurrentItem(list[0]); - } + if (!list.empty()) + m_option_list->setCurrentItem(list[0]); +} - releaseMouse(); - releaseKeyboard(); - removeEventFilter(BlockUserInputFilter::Instance()); - }) - .detach(); +void IOWindow::OnTestButtonPressed() +{ + MappingCommon::TestOutput(m_test_button, static_cast(m_reference)); } void IOWindow::OnRangeChanged(int value) diff --git a/Source/Core/DolphinQt/Config/Mapping/IOWindow.h b/Source/Core/DolphinQt/Config/Mapping/IOWindow.h index 011739327251..250b01630904 100644 --- a/Source/Core/DolphinQt/Config/Mapping/IOWindow.h +++ b/Source/Core/DolphinQt/Config/Mapping/IOWindow.h @@ -48,6 +48,7 @@ class IOWindow final : public QDialog void OnDialogButtonPressed(QAbstractButton* button); void OnDeviceChanged(const QString& device); void OnDetectButtonPressed(); + void OnTestButtonPressed(); void OnRangeChanged(int range); void AppendSelectedOption(const std::string& prefix); diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp index 6e1a2fa81385..a068397d096f 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp @@ -2,8 +2,7 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. -#include -#include +#include "DolphinQt/Config/Mapping/MappingButton.h" #include #include @@ -12,8 +11,6 @@ #include #include -#include "DolphinQt/Config/Mapping/MappingButton.h" - #include "Common/Thread.h" #include "Core/Core.h" @@ -103,96 +100,33 @@ void MappingButton::Detect() if (m_parent->GetDevice() == nullptr || !m_reference->IsInput()) return; - installEventFilter(BlockUserInputFilter::Instance()); - grabKeyboard(); - - // Make sure that we don't block event handling - QueueOnObject(this, [this] { - setText(QStringLiteral("...")); - - // The button text won't be updated if we don't process events here - QApplication::processEvents(); - - // Avoid that the button press itself is registered as an event - Common::SleepCurrentThread(100); - - std::vector>> futures; - QString expr; + const auto default_device_qualifier = m_parent->GetController()->GetDefaultDevice(); - if (m_parent->GetParent()->IsMappingAllDevices()) - { - for (const std::string& device_str : g_controller_interface.GetAllDeviceStrings()) - { - ciface::Core::DeviceQualifier devq; - devq.FromString(device_str); - - auto dev = g_controller_interface.FindDevice(devq); - - auto future = std::async(std::launch::async, [this, devq, dev, device_str] { - return std::make_pair( - QString::fromStdString(device_str), - MappingCommon::DetectExpression(m_reference, dev.get(), - m_parent->GetController()->GetDefaultDevice())); - }); - - futures.push_back(std::move(future)); - } - - bool done = false; - - while (!done) - { - for (auto& future : futures) - { - const auto status = future.wait_for(std::chrono::milliseconds(10)); - if (status == std::future_status::ready) - { - const auto pair = future.get(); - - done = true; - - if (pair.second.isEmpty()) - break; - - expr = QStringLiteral("`%1:%2`") - .arg(pair.first) - .arg(pair.second.startsWith(QLatin1Char('`')) ? pair.second.mid(1) : - pair.second); - break; - } - } - } - } - else - { - const auto dev = m_parent->GetDevice(); - expr = MappingCommon::DetectExpression(m_reference, dev.get(), - m_parent->GetController()->GetDefaultDevice()); - } + QString expression; - releaseKeyboard(); - removeEventFilter(BlockUserInputFilter::Instance()); + if (m_parent->GetParent()->IsMappingAllDevices()) + { + expression = MappingCommon::DetectExpression(this, g_controller_interface, + g_controller_interface.GetAllDeviceStrings(), + default_device_qualifier); + } + else + { + expression = MappingCommon::DetectExpression(this, g_controller_interface, + {default_device_qualifier.ToString()}, + default_device_qualifier); + } - if (!expr.isEmpty()) - { - m_reference->SetExpression(expr.toStdString()); - m_parent->SaveSettings(); - Update(); - m_parent->GetController()->UpdateReferences(g_controller_interface); + if (expression.isEmpty()) + return; - if (m_parent->IsIterativeInput()) - m_parent->NextButton(this); - } - else - { - OnButtonTimeout(); - } - }); -} + m_reference->SetExpression(expression.toStdString()); + m_parent->SaveSettings(); + Update(); + m_parent->GetController()->UpdateReferences(g_controller_interface); -void MappingButton::OnButtonTimeout() -{ - setText(ToDisplayString(QString::fromStdString(m_reference->GetExpression()))); + if (m_parent->IsIterativeInput()) + m_parent->NextButton(this); } void MappingButton::Clear() diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingButton.h b/Source/Core/DolphinQt/Config/Mapping/MappingButton.h index 040b7fd75a9c..96847775c9fc 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingButton.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingButton.h @@ -30,7 +30,6 @@ class MappingButton : public ElidedButton private: void mouseReleaseEvent(QMouseEvent* event) override; - void OnButtonTimeout(); void Connect(); MappingWidget* m_parent; diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp index 4c1f230c4b82..dabc4d8530a2 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp @@ -4,16 +4,23 @@ #include "DolphinQt/Config/Mapping/MappingCommon.h" +#include + +#include +#include #include #include +#include "DolphinQt/QtUtils/BlockUserInputFilter.h" #include "InputCommon/ControlReference/ControlReference.h" #include "InputCommon/ControllerInterface/Device.h" +#include "Common/Thread.h" + namespace MappingCommon { constexpr int INPUT_DETECT_TIME = 3000; -constexpr int OUTPUT_DETECT_TIME = 2000; +constexpr int OUTPUT_TEST_TIME = 2000; QString GetExpressionForControl(const QString& control_name, const ciface::Core::DeviceQualifier& control_device, @@ -41,18 +48,56 @@ QString GetExpressionForControl(const QString& control_name, return expr; } -QString DetectExpression(ControlReference* reference, ciface::Core::Device* device, +QString DetectExpression(QPushButton* button, ciface::Core::DeviceContainer& device_container, + const std::vector& device_strings, const ciface::Core::DeviceQualifier& default_device, Quote quote) { - const int ms = reference->IsInput() ? INPUT_DETECT_TIME : OUTPUT_DETECT_TIME; + button->installEventFilter(BlockUserInputFilter::Instance()); + button->grabKeyboard(); + button->grabMouse(); - ciface::Core::Device::Control* const ctrl = reference->Detect(ms, device); + const auto old_text = button->text(); + button->setText(QStringLiteral("...")); - if (ctrl) - { - return MappingCommon::GetExpressionForControl(QString::fromStdString(ctrl->GetName()), - default_device, default_device, quote); - } - return QStringLiteral(""); + // The button text won't be updated if we don't process events here + QApplication::processEvents(); + + // Avoid that the button press itself is registered as an event + Common::SleepCurrentThread(100); + + std::shared_ptr device; + ciface::Core::Device::Input* input; + std::tie(device, input) = device_container.DetectInput(INPUT_DETECT_TIME, device_strings); + + button->releaseMouse(); + button->releaseKeyboard(); + button->removeEventFilter(BlockUserInputFilter::Instance()); + + button->setText(old_text); + + if (!input) + return {}; + + ciface::Core::DeviceQualifier device_qualifier; + device_qualifier.FromDevice(device.get()); + + return MappingCommon::GetExpressionForControl(QString::fromStdString(input->GetName()), + device_qualifier, default_device, quote); } + +void TestOutput(QPushButton* button, OutputReference* reference) +{ + const auto old_text = button->text(); + button->setText(QStringLiteral("...")); + + // The button text won't be updated if we don't process events here + QApplication::processEvents(); + + reference->State(1.0); + Common::SleepCurrentThread(OUTPUT_TEST_TIME); + reference->State(0.0); + + button->setText(old_text); +} + } // namespace MappingCommon diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.h b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.h index 1ee3ae1ead99..5e213b32215b 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.h @@ -4,17 +4,21 @@ #pragma once +#include +#include + class QString; -class ControlReference; +class OutputReference; +class QPushButton; namespace ciface { namespace Core { -class Device; +class DeviceContainer; class DeviceQualifier; -} -} +} // namespace Core +} // namespace ciface namespace MappingCommon { @@ -28,7 +32,12 @@ QString GetExpressionForControl(const QString& control_name, const ciface::Core::DeviceQualifier& control_device, const ciface::Core::DeviceQualifier& default_device, Quote quote = Quote::On); -QString DetectExpression(ControlReference* reference, ciface::Core::Device* device, + +QString DetectExpression(QPushButton* button, ciface::Core::DeviceContainer& device_container, + const std::vector& device_strings, const ciface::Core::DeviceQualifier& default_device, Quote quote = Quote::On); + +void TestOutput(QPushButton* button, OutputReference* reference); + } // namespace MappingCommon diff --git a/Source/Core/InputCommon/ControlReference/ControlReference.cpp b/Source/Core/InputCommon/ControlReference/ControlReference.cpp index 3132baf07b1d..1f392c4e3a35 100644 --- a/Source/Core/InputCommon/ControlReference/ControlReference.cpp +++ b/Source/Core/InputCommon/ControlReference/ControlReference.cpp @@ -4,9 +4,6 @@ #include "InputCommon/ControlReference/ControlReference.h" -#include - -#include "Common/Thread.h" // For InputGateOn() // This is a bad layering violation, but it's the cleanest // place I could find to put it. @@ -15,12 +12,6 @@ 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() { return SConfig::GetInstance().m_BackgroundInput || Host_RendererHasFocus() || @@ -115,83 +106,3 @@ ControlState OutputReference::State(const ControlState state) m_parsed_expression->SetValue(state * range); return 0.0; } - -// 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) -{ - struct InputState - { - 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()}); - } - - 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; -} - -// -// OutputReference :: Detect -// -// Totally different from the inputReference detect / I have them combined so it was simpler to make -// the GUI. -// The GUI doesn't know the difference between an input and an output / it's odd but I was lazy and -// it was easy -// -// set all binded outputs to power for x milliseconds return false -// -ciface::Core::Device::Control* OutputReference::Detect(const unsigned int ms, - ciface::Core::Device* const device) -{ - // ignore device - - // don't hang if we don't even have any controls mapped - if (BoundCount() > 0) - { - State(1); - unsigned int slept = 0; - - // this loop is to make stuff like flashing keyboard LEDs work - while (ms > (slept += 10)) - Common::SleepCurrentThread(10); - - State(0); - } - return nullptr; -} diff --git a/Source/Core/InputCommon/ControlReference/ControlReference.h b/Source/Core/InputCommon/ControlReference/ControlReference.h index 6740ed8be023..83fa2886768f 100644 --- a/Source/Core/InputCommon/ControlReference/ControlReference.h +++ b/Source/Core/InputCommon/ControlReference/ControlReference.h @@ -26,8 +26,6 @@ class ControlReference virtual ~ControlReference(); virtual ControlState State(const ControlState state = 0) = 0; - virtual ciface::Core::Device::Control* Detect(const unsigned int ms, - ciface::Core::Device* const device) = 0; virtual bool IsInput() const = 0; int BoundCount() const; @@ -57,8 +55,6 @@ class InputReference : public ControlReference InputReference(); bool IsInput() const override; ControlState State(const ControlState state) override; - ciface::Core::Device::Control* Detect(const unsigned int ms, - ciface::Core::Device* const device) override; }; // @@ -72,6 +68,4 @@ class OutputReference : public ControlReference OutputReference(); bool IsInput() const override; ControlState State(const ControlState state) override; - ciface::Core::Device::Control* Detect(const unsigned int ms, - ciface::Core::Device* const device) override; }; diff --git a/Source/Core/InputCommon/ControllerInterface/Device.cpp b/Source/Core/InputCommon/ControllerInterface/Device.cpp index efe1372711eb..14f5cb23d911 100644 --- a/Source/Core/InputCommon/ControllerInterface/Device.cpp +++ b/Source/Core/InputCommon/ControllerInterface/Device.cpp @@ -11,16 +11,15 @@ #include #include "Common/StringUtil.h" +#include "Common/Thread.h" namespace ciface { namespace Core { -// -// Device :: ~Device -// -// Destructor, delete all inputs/outputs on device destruction -// +// Compared to an input's current state (ideally 1.0) minus abs(initial_state) (ideally 0.0). +constexpr ControlState INPUT_DETECT_THRESHOLD = 0.55; + Device::~Device() { // delete inputs @@ -220,5 +219,83 @@ bool DeviceContainer::HasConnectedDevice(const DeviceQualifier& qualifier) const const auto device = FindDevice(qualifier); return device != nullptr && device->IsValid(); } + +// 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 the detected Device and Input, else return nullptrs +std::pair, Device::Input*> +DeviceContainer::DetectInput(u32 wait_ms, std::vector device_strings) +{ + struct InputState + { + ciface::Core::Device::Input& input; + ControlState initial_state; + }; + + struct DeviceState + { + std::shared_ptr device; + + std::vector input_states; + }; + + // Acquire devices and initial input states. + std::vector device_states; + for (auto& device_string : device_strings) + { + DeviceQualifier dq; + dq.FromString(device_string); + auto device = FindDevice(dq); + + if (!device) + continue; + + 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()}); + } + + if (!input_states.empty()) + device_states.emplace_back(DeviceState{std::move(device), std::move(input_states)}); + } + + if (device_states.empty()) + return {}; + + u32 time = 0; + while (time < wait_ms) + { + Common::SleepCurrentThread(10); + time += 10; + + for (auto& device_state : device_states) + { + device_state.device->UpdateInput(); + for (auto& input_state : device_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 {device_state.device, &input_state.input}; + } + } + } + + // No input was detected. :'( + return {}; +} + } // namespace Core } // namespace ciface diff --git a/Source/Core/InputCommon/ControllerInterface/Device.h b/Source/Core/InputCommon/ControllerInterface/Device.h index 0ee2b314447e..7d7623ba0311 100644 --- a/Source/Core/InputCommon/ControllerInterface/Device.h +++ b/Source/Core/InputCommon/ControllerInterface/Device.h @@ -172,6 +172,9 @@ class DeviceContainer bool HasConnectedDevice(const DeviceQualifier& qualifier) const; + std::pair, Device::Input*> + DetectInput(u32 wait_ms, std::vector device_strings); + protected: mutable std::mutex m_devices_mutex; std::vector> m_devices;