From 7e1df34735454636c25ed082b1e0be3c6bd2530a Mon Sep 17 00:00:00 2001 From: Shawn Hoffman Date: Tue, 15 Sep 2020 04:34:41 -0700 Subject: rename InputCommon/ControllerInterface/Device to CoreDevice --- .../ControllerInterface/ControllerInterface.h | 2 +- .../InputCommon/ControllerInterface/CoreDevice.cpp | 448 +++++++++++++++++++++ .../InputCommon/ControllerInterface/CoreDevice.h | 228 +++++++++++ .../ControllerInterface/DInput/DInputJoystick.h | 2 +- .../DInput/DInputKeyboardMouse.h | 2 +- .../InputCommon/ControllerInterface/Device.cpp | 448 --------------------- .../Core/InputCommon/ControllerInterface/Device.h | 228 ----------- .../ForceFeedback/ForceFeedbackDevice.h | 2 +- .../ControllerInterface/OSX/OSXJoystick.h | 2 +- .../Quartz/QuartzKeyboardAndMouse.h | 2 +- .../Core/InputCommon/ControllerInterface/SDL/SDL.h | 2 +- .../Wiimote/WiimoteController.h | 2 +- 12 files changed, 684 insertions(+), 684 deletions(-) create mode 100644 Source/Core/InputCommon/ControllerInterface/CoreDevice.cpp create mode 100644 Source/Core/InputCommon/ControllerInterface/CoreDevice.h delete mode 100644 Source/Core/InputCommon/ControllerInterface/Device.cpp delete mode 100644 Source/Core/InputCommon/ControllerInterface/Device.h (limited to 'Source/Core/InputCommon/ControllerInterface') diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h index 84209a720b..ffa189437f 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h @@ -12,7 +12,7 @@ #include "Common/Matrix.h" #include "Common/WindowSystemInfo.h" -#include "InputCommon/ControllerInterface/Device.h" +#include "InputCommon/ControllerInterface/CoreDevice.h" // enable disable sources #ifdef _WIN32 diff --git a/Source/Core/InputCommon/ControllerInterface/CoreDevice.cpp b/Source/Core/InputCommon/ControllerInterface/CoreDevice.cpp new file mode 100644 index 0000000000..3a396ddf37 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/CoreDevice.cpp @@ -0,0 +1,448 @@ +// Copyright 2013 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "InputCommon/ControllerInterface/CoreDevice.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "Common/MathUtil.h" +#include "Common/Thread.h" + +namespace ciface::Core +{ +// Compared to an input's current state (ideally 1.0) minus abs(initial_state) (ideally 0.0). +// Note: Detect() logic assumes this is greater than 0.5. +constexpr ControlState INPUT_DETECT_THRESHOLD = 0.55; + +class CombinedInput final : public Device::Input +{ +public: + using Inputs = std::pair; + + CombinedInput(std::string name, const Inputs& inputs) : m_name(std::move(name)), m_inputs(inputs) + { + } + ControlState GetState() const override + { + ControlState result = 0; + + if (m_inputs.first) + result = m_inputs.first->GetState(); + + if (m_inputs.second) + result = std::max(result, m_inputs.second->GetState()); + + return result; + } + std::string GetName() const override { return m_name; } + bool IsDetectable() const override { return false; } + bool IsChild(const Input* input) const override + { + return m_inputs.first == input || m_inputs.second == input; + } + +private: + const std::string m_name; + const std::pair m_inputs; +}; + +Device::~Device() +{ + // delete inputs + for (Device::Input* input : m_inputs) + delete input; + + // delete outputs + for (Device::Output* output : m_outputs) + delete output; +} + +std::optional Device::GetPreferredId() const +{ + return {}; +} + +void Device::AddInput(Device::Input* const i) +{ + m_inputs.push_back(i); +} + +void Device::AddOutput(Device::Output* const o) +{ + m_outputs.push_back(o); +} + +std::string Device::GetQualifiedName() const +{ + return fmt::format("{}/{}/{}", GetSource(), GetId(), GetName()); +} + +auto Device::GetParentMostInput(Input* child) const -> Input* +{ + for (auto* input : m_inputs) + { + if (input->IsChild(child)) + { + // Running recursively is currently unnecessary but it doesn't hurt. + return GetParentMostInput(input); + } + } + + return child; +} + +Device::Input* Device::FindInput(std::string_view name) const +{ + for (Input* input : m_inputs) + { + if (input->IsMatchingName(name)) + return input; + } + + return nullptr; +} + +Device::Output* Device::FindOutput(std::string_view name) const +{ + for (Output* output : m_outputs) + { + if (output->IsMatchingName(name)) + return output; + } + + return nullptr; +} + +bool Device::Control::IsMatchingName(std::string_view name) const +{ + return GetName() == name; +} + +ControlState Device::FullAnalogSurface::GetState() const +{ + return (1 + std::max(0.0, m_high.GetState()) - std::max(0.0, m_low.GetState())) / 2; +} + +std::string Device::FullAnalogSurface::GetName() const +{ + // E.g. "Full Axis X+" + return "Full " + m_high.GetName(); +} + +bool Device::FullAnalogSurface::IsMatchingName(std::string_view name) const +{ + if (Control::IsMatchingName(name)) + return true; + + // Old naming scheme was "Axis X-+" which is too visually similar to "Axis X+". + // This has caused countless problems for users with mysterious misconfigurations. + // We match this old name to support old configurations. + const auto old_name = m_low.GetName() + *m_high.GetName().rbegin(); + + return old_name == name; +} + +void Device::AddCombinedInput(std::string name, const std::pair& inputs) +{ + AddInput(new CombinedInput(std::move(name), {FindInput(inputs.first), FindInput(inputs.second)})); +} + +// +// DeviceQualifier :: ToString +// +// Get string from a device qualifier / serialize +// +std::string DeviceQualifier::ToString() const +{ + if (source.empty() && (cid < 0) && name.empty()) + return ""; + + std::ostringstream ss; + ss << source << '/'; + if (cid > -1) + ss << cid; + ss << '/' << name; + + return ss.str(); +} + +// +// DeviceQualifier :: FromString +// +// Set a device qualifier from a string / unserialize +// +void DeviceQualifier::FromString(const std::string& str) +{ + *this = {}; + + std::istringstream ss(str); + + std::getline(ss, source, '/'); + + // silly + std::getline(ss, name, '/'); + std::istringstream(name) >> cid; + + std::getline(ss, name); +} + +// +// DeviceQualifier :: FromDevice +// +// Set a device qualifier from a device +// +void DeviceQualifier::FromDevice(const Device* const dev) +{ + name = dev->GetName(); + cid = dev->GetId(); + source = dev->GetSource(); +} + +bool DeviceQualifier::operator==(const Device* const dev) const +{ + if (dev->GetId() == cid) + if (dev->GetName() == name) + if (dev->GetSource() == source) + return true; + + return false; +} + +bool DeviceQualifier::operator!=(const Device* const dev) const +{ + return !operator==(dev); +} + +bool DeviceQualifier::operator==(const DeviceQualifier& devq) const +{ + return std::tie(cid, name, source) == std::tie(devq.cid, devq.name, devq.source); +} + +bool DeviceQualifier::operator!=(const DeviceQualifier& devq) const +{ + return !operator==(devq); +} + +std::shared_ptr DeviceContainer::FindDevice(const DeviceQualifier& devq) const +{ + std::lock_guard lk(m_devices_mutex); + for (const auto& d : m_devices) + { + if (devq == d.get()) + return d; + } + + return nullptr; +} + +std::vector DeviceContainer::GetAllDeviceStrings() const +{ + std::lock_guard lk(m_devices_mutex); + + std::vector device_strings; + DeviceQualifier device_qualifier; + + for (const auto& d : m_devices) + { + device_qualifier.FromDevice(d.get()); + device_strings.emplace_back(device_qualifier.ToString()); + } + + return device_strings; +} + +std::string DeviceContainer::GetDefaultDeviceString() const +{ + std::lock_guard lk(m_devices_mutex); + if (m_devices.empty()) + return ""; + + DeviceQualifier device_qualifier; + device_qualifier.FromDevice(m_devices[0].get()); + return device_qualifier.ToString(); +} + +Device::Input* DeviceContainer::FindInput(std::string_view name, const Device* def_dev) const +{ + if (def_dev) + { + Device::Input* const inp = def_dev->FindInput(name); + if (inp) + return inp; + } + + std::lock_guard lk(m_devices_mutex); + for (const auto& d : m_devices) + { + Device::Input* const i = d->FindInput(name); + + if (i) + return i; + } + + return nullptr; +} + +Device::Output* DeviceContainer::FindOutput(std::string_view name, const Device* def_dev) const +{ + return def_dev->FindOutput(name); +} + +bool DeviceContainer::HasConnectedDevice(const DeviceQualifier& qualifier) const +{ + const auto device = FindDevice(qualifier); + return device != nullptr && device->IsValid(); +} + +// Wait for inputs on supplied devices. +// Inputs are only 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. +// Multiple detections are returned until the various timeouts have been reached. +auto DeviceContainer::DetectInput(const std::vector& device_strings, + std::chrono::milliseconds initial_wait, + std::chrono::milliseconds confirmation_wait, + std::chrono::milliseconds maximum_wait) const + -> std::vector +{ + struct InputState + { + InputState(ciface::Core::Device::Input* input_) : input{input_} { stats.Push(0.0); } + + ciface::Core::Device::Input* input; + ControlState initial_state = input->GetState(); + ControlState last_state = initial_state; + MathUtil::RunningVariance stats; + + // Prevent multiiple detections until after release. + bool is_ready = true; + + void Update() + { + const auto new_state = input->GetState(); + + if (!is_ready && new_state < (1 - INPUT_DETECT_THRESHOLD)) + { + last_state = new_state; + is_ready = true; + stats.Clear(); + } + + const auto difference = new_state - last_state; + stats.Push(difference); + last_state = new_state; + } + + bool IsPressed() + { + if (!is_ready) + return false; + + // We want an input that was initially 0.0 and currently 1.0. + const auto detection_score = (last_state - std::abs(initial_state)); + return detection_score > INPUT_DETECT_THRESHOLD; + } + }; + + struct DeviceState + { + std::shared_ptr device; + + std::vector input_states; + }; + + // Acquire devices and initial input states. + std::vector device_states; + for (const 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 positions, accelerometers, or gyroscopes. + if (!input->IsDetectable()) + continue; + + // Undesirable axes will have negative values here when trying to map a + // "FullAnalogSurface". + input_states.push_back(InputState{input}); + } + + if (!input_states.empty()) + device_states.emplace_back(DeviceState{std::move(device), std::move(input_states)}); + } + + if (device_states.empty()) + return {}; + + std::vector detections; + + const auto start_time = Clock::now(); + while (true) + { + const auto now = Clock::now(); + const auto elapsed_time = now - start_time; + + if (elapsed_time >= maximum_wait || (detections.empty() && elapsed_time >= initial_wait) || + (!detections.empty() && detections.back().release_time.has_value() && + now >= *detections.back().release_time + confirmation_wait)) + { + break; + } + + Common::SleepCurrentThread(10); + + for (auto& device_state : device_states) + { + for (std::size_t i = 0; i != device_state.input_states.size(); ++i) + { + auto& input_state = device_state.input_states[i]; + input_state.Update(); + + if (input_state.IsPressed()) + { + input_state.is_ready = false; + + // Digital presses will evaluate as 1 here. + // Analog presses will evaluate greater than 1. + const auto smoothness = + 1 / std::sqrt(input_state.stats.Variance() / input_state.stats.Mean()); + + InputDetection new_detection; + new_detection.device = device_state.device; + new_detection.input = input_state.input; + new_detection.press_time = Clock::now(); + new_detection.smoothness = smoothness; + + // We found an input. Add it to our detections. + detections.emplace_back(std::move(new_detection)); + } + } + } + + // Check for any releases of our detected inputs. + for (auto& d : detections) + { + if (!d.release_time.has_value() && d.input->GetState() < (1 - INPUT_DETECT_THRESHOLD)) + d.release_time = Clock::now(); + } + } + + return detections; +} +} // namespace ciface::Core diff --git a/Source/Core/InputCommon/ControllerInterface/CoreDevice.h b/Source/Core/InputCommon/ControllerInterface/CoreDevice.h new file mode 100644 index 0000000000..2612f59627 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/CoreDevice.h @@ -0,0 +1,228 @@ +// Copyright 2013 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "Common/CommonTypes.h" + +// idk in case I wanted to change it to double or something, idk what's best +typedef double ControlState; + +namespace ciface +{ +// 100Hz which homebrew docs very roughly imply is within WiiMote normal +// range, used for periodic haptic effects though often ignored by devices +// TODO: Make this configurable. +constexpr int RUMBLE_PERIOD_MS = 10; + +// This needs to be at least as long as the longest rumble that might ever be played. +// Too short and it's going to stop in the middle of a long effect. +// Infinite values are invalid for ramp effects and probably not sensible. +constexpr int RUMBLE_LENGTH_MS = 1000 * 10; + +// All inputs (other than accel/gyro) return 1.0 as their maximum value. +// Battery inputs will almost always be mapped to the "Battery" setting which is a percentage. +// If someone actually wants to map a battery input to a regular control they can divide by 100. +// I think this is better than requiring multiplication by 100 for the most common usage. +constexpr ControlState BATTERY_INPUT_MAX_VALUE = 100.0; + +namespace Core +{ +class Device +{ +public: + class Input; + class Output; + + // + // Control + // + // Control includes inputs and outputs + // + class Control // input or output + { + public: + virtual ~Control() = default; + virtual std::string GetName() const = 0; + virtual Input* ToInput() { return nullptr; } + virtual Output* ToOutput() { return nullptr; } + + // May be overridden to allow multiple valid names. + // Useful for backwards-compatible configurations when names change. + virtual bool IsMatchingName(std::string_view name) const; + }; + + // + // Input + // + // An input on a device + // + class Input : public Control + { + public: + // Things like absolute axes/ absolute mouse position should override this to prevent + // undesirable behavior in our mapping logic. + virtual bool IsDetectable() const { 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; } + + // Overridden by CombinedInput, + // so hotkey logic knows Ctrl, L_Ctrl, and R_Ctrl are the same, + // and so input detection can return the parent name. + virtual bool IsChild(const Input*) const { return false; } + }; + + // + // Output + // + // An output on a device + // + class Output : public Control + { + public: + virtual ~Output() = default; + virtual void SetState(ControlState state) = 0; + Output* ToOutput() override { return this; } + }; + + virtual ~Device(); + + int GetId() const { return m_id; } + void SetId(int id) { m_id = id; } + virtual std::string GetName() const = 0; + virtual std::string GetSource() const = 0; + std::string GetQualifiedName() const; + virtual void UpdateInput() {} + + // May be overridden to implement hotplug removal. + // Currently handled on a per-backend basis but this could change. + virtual bool IsValid() const { return true; } + + // (e.g. Xbox 360 controllers have controller number LEDs which should match the ID we use.) + virtual std::optional GetPreferredId() const; + + const std::vector& Inputs() const { return m_inputs; } + const std::vector& Outputs() const { return m_outputs; } + + Input* GetParentMostInput(Input* input) const; + + Input* FindInput(std::string_view name) const; + Output* FindOutput(std::string_view name) const; + +protected: + void AddInput(Input* const i); + void AddOutput(Output* const o); + + class FullAnalogSurface final : public Input + { + public: + FullAnalogSurface(Input* low, Input* high) : m_low(*low), m_high(*high) {} + ControlState GetState() const override; + std::string GetName() const override; + bool IsMatchingName(std::string_view name) const override; + + private: + Input& m_low; + Input& m_high; + }; + + void AddAnalogInputs(Input* low, Input* high) + { + AddInput(low); + AddInput(high); + AddInput(new FullAnalogSurface(low, high)); + AddInput(new FullAnalogSurface(high, low)); + } + + void AddCombinedInput(std::string name, const std::pair& inputs); + +private: + int m_id; + std::vector m_inputs; + std::vector m_outputs; +}; + +// +// DeviceQualifier +// +// Device qualifier used to match devices. +// Currently has ( source, id, name ) properties which match a device +// +class DeviceQualifier +{ +public: + DeviceQualifier() : cid(-1) {} + DeviceQualifier(std::string source_, const int id_, std::string name_) + : source(std::move(source_)), cid(id_), name(std::move(name_)) + { + } + void FromDevice(const Device* const dev); + void FromString(const std::string& str); + std::string ToString() const; + + bool operator==(const DeviceQualifier& devq) const; + bool operator!=(const DeviceQualifier& devq) const; + + bool operator==(const Device* dev) const; + bool operator!=(const Device* dev) const; + + std::string source; + int cid; + std::string name; +}; + +class DeviceContainer +{ +public: + using Clock = std::chrono::steady_clock; + + struct InputDetection + { + std::shared_ptr device; + Device::Input* input; + Clock::time_point press_time; + std::optional release_time; + ControlState smoothness; + }; + + Device::Input* FindInput(std::string_view name, const Device* def_dev) const; + Device::Output* FindOutput(std::string_view name, const Device* def_dev) const; + + std::vector GetAllDeviceStrings() const; + std::string GetDefaultDeviceString() const; + std::shared_ptr FindDevice(const DeviceQualifier& devq) const; + + bool HasConnectedDevice(const DeviceQualifier& qualifier) const; + + std::vector DetectInput(const std::vector& device_strings, + std::chrono::milliseconds initial_wait, + std::chrono::milliseconds confirmation_wait, + std::chrono::milliseconds maximum_wait) const; + +protected: + mutable std::recursive_mutex m_devices_mutex; + std::vector> m_devices; +}; +} // namespace Core +} // namespace ciface diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.h b/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.h index 70e8af8e6e..fe0f46424f 100644 --- a/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.h +++ b/Source/Core/InputCommon/ControllerInterface/DInput/DInputJoystick.h @@ -4,7 +4,7 @@ #pragma once -#include "InputCommon/ControllerInterface/Device.h" +#include "InputCommon/ControllerInterface/CoreDevice.h" #include "InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h" namespace ciface::DInput diff --git a/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.h b/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.h index 0ae81fb6be..c3834df528 100644 --- a/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.h +++ b/Source/Core/InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.h @@ -7,8 +7,8 @@ #include #include "Common/Matrix.h" +#include "InputCommon/ControllerInterface/CoreDevice.h" #include "InputCommon/ControllerInterface/DInput/DInput8.h" -#include "InputCommon/ControllerInterface/Device.h" namespace ciface::DInput { diff --git a/Source/Core/InputCommon/ControllerInterface/Device.cpp b/Source/Core/InputCommon/ControllerInterface/Device.cpp deleted file mode 100644 index 2fd7fb02e7..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/Device.cpp +++ /dev/null @@ -1,448 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include "InputCommon/ControllerInterface/Device.h" - -#include -#include -#include -#include -#include -#include - -#include - -#include "Common/MathUtil.h" -#include "Common/Thread.h" - -namespace ciface::Core -{ -// Compared to an input's current state (ideally 1.0) minus abs(initial_state) (ideally 0.0). -// Note: Detect() logic assumes this is greater than 0.5. -constexpr ControlState INPUT_DETECT_THRESHOLD = 0.55; - -class CombinedInput final : public Device::Input -{ -public: - using Inputs = std::pair; - - CombinedInput(std::string name, const Inputs& inputs) : m_name(std::move(name)), m_inputs(inputs) - { - } - ControlState GetState() const override - { - ControlState result = 0; - - if (m_inputs.first) - result = m_inputs.first->GetState(); - - if (m_inputs.second) - result = std::max(result, m_inputs.second->GetState()); - - return result; - } - std::string GetName() const override { return m_name; } - bool IsDetectable() const override { return false; } - bool IsChild(const Input* input) const override - { - return m_inputs.first == input || m_inputs.second == input; - } - -private: - const std::string m_name; - const std::pair m_inputs; -}; - -Device::~Device() -{ - // delete inputs - for (Device::Input* input : m_inputs) - delete input; - - // delete outputs - for (Device::Output* output : m_outputs) - delete output; -} - -std::optional Device::GetPreferredId() const -{ - return {}; -} - -void Device::AddInput(Device::Input* const i) -{ - m_inputs.push_back(i); -} - -void Device::AddOutput(Device::Output* const o) -{ - m_outputs.push_back(o); -} - -std::string Device::GetQualifiedName() const -{ - return fmt::format("{}/{}/{}", GetSource(), GetId(), GetName()); -} - -auto Device::GetParentMostInput(Input* child) const -> Input* -{ - for (auto* input : m_inputs) - { - if (input->IsChild(child)) - { - // Running recursively is currently unnecessary but it doesn't hurt. - return GetParentMostInput(input); - } - } - - return child; -} - -Device::Input* Device::FindInput(std::string_view name) const -{ - for (Input* input : m_inputs) - { - if (input->IsMatchingName(name)) - return input; - } - - return nullptr; -} - -Device::Output* Device::FindOutput(std::string_view name) const -{ - for (Output* output : m_outputs) - { - if (output->IsMatchingName(name)) - return output; - } - - return nullptr; -} - -bool Device::Control::IsMatchingName(std::string_view name) const -{ - return GetName() == name; -} - -ControlState Device::FullAnalogSurface::GetState() const -{ - return (1 + std::max(0.0, m_high.GetState()) - std::max(0.0, m_low.GetState())) / 2; -} - -std::string Device::FullAnalogSurface::GetName() const -{ - // E.g. "Full Axis X+" - return "Full " + m_high.GetName(); -} - -bool Device::FullAnalogSurface::IsMatchingName(std::string_view name) const -{ - if (Control::IsMatchingName(name)) - return true; - - // Old naming scheme was "Axis X-+" which is too visually similar to "Axis X+". - // This has caused countless problems for users with mysterious misconfigurations. - // We match this old name to support old configurations. - const auto old_name = m_low.GetName() + *m_high.GetName().rbegin(); - - return old_name == name; -} - -void Device::AddCombinedInput(std::string name, const std::pair& inputs) -{ - AddInput(new CombinedInput(std::move(name), {FindInput(inputs.first), FindInput(inputs.second)})); -} - -// -// DeviceQualifier :: ToString -// -// Get string from a device qualifier / serialize -// -std::string DeviceQualifier::ToString() const -{ - if (source.empty() && (cid < 0) && name.empty()) - return ""; - - std::ostringstream ss; - ss << source << '/'; - if (cid > -1) - ss << cid; - ss << '/' << name; - - return ss.str(); -} - -// -// DeviceQualifier :: FromString -// -// Set a device qualifier from a string / unserialize -// -void DeviceQualifier::FromString(const std::string& str) -{ - *this = {}; - - std::istringstream ss(str); - - std::getline(ss, source, '/'); - - // silly - std::getline(ss, name, '/'); - std::istringstream(name) >> cid; - - std::getline(ss, name); -} - -// -// DeviceQualifier :: FromDevice -// -// Set a device qualifier from a device -// -void DeviceQualifier::FromDevice(const Device* const dev) -{ - name = dev->GetName(); - cid = dev->GetId(); - source = dev->GetSource(); -} - -bool DeviceQualifier::operator==(const Device* const dev) const -{ - if (dev->GetId() == cid) - if (dev->GetName() == name) - if (dev->GetSource() == source) - return true; - - return false; -} - -bool DeviceQualifier::operator!=(const Device* const dev) const -{ - return !operator==(dev); -} - -bool DeviceQualifier::operator==(const DeviceQualifier& devq) const -{ - return std::tie(cid, name, source) == std::tie(devq.cid, devq.name, devq.source); -} - -bool DeviceQualifier::operator!=(const DeviceQualifier& devq) const -{ - return !operator==(devq); -} - -std::shared_ptr DeviceContainer::FindDevice(const DeviceQualifier& devq) const -{ - std::lock_guard lk(m_devices_mutex); - for (const auto& d : m_devices) - { - if (devq == d.get()) - return d; - } - - return nullptr; -} - -std::vector DeviceContainer::GetAllDeviceStrings() const -{ - std::lock_guard lk(m_devices_mutex); - - std::vector device_strings; - DeviceQualifier device_qualifier; - - for (const auto& d : m_devices) - { - device_qualifier.FromDevice(d.get()); - device_strings.emplace_back(device_qualifier.ToString()); - } - - return device_strings; -} - -std::string DeviceContainer::GetDefaultDeviceString() const -{ - std::lock_guard lk(m_devices_mutex); - if (m_devices.empty()) - return ""; - - DeviceQualifier device_qualifier; - device_qualifier.FromDevice(m_devices[0].get()); - return device_qualifier.ToString(); -} - -Device::Input* DeviceContainer::FindInput(std::string_view name, const Device* def_dev) const -{ - if (def_dev) - { - Device::Input* const inp = def_dev->FindInput(name); - if (inp) - return inp; - } - - std::lock_guard lk(m_devices_mutex); - for (const auto& d : m_devices) - { - Device::Input* const i = d->FindInput(name); - - if (i) - return i; - } - - return nullptr; -} - -Device::Output* DeviceContainer::FindOutput(std::string_view name, const Device* def_dev) const -{ - return def_dev->FindOutput(name); -} - -bool DeviceContainer::HasConnectedDevice(const DeviceQualifier& qualifier) const -{ - const auto device = FindDevice(qualifier); - return device != nullptr && device->IsValid(); -} - -// Wait for inputs on supplied devices. -// Inputs are only 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. -// Multiple detections are returned until the various timeouts have been reached. -auto DeviceContainer::DetectInput(const std::vector& device_strings, - std::chrono::milliseconds initial_wait, - std::chrono::milliseconds confirmation_wait, - std::chrono::milliseconds maximum_wait) const - -> std::vector -{ - struct InputState - { - InputState(ciface::Core::Device::Input* input_) : input{input_} { stats.Push(0.0); } - - ciface::Core::Device::Input* input; - ControlState initial_state = input->GetState(); - ControlState last_state = initial_state; - MathUtil::RunningVariance stats; - - // Prevent multiiple detections until after release. - bool is_ready = true; - - void Update() - { - const auto new_state = input->GetState(); - - if (!is_ready && new_state < (1 - INPUT_DETECT_THRESHOLD)) - { - last_state = new_state; - is_ready = true; - stats.Clear(); - } - - const auto difference = new_state - last_state; - stats.Push(difference); - last_state = new_state; - } - - bool IsPressed() - { - if (!is_ready) - return false; - - // We want an input that was initially 0.0 and currently 1.0. - const auto detection_score = (last_state - std::abs(initial_state)); - return detection_score > INPUT_DETECT_THRESHOLD; - } - }; - - struct DeviceState - { - std::shared_ptr device; - - std::vector input_states; - }; - - // Acquire devices and initial input states. - std::vector device_states; - for (const 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 positions, accelerometers, or gyroscopes. - if (!input->IsDetectable()) - continue; - - // Undesirable axes will have negative values here when trying to map a - // "FullAnalogSurface". - input_states.push_back(InputState{input}); - } - - if (!input_states.empty()) - device_states.emplace_back(DeviceState{std::move(device), std::move(input_states)}); - } - - if (device_states.empty()) - return {}; - - std::vector detections; - - const auto start_time = Clock::now(); - while (true) - { - const auto now = Clock::now(); - const auto elapsed_time = now - start_time; - - if (elapsed_time >= maximum_wait || (detections.empty() && elapsed_time >= initial_wait) || - (!detections.empty() && detections.back().release_time.has_value() && - now >= *detections.back().release_time + confirmation_wait)) - { - break; - } - - Common::SleepCurrentThread(10); - - for (auto& device_state : device_states) - { - for (std::size_t i = 0; i != device_state.input_states.size(); ++i) - { - auto& input_state = device_state.input_states[i]; - input_state.Update(); - - if (input_state.IsPressed()) - { - input_state.is_ready = false; - - // Digital presses will evaluate as 1 here. - // Analog presses will evaluate greater than 1. - const auto smoothness = - 1 / std::sqrt(input_state.stats.Variance() / input_state.stats.Mean()); - - InputDetection new_detection; - new_detection.device = device_state.device; - new_detection.input = input_state.input; - new_detection.press_time = Clock::now(); - new_detection.smoothness = smoothness; - - // We found an input. Add it to our detections. - detections.emplace_back(std::move(new_detection)); - } - } - } - - // Check for any releases of our detected inputs. - for (auto& d : detections) - { - if (!d.release_time.has_value() && d.input->GetState() < (1 - INPUT_DETECT_THRESHOLD)) - d.release_time = Clock::now(); - } - } - - return detections; -} -} // namespace ciface::Core diff --git a/Source/Core/InputCommon/ControllerInterface/Device.h b/Source/Core/InputCommon/ControllerInterface/Device.h deleted file mode 100644 index 2612f59627..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/Device.h +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include "Common/CommonTypes.h" - -// idk in case I wanted to change it to double or something, idk what's best -typedef double ControlState; - -namespace ciface -{ -// 100Hz which homebrew docs very roughly imply is within WiiMote normal -// range, used for periodic haptic effects though often ignored by devices -// TODO: Make this configurable. -constexpr int RUMBLE_PERIOD_MS = 10; - -// This needs to be at least as long as the longest rumble that might ever be played. -// Too short and it's going to stop in the middle of a long effect. -// Infinite values are invalid for ramp effects and probably not sensible. -constexpr int RUMBLE_LENGTH_MS = 1000 * 10; - -// All inputs (other than accel/gyro) return 1.0 as their maximum value. -// Battery inputs will almost always be mapped to the "Battery" setting which is a percentage. -// If someone actually wants to map a battery input to a regular control they can divide by 100. -// I think this is better than requiring multiplication by 100 for the most common usage. -constexpr ControlState BATTERY_INPUT_MAX_VALUE = 100.0; - -namespace Core -{ -class Device -{ -public: - class Input; - class Output; - - // - // Control - // - // Control includes inputs and outputs - // - class Control // input or output - { - public: - virtual ~Control() = default; - virtual std::string GetName() const = 0; - virtual Input* ToInput() { return nullptr; } - virtual Output* ToOutput() { return nullptr; } - - // May be overridden to allow multiple valid names. - // Useful for backwards-compatible configurations when names change. - virtual bool IsMatchingName(std::string_view name) const; - }; - - // - // Input - // - // An input on a device - // - class Input : public Control - { - public: - // Things like absolute axes/ absolute mouse position should override this to prevent - // undesirable behavior in our mapping logic. - virtual bool IsDetectable() const { 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; } - - // Overridden by CombinedInput, - // so hotkey logic knows Ctrl, L_Ctrl, and R_Ctrl are the same, - // and so input detection can return the parent name. - virtual bool IsChild(const Input*) const { return false; } - }; - - // - // Output - // - // An output on a device - // - class Output : public Control - { - public: - virtual ~Output() = default; - virtual void SetState(ControlState state) = 0; - Output* ToOutput() override { return this; } - }; - - virtual ~Device(); - - int GetId() const { return m_id; } - void SetId(int id) { m_id = id; } - virtual std::string GetName() const = 0; - virtual std::string GetSource() const = 0; - std::string GetQualifiedName() const; - virtual void UpdateInput() {} - - // May be overridden to implement hotplug removal. - // Currently handled on a per-backend basis but this could change. - virtual bool IsValid() const { return true; } - - // (e.g. Xbox 360 controllers have controller number LEDs which should match the ID we use.) - virtual std::optional GetPreferredId() const; - - const std::vector& Inputs() const { return m_inputs; } - const std::vector& Outputs() const { return m_outputs; } - - Input* GetParentMostInput(Input* input) const; - - Input* FindInput(std::string_view name) const; - Output* FindOutput(std::string_view name) const; - -protected: - void AddInput(Input* const i); - void AddOutput(Output* const o); - - class FullAnalogSurface final : public Input - { - public: - FullAnalogSurface(Input* low, Input* high) : m_low(*low), m_high(*high) {} - ControlState GetState() const override; - std::string GetName() const override; - bool IsMatchingName(std::string_view name) const override; - - private: - Input& m_low; - Input& m_high; - }; - - void AddAnalogInputs(Input* low, Input* high) - { - AddInput(low); - AddInput(high); - AddInput(new FullAnalogSurface(low, high)); - AddInput(new FullAnalogSurface(high, low)); - } - - void AddCombinedInput(std::string name, const std::pair& inputs); - -private: - int m_id; - std::vector m_inputs; - std::vector m_outputs; -}; - -// -// DeviceQualifier -// -// Device qualifier used to match devices. -// Currently has ( source, id, name ) properties which match a device -// -class DeviceQualifier -{ -public: - DeviceQualifier() : cid(-1) {} - DeviceQualifier(std::string source_, const int id_, std::string name_) - : source(std::move(source_)), cid(id_), name(std::move(name_)) - { - } - void FromDevice(const Device* const dev); - void FromString(const std::string& str); - std::string ToString() const; - - bool operator==(const DeviceQualifier& devq) const; - bool operator!=(const DeviceQualifier& devq) const; - - bool operator==(const Device* dev) const; - bool operator!=(const Device* dev) const; - - std::string source; - int cid; - std::string name; -}; - -class DeviceContainer -{ -public: - using Clock = std::chrono::steady_clock; - - struct InputDetection - { - std::shared_ptr device; - Device::Input* input; - Clock::time_point press_time; - std::optional release_time; - ControlState smoothness; - }; - - Device::Input* FindInput(std::string_view name, const Device* def_dev) const; - Device::Output* FindOutput(std::string_view name, const Device* def_dev) const; - - std::vector GetAllDeviceStrings() const; - std::string GetDefaultDeviceString() const; - std::shared_ptr FindDevice(const DeviceQualifier& devq) const; - - bool HasConnectedDevice(const DeviceQualifier& qualifier) const; - - std::vector DetectInput(const std::vector& device_strings, - std::chrono::milliseconds initial_wait, - std::chrono::milliseconds confirmation_wait, - std::chrono::milliseconds maximum_wait) const; - -protected: - mutable std::recursive_mutex m_devices_mutex; - std::vector> m_devices; -}; -} // namespace Core -} // namespace ciface diff --git a/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h b/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h index c689cef5a5..8fab93c429 100644 --- a/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h +++ b/Source/Core/InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h @@ -10,7 +10,7 @@ #include "Common/Event.h" #include "Common/Flag.h" -#include "InputCommon/ControllerInterface/Device.h" +#include "InputCommon/ControllerInterface/CoreDevice.h" #ifdef _WIN32 #include diff --git a/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.h b/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.h index 377f7d9b29..89552a80de 100644 --- a/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.h +++ b/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.h @@ -8,7 +8,7 @@ #include -#include "InputCommon/ControllerInterface/Device.h" +#include "InputCommon/ControllerInterface/CoreDevice.h" #include "InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h" namespace ciface::OSX diff --git a/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h b/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h index 39516035a3..740954082b 100644 --- a/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h +++ b/Source/Core/InputCommon/ControllerInterface/Quartz/QuartzKeyboardAndMouse.h @@ -7,7 +7,7 @@ #include #include "Common/Matrix.h" -#include "InputCommon/ControllerInterface/Device.h" +#include "InputCommon/ControllerInterface/CoreDevice.h" namespace ciface::Quartz { diff --git a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.h b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.h index e9ac021bd9..38b6bf3cfb 100644 --- a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.h +++ b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.h @@ -14,7 +14,7 @@ #include #endif -#include "InputCommon/ControllerInterface/Device.h" +#include "InputCommon/ControllerInterface/CoreDevice.h" namespace ciface::SDL { diff --git a/Source/Core/InputCommon/ControllerInterface/Wiimote/WiimoteController.h b/Source/Core/InputCommon/ControllerInterface/Wiimote/WiimoteController.h index 53abc0bf3f..4864e0abb3 100644 --- a/Source/Core/InputCommon/ControllerInterface/Wiimote/WiimoteController.h +++ b/Source/Core/InputCommon/ControllerInterface/Wiimote/WiimoteController.h @@ -16,7 +16,7 @@ #include "Core/HW/WiimoteEmu/Extension/Nunchuk.h" #include "Core/HW/WiimoteEmu/MotionPlus.h" #include "Core/HW/WiimoteReal/WiimoteReal.h" -#include "InputCommon/ControllerInterface/Device.h" +#include "InputCommon/ControllerInterface/CoreDevice.h" namespace ciface::WiimoteController { -- cgit v1.2.3