diff options
Diffstat (limited to 'src/input_common')
| -rw-r--r-- | src/input_common/CMakeLists.txt | 6 | ||||
| -rw-r--r-- | src/input_common/gcadapter/gc_adapter.cpp | 8 | ||||
| -rw-r--r-- | src/input_common/gcadapter/gc_adapter.h | 2 | ||||
| -rw-r--r-- | src/input_common/gcadapter/gc_poller.cpp | 40 | ||||
| -rw-r--r-- | src/input_common/main.cpp | 212 | ||||
| -rw-r--r-- | src/input_common/main.h | 132 | ||||
| -rw-r--r-- | src/input_common/motion_input.cpp | 181 | ||||
| -rw-r--r-- | src/input_common/motion_input.h | 68 | ||||
| -rw-r--r-- | src/input_common/sdl/sdl.h | 19 | ||||
| -rw-r--r-- | src/input_common/sdl/sdl_impl.cpp | 438 | ||||
| -rw-r--r-- | src/input_common/sdl/sdl_impl.h | 8 | ||||
| -rw-r--r-- | src/input_common/settings.cpp | 33 | ||||
| -rw-r--r-- | src/input_common/settings.h | 335 | ||||
| -rw-r--r-- | src/input_common/touch_from_button.cpp | 50 | ||||
| -rw-r--r-- | src/input_common/touch_from_button.h | 23 | ||||
| -rw-r--r-- | src/input_common/udp/client.cpp | 7 | ||||
| -rw-r--r-- | src/input_common/udp/udp.cpp | 14 | ||||
| -rw-r--r-- | src/input_common/udp/udp.h | 7 | 
18 files changed, 1349 insertions, 234 deletions
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 317c25bad..09361e37e 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -7,6 +7,12 @@ add_library(input_common STATIC      main.h      motion_emu.cpp      motion_emu.h +    motion_input.cpp +    motion_input.h +    settings.cpp +    settings.h +    touch_from_button.cpp +    touch_from_button.h      gcadapter/gc_adapter.cpp      gcadapter/gc_adapter.h      gcadapter/gc_poller.cpp diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp index 02d06876f..c6c423c4b 100644 --- a/src/input_common/gcadapter/gc_adapter.cpp +++ b/src/input_common/gcadapter/gc_adapter.cpp @@ -254,7 +254,7 @@ void Adapter::GetGCEndpoint(libusb_device* device) {                                sizeof(clear_payload), nullptr, 16);      adapter_thread_running = true; -    adapter_input_thread = std::thread([=] { Read(); }); // Read input +    adapter_input_thread = std::thread(&Adapter::Read, this);  }  Adapter::~Adapter() { @@ -265,7 +265,9 @@ void Adapter::Reset() {      if (adapter_thread_running) {          adapter_thread_running = false;      } -    adapter_input_thread.join(); +    if (adapter_input_thread.joinable()) { +        adapter_input_thread.join(); +    }      adapter_controllers_status.fill(ControllerTypes::None);      get_origin.fill(true); @@ -281,7 +283,7 @@ void Adapter::Reset() {      }  } -bool Adapter::DeviceConnected(std::size_t port) { +bool Adapter::DeviceConnected(std::size_t port) const {      return adapter_controllers_status[port] != ControllerTypes::None;  } diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h index bed81915c..20e97d283 100644 --- a/src/input_common/gcadapter/gc_adapter.h +++ b/src/input_common/gcadapter/gc_adapter.h @@ -76,7 +76,7 @@ public:      void EndConfiguration();      /// Returns true if there is a device connected to port -    bool DeviceConnected(std::size_t port); +    bool DeviceConnected(std::size_t port) const;      std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue();      const std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue() const; diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp index 96e22d3ad..92e9e8e89 100644 --- a/src/input_common/gcadapter/gc_poller.cpp +++ b/src/input_common/gcadapter/gc_poller.cpp @@ -15,7 +15,7 @@ namespace InputCommon {  class GCButton final : public Input::ButtonDevice {  public: -    explicit GCButton(int port_, int button_, GCAdapter::Adapter* adapter) +    explicit GCButton(int port_, int button_, const GCAdapter::Adapter* adapter)          : port(port_), button(button_), gcadapter(adapter) {}      ~GCButton() override; @@ -30,15 +30,16 @@ public:  private:      const int port;      const int button; -    GCAdapter::Adapter* gcadapter; +    const GCAdapter::Adapter* gcadapter;  };  class GCAxisButton final : public Input::ButtonDevice {  public:      explicit GCAxisButton(int port_, int axis_, float threshold_, bool trigger_if_greater_, -                          GCAdapter::Adapter* adapter) +                          const GCAdapter::Adapter* adapter)          : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_), -          gcadapter(adapter), origin_value(adapter->GetOriginValue(port_, axis_)) {} +          gcadapter(adapter), +          origin_value(static_cast<float>(adapter->GetOriginValue(port_, axis_))) {}      bool GetStatus() const override {          if (gcadapter->DeviceConnected(port)) { @@ -59,7 +60,7 @@ private:      const int axis;      float threshold;      bool trigger_if_greater; -    GCAdapter::Adapter* gcadapter; +    const GCAdapter::Adapter* gcadapter;      const float origin_value;  }; @@ -76,8 +77,7 @@ std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::Param      // button is not an axis/stick button      if (button_id != PAD_STICK_ID) { -        auto button = std::make_unique<GCButton>(port, button_id, adapter.get()); -        return std::move(button); +        return std::make_unique<GCButton>(port, button_id, adapter.get());      }      // For Axis buttons, used by the binary sticks. @@ -149,19 +149,18 @@ void GCButtonFactory::EndConfiguration() {  class GCAnalog final : public Input::AnalogDevice {  public: -    GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_, GCAdapter::Adapter* adapter) +    GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_, +             const GCAdapter::Adapter* adapter, float range_)          : port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter), -          origin_value_x(adapter->GetOriginValue(port_, axis_x_)), -          origin_value_y(adapter->GetOriginValue(port_, axis_y_)) {} +          origin_value_x(static_cast<float>(adapter->GetOriginValue(port_, axis_x_))), +          origin_value_y(static_cast<float>(adapter->GetOriginValue(port_, axis_y_))), +          range(range_) {}      float GetAxis(int axis) const {          if (gcadapter->DeviceConnected(port)) {              std::lock_guard lock{mutex};              const auto origin_value = axis % 2 == 0 ? origin_value_x : origin_value_y; -            // division is not by a perfect 128 to account for some variance in center location -            // e.g. my device idled at 131 in X, 120 in Y, and full range of motion was in range -            // [20-230] -            return (gcadapter->GetPadState()[port].axes.at(axis) - origin_value) / 95.0f; +            return (gcadapter->GetPadState()[port].axes.at(axis) - origin_value) / (100.0f * range);          }          return 0.0f;      } @@ -194,7 +193,7 @@ public:      bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {          const auto [x, y] = GetStatus(); -        const float directional_deadzone = 0.4f; +        const float directional_deadzone = 0.5f;          switch (direction) {          case Input::AnalogDirection::RIGHT:              return x > directional_deadzone; @@ -213,9 +212,10 @@ private:      const int axis_x;      const int axis_y;      const float deadzone; -    GCAdapter::Adapter* gcadapter; +    const GCAdapter::Adapter* gcadapter;      const float origin_value_x;      const float origin_value_y; +    const float range;      mutable std::mutex mutex;  }; @@ -234,9 +234,10 @@ std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::Param      const int port = params.Get("port", 0);      const int axis_x = params.Get("axis_x", 0);      const int axis_y = params.Get("axis_y", 1); -    const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f); +    const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); +    const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f); -    return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone, adapter.get()); +    return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone, adapter.get(), range);  }  void GCAnalogFactory::BeginConfiguration() { @@ -264,7 +265,8 @@ Common::ParamPackage GCAnalogFactory::GetNextInput() {              if (analog_x_axis == -1) {                  analog_x_axis = axis;                  controller_number = static_cast<int>(port); -            } else if (analog_y_axis == -1 && analog_x_axis != axis && controller_number == port) { +            } else if (analog_y_axis == -1 && analog_x_axis != axis && +                       controller_number == static_cast<int>(port)) {                  analog_y_axis = axis;              }          } diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index b9d5d0ec3..ea1a1cee6 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -11,6 +11,7 @@  #include "input_common/keyboard.h"  #include "input_common/main.h"  #include "input_common/motion_emu.h" +#include "input_common/touch_from_button.h"  #include "input_common/udp/udp.h"  #ifdef HAVE_SDL2  #include "input_common/sdl/sdl.h" @@ -18,67 +19,176 @@  namespace InputCommon { -static std::shared_ptr<Keyboard> keyboard; -static std::shared_ptr<MotionEmu> motion_emu; +struct InputSubsystem::Impl { +    void Initialize() { +        auto gcadapter = std::make_shared<GCAdapter::Adapter>(); +        gcbuttons = std::make_shared<GCButtonFactory>(gcadapter); +        Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons); +        gcanalog = std::make_shared<GCAnalogFactory>(gcadapter); +        Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog); + +        keyboard = std::make_shared<Keyboard>(); +        Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard); +        Input::RegisterFactory<Input::AnalogDevice>("analog_from_button", +                                                    std::make_shared<AnalogFromButton>()); +        motion_emu = std::make_shared<MotionEmu>(); +        Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu); +        Input::RegisterFactory<Input::TouchDevice>("touch_from_button", +                                                   std::make_shared<TouchFromButtonFactory>()); +  #ifdef HAVE_SDL2 -static std::unique_ptr<SDL::State> sdl; +        sdl = SDL::Init();  #endif -static std::unique_ptr<CemuhookUDP::State> udp; -static std::shared_ptr<GCButtonFactory> gcbuttons; -static std::shared_ptr<GCAnalogFactory> gcanalog; - -void Init() { -    auto gcadapter = std::make_shared<GCAdapter::Adapter>(); -    gcbuttons = std::make_shared<GCButtonFactory>(gcadapter); -    Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons); -    gcanalog = std::make_shared<GCAnalogFactory>(gcadapter); -    Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog); - -    keyboard = std::make_shared<Keyboard>(); -    Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard); -    Input::RegisterFactory<Input::AnalogDevice>("analog_from_button", -                                                std::make_shared<AnalogFromButton>()); -    motion_emu = std::make_shared<MotionEmu>(); -    Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu); +        udp = CemuhookUDP::Init(); +    } + +    void Shutdown() { +        Input::UnregisterFactory<Input::ButtonDevice>("keyboard"); +        keyboard.reset(); +        Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button"); +        Input::UnregisterFactory<Input::MotionDevice>("motion_emu"); +        motion_emu.reset(); +        Input::UnregisterFactory<Input::TouchDevice>("touch_from_button");  #ifdef HAVE_SDL2 -    sdl = SDL::Init(); +        sdl.reset();  #endif +        udp.reset(); +        Input::UnregisterFactory<Input::ButtonDevice>("gcpad"); +        Input::UnregisterFactory<Input::AnalogDevice>("gcpad"); + +        gcbuttons.reset(); +        gcanalog.reset(); +    } + +    [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const { +        std::vector<Common::ParamPackage> devices = { +            Common::ParamPackage{{"display", "Any"}, {"class", "any"}}, +            Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "key"}}, +        }; +#ifdef HAVE_SDL2 +        auto sdl_devices = sdl->GetInputDevices(); +        devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end()); +#endif +        auto udp_devices = udp->GetInputDevices(); +        devices.insert(devices.end(), udp_devices.begin(), udp_devices.end()); +        return devices; +    } + +    [[nodiscard]] AnalogMapping GetAnalogMappingForDevice( +        const Common::ParamPackage& params) const { +        if (!params.Has("class") || params.Get("class", "") == "any") { +            return {}; +        } +        if (params.Get("class", "") == "key") { +            // TODO consider returning the SDL key codes for the default keybindings +            return {}; +        } +#ifdef HAVE_SDL2 +        if (params.Get("class", "") == "sdl") { +            return sdl->GetAnalogMappingForDevice(params); +        } +#endif +        return {}; +    } + +    [[nodiscard]] ButtonMapping GetButtonMappingForDevice( +        const Common::ParamPackage& params) const { +        if (!params.Has("class") || params.Get("class", "") == "any") { +            return {}; +        } +        if (params.Get("class", "") == "key") { +            // TODO consider returning the SDL key codes for the default keybindings +            return {}; +        } +#ifdef HAVE_SDL2 +        if (params.Get("class", "") == "sdl") { +            return sdl->GetButtonMappingForDevice(params); +        } +#endif +        return {}; +    } -    udp = CemuhookUDP::Init(); -} - -void Shutdown() { -    Input::UnregisterFactory<Input::ButtonDevice>("keyboard"); -    keyboard.reset(); -    Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button"); -    Input::UnregisterFactory<Input::MotionDevice>("motion_emu"); -    motion_emu.reset(); +    std::shared_ptr<Keyboard> keyboard; +    std::shared_ptr<MotionEmu> motion_emu;  #ifdef HAVE_SDL2 -    sdl.reset(); +    std::unique_ptr<SDL::State> sdl;  #endif -    udp.reset(); -    Input::UnregisterFactory<Input::ButtonDevice>("gcpad"); -    Input::UnregisterFactory<Input::AnalogDevice>("gcpad"); +    std::unique_ptr<CemuhookUDP::State> udp; +    std::shared_ptr<GCButtonFactory> gcbuttons; +    std::shared_ptr<GCAnalogFactory> gcanalog; +}; + +InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {} + +InputSubsystem::~InputSubsystem() = default; + +void InputSubsystem::Initialize() { +    impl->Initialize(); +} + +void InputSubsystem::Shutdown() { +    impl->Shutdown(); +} + +Keyboard* InputSubsystem::GetKeyboard() { +    return impl->keyboard.get(); +} + +const Keyboard* InputSubsystem::GetKeyboard() const { +    return impl->keyboard.get(); +} + +MotionEmu* InputSubsystem::GetMotionEmu() { +    return impl->motion_emu.get(); +} + +const MotionEmu* InputSubsystem::GetMotionEmu() const { +    return impl->motion_emu.get(); +} -    gcbuttons.reset(); -    gcanalog.reset(); +std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const { +    return impl->GetInputDevices();  } -Keyboard* GetKeyboard() { -    return keyboard.get(); +AnalogMapping InputSubsystem::GetAnalogMappingForDevice(const Common::ParamPackage& device) const { +    return impl->GetAnalogMappingForDevice(device);  } -MotionEmu* GetMotionEmu() { -    return motion_emu.get(); +ButtonMapping InputSubsystem::GetButtonMappingForDevice(const Common::ParamPackage& device) const { +    return impl->GetButtonMappingForDevice(device);  } -GCButtonFactory* GetGCButtons() { -    return gcbuttons.get(); +GCAnalogFactory* InputSubsystem::GetGCAnalogs() { +    return impl->gcanalog.get();  } -GCAnalogFactory* GetGCAnalogs() { -    return gcanalog.get(); +const GCAnalogFactory* InputSubsystem::GetGCAnalogs() const { +    return impl->gcanalog.get(); +} + +GCButtonFactory* InputSubsystem::GetGCButtons() { +    return impl->gcbuttons.get(); +} + +const GCButtonFactory* InputSubsystem::GetGCButtons() const { +    return impl->gcbuttons.get(); +} + +void InputSubsystem::ReloadInputDevices() { +    if (!impl->udp) { +        return; +    } +    impl->udp->ReloadUDPClient(); +} + +std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers( +    Polling::DeviceType type) const { +#ifdef HAVE_SDL2 +    return impl->sdl->GetPollers(type); +#else +    return {}; +#endif  }  std::string GenerateKeyboardParam(int key_code) { @@ -102,18 +212,4 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left,      };      return circle_pad_param.Serialize();  } - -namespace Polling { - -std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) { -    std::vector<std::unique_ptr<DevicePoller>> pollers; - -#ifdef HAVE_SDL2 -    pollers = sdl->GetPollers(type); -#endif - -    return pollers; -} - -} // namespace Polling  } // namespace InputCommon diff --git a/src/input_common/main.h b/src/input_common/main.h index 0e32856f6..f3fbf696e 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -6,45 +6,25 @@  #include <memory>  #include <string> +#include <unordered_map>  #include <vector> -#include "input_common/gcadapter/gc_poller.h"  namespace Common {  class ParamPackage;  } -namespace InputCommon { - -/// Initializes and registers all built-in input device factories. -void Init(); - -/// Deregisters all built-in input device factories and shuts them down. -void Shutdown(); - -class Keyboard; - -/// Gets the keyboard button device factory. -Keyboard* GetKeyboard(); - -class MotionEmu; - -/// Gets the motion emulation factory. -MotionEmu* GetMotionEmu(); - -GCButtonFactory* GetGCButtons(); - -GCAnalogFactory* GetGCAnalogs(); - -/// Generates a serialized param package for creating a keyboard button device -std::string GenerateKeyboardParam(int key_code); +namespace Settings::NativeAnalog { +enum Values : int; +} -/// Generates a serialized param package for creating an analog device taking input from keyboard -std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, -                                        int key_modifier, float modifier_scale); +namespace Settings::NativeButton { +enum Values : int; +} +namespace InputCommon {  namespace Polling { -enum class DeviceType { Button, Analog }; +enum class DeviceType { Button, AnalogPreferred };  /**   * A class that can be used to get inputs from an input device like controllers without having to @@ -54,7 +34,9 @@ class DevicePoller {  public:      virtual ~DevicePoller() = default;      /// Setup and start polling for inputs, should be called before GetNextInput -    virtual void Start() = 0; +    /// If a device_id is provided, events should be filtered to only include events from this +    /// device id +    virtual void Start(const std::string& device_id = "") = 0;      /// Stop polling      virtual void Stop() = 0;      /** @@ -64,8 +46,92 @@ public:       */      virtual Common::ParamPackage GetNextInput() = 0;  }; - -// Get all DevicePoller from all backends for a specific device type -std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type);  } // namespace Polling + +class GCAnalogFactory; +class GCButtonFactory; +class Keyboard; +class MotionEmu; + +/** + * Given a ParamPackage for a Device returned from `GetInputDevices`, attempt to get the default + * mapping for the device. This is currently only implemented for the SDL backend devices. + */ +using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>; +using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>; + +class InputSubsystem { +public: +    explicit InputSubsystem(); +    ~InputSubsystem(); + +    InputSubsystem(const InputSubsystem&) = delete; +    InputSubsystem& operator=(const InputSubsystem&) = delete; + +    InputSubsystem(InputSubsystem&&) = delete; +    InputSubsystem& operator=(InputSubsystem&&) = delete; + +    /// Initializes and registers all built-in input device factories. +    void Initialize(); + +    /// Unregisters all built-in input device factories and shuts them down. +    void Shutdown(); + +    /// Retrieves the underlying keyboard device. +    [[nodiscard]] Keyboard* GetKeyboard(); + +    /// Retrieves the underlying keyboard device. +    [[nodiscard]] const Keyboard* GetKeyboard() const; + +    /// Retrieves the underlying motion emulation factory. +    [[nodiscard]] MotionEmu* GetMotionEmu(); + +    /// Retrieves the underlying motion emulation factory. +    [[nodiscard]] const MotionEmu* GetMotionEmu() const; + +    /** +     * Returns all available input devices that this Factory can create a new device with. +     * Each returned ParamPackage should have a `display` field used for display, a class field for +     * backends to determine if this backend is meant to service the request and any other +     * information needed to identify this in the backend later. +     */ +    [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const; + +    /// Retrieves the analog mappings for the given device. +    [[nodiscard]] AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& device) const; + +    /// Retrieves the button mappings for the given device. +    [[nodiscard]] ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& device) const; + +    /// Retrieves the underlying GameCube analog handler. +    [[nodiscard]] GCAnalogFactory* GetGCAnalogs(); + +    /// Retrieves the underlying GameCube analog handler. +    [[nodiscard]] const GCAnalogFactory* GetGCAnalogs() const; + +    /// Retrieves the underlying GameCube button handler. +    [[nodiscard]] GCButtonFactory* GetGCButtons(); + +    /// Retrieves the underlying GameCube button handler. +    [[nodiscard]] const GCButtonFactory* GetGCButtons() const; + +    /// Reloads the input devices +    void ReloadInputDevices(); + +    /// Get all DevicePoller from all backends for a specific device type +    [[nodiscard]] std::vector<std::unique_ptr<Polling::DevicePoller>> GetPollers( +        Polling::DeviceType type) const; + +private: +    struct Impl; +    std::unique_ptr<Impl> impl; +}; + +/// Generates a serialized param package for creating a keyboard button device +std::string GenerateKeyboardParam(int key_code); + +/// Generates a serialized param package for creating an analog device taking input from keyboard +std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, +                                        int key_modifier, float modifier_scale); +  } // namespace InputCommon diff --git a/src/input_common/motion_input.cpp b/src/input_common/motion_input.cpp new file mode 100644 index 000000000..22a849866 --- /dev/null +++ b/src/input_common/motion_input.cpp @@ -0,0 +1,181 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#include "common/math_util.h" +#include "input_common/motion_input.h" + +namespace InputCommon { + +MotionInput::MotionInput(f32 new_kp, f32 new_ki, f32 new_kd) +    : kp(new_kp), ki(new_ki), kd(new_kd), quat{{0, 0, -1}, 0} {} + +void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) { +    accel = acceleration; +} + +void MotionInput::SetGyroscope(const Common::Vec3f& gyroscope) { +    gyro = gyroscope - gyro_drift; +    if (gyro.Length2() < gyro_threshold) { +        gyro = {}; +    } +} + +void MotionInput::SetQuaternion(const Common::Quaternion<f32>& quaternion) { +    quat = quaternion; +} + +void MotionInput::SetGyroDrift(const Common::Vec3f& drift) { +    gyro_drift = drift; +} + +void MotionInput::SetGyroThreshold(f32 threshold) { +    gyro_threshold = threshold; +} + +void MotionInput::EnableReset(bool reset) { +    reset_enabled = reset; +} + +void MotionInput::ResetRotations() { +    rotations = {}; +} + +bool MotionInput::IsMoving(f32 sensitivity) const { +    return gyro.Length() >= sensitivity || accel.Length() <= 0.9f || accel.Length() >= 1.1f; +} + +bool MotionInput::IsCalibrated(f32 sensitivity) const { +    return real_error.Length() < sensitivity; +} + +void MotionInput::UpdateRotation(u64 elapsed_time) { +    const f32 sample_period = elapsed_time / 1000000.0f; +    if (sample_period > 0.1f) { +        return; +    } +    rotations += gyro * sample_period; +} + +void MotionInput::UpdateOrientation(u64 elapsed_time) { +    if (!IsCalibrated(0.1f)) { +        ResetOrientation(); +    } +    // Short name local variable for readability +    f32 q1 = quat.w; +    f32 q2 = quat.xyz[0]; +    f32 q3 = quat.xyz[1]; +    f32 q4 = quat.xyz[2]; +    const f32 sample_period = elapsed_time / 1000000.0f; + +    // ignore invalid elapsed time +    if (sample_period > 0.1f) { +        return; +    } + +    const auto normal_accel = accel.Normalized(); +    auto rad_gyro = gyro * Common::PI * 2; +    const f32 swap = rad_gyro.x; +    rad_gyro.x = rad_gyro.y; +    rad_gyro.y = -swap; +    rad_gyro.z = -rad_gyro.z; + +    // Ignore drift correction if acceleration is not reliable +    if (accel.Length() >= 0.75f && accel.Length() <= 1.25f) { +        const f32 ax = -normal_accel.x; +        const f32 ay = normal_accel.y; +        const f32 az = -normal_accel.z; + +        // Estimated direction of gravity +        const f32 vx = 2.0f * (q2 * q4 - q1 * q3); +        const f32 vy = 2.0f * (q1 * q2 + q3 * q4); +        const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4; + +        // Error is cross product between estimated direction and measured direction of gravity +        const Common::Vec3f new_real_error = {az * vx - ax * vz, ay * vz - az * vy, +                                              ax * vy - ay * vx}; + +        derivative_error = new_real_error - real_error; +        real_error = new_real_error; + +        // Prevent integral windup +        if (ki != 0.0f && !IsCalibrated(0.05f)) { +            integral_error += real_error; +        } else { +            integral_error = {}; +        } + +        // Apply feedback terms +        rad_gyro += kp * real_error; +        rad_gyro += ki * integral_error; +        rad_gyro += kd * derivative_error; +    } + +    const f32 gx = rad_gyro.y; +    const f32 gy = rad_gyro.x; +    const f32 gz = rad_gyro.z; + +    // Integrate rate of change of quaternion +    const f32 pa = q2; +    const f32 pb = q3; +    const f32 pc = q4; +    q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period); +    q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period); +    q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period); +    q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period); + +    quat.w = q1; +    quat.xyz[0] = q2; +    quat.xyz[1] = q3; +    quat.xyz[2] = q4; +    quat = quat.Normalized(); +} + +std::array<Common::Vec3f, 3> MotionInput::GetOrientation() const { +    const Common::Quaternion<float> quad{ +        .xyz = {-quat.xyz[1], -quat.xyz[0], -quat.w}, +        .w = -quat.xyz[2], +    }; +    const std::array<float, 16> matrix4x4 = quad.ToMatrix(); + +    return {Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]), +            Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]), +            Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10])}; +} + +Common::Vec3f MotionInput::GetAcceleration() const { +    return accel; +} + +Common::Vec3f MotionInput::GetGyroscope() const { +    return gyro; +} + +Common::Quaternion<f32> MotionInput::GetQuaternion() const { +    return quat; +} + +Common::Vec3f MotionInput::GetRotations() const { +    return rotations; +} + +void MotionInput::ResetOrientation() { +    if (!reset_enabled) { +        return; +    } +    if (!IsMoving(0.5f) && accel.z <= -0.9f) { +        ++reset_counter; +        if (reset_counter > 900) { +            // TODO: calculate quaternion from gravity vector +            quat.w = 0; +            quat.xyz[0] = 0; +            quat.xyz[1] = 0; +            quat.xyz[2] = -1; +            integral_error = {}; +            reset_counter = 0; +        } +    } else { +        reset_counter = 0; +    } +} +} // namespace InputCommon diff --git a/src/input_common/motion_input.h b/src/input_common/motion_input.h new file mode 100644 index 000000000..54b4439d9 --- /dev/null +++ b/src/input_common/motion_input.h @@ -0,0 +1,68 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#pragma once + +#include "common/common_types.h" +#include "common/quaternion.h" +#include "common/vector_math.h" + +namespace InputCommon { + +class MotionInput { +public: +    MotionInput(f32 new_kp, f32 new_ki, f32 new_kd); + +    MotionInput(const MotionInput&) = default; +    MotionInput& operator=(const MotionInput&) = default; + +    MotionInput(MotionInput&&) = default; +    MotionInput& operator=(MotionInput&&) = default; + +    void SetAcceleration(const Common::Vec3f& acceleration); +    void SetGyroscope(const Common::Vec3f& acceleration); +    void SetQuaternion(const Common::Quaternion<f32>& quaternion); +    void SetGyroDrift(const Common::Vec3f& drift); +    void SetGyroThreshold(f32 threshold); + +    void EnableReset(bool reset); +    void ResetRotations(); + +    void UpdateRotation(u64 elapsed_time); +    void UpdateOrientation(u64 elapsed_time); + +    std::array<Common::Vec3f, 3> GetOrientation() const; +    Common::Vec3f GetAcceleration() const; +    Common::Vec3f GetGyroscope() const; +    Common::Vec3f GetRotations() const; +    Common::Quaternion<f32> GetQuaternion() const; + +    bool IsMoving(f32 sensitivity) const; +    bool IsCalibrated(f32 sensitivity) const; + +private: +    void ResetOrientation(); + +    // PID constants +    const f32 kp; +    const f32 ki; +    const f32 kd; + +    // PID errors +    Common::Vec3f real_error; +    Common::Vec3f integral_error; +    Common::Vec3f derivative_error; + +    Common::Quaternion<f32> quat; +    Common::Vec3f rotations; +    Common::Vec3f accel; +    Common::Vec3f gyro; +    Common::Vec3f gyro_drift; + +    f32 gyro_threshold = 0.0f; +    u32 reset_counter = 0; +    bool reset_enabled = true; +}; + +} // namespace InputCommon diff --git a/src/input_common/sdl/sdl.h b/src/input_common/sdl/sdl.h index 5306daa70..f3554be9a 100644 --- a/src/input_common/sdl/sdl.h +++ b/src/input_common/sdl/sdl.h @@ -6,6 +6,7 @@  #include <memory>  #include <vector> +#include "common/param_package.h"  #include "input_common/main.h"  namespace InputCommon::Polling { @@ -22,14 +23,24 @@ public:      /// Unregisters SDL device factories and shut them down.      virtual ~State() = default; -    virtual Pollers GetPollers(Polling::DeviceType type) = 0; +    virtual Pollers GetPollers(Polling::DeviceType type) { +        return {}; +    } + +    virtual std::vector<Common::ParamPackage> GetInputDevices() { +        return {}; +    } + +    virtual ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage&) { +        return {}; +    } +    virtual AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage&) { +        return {}; +    }  };  class NullState : public State {  public: -    Pollers GetPollers(Polling::DeviceType type) override { -        return {}; -    }  };  std::unique_ptr<State> Init(); diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp index 675b477fa..a9e676f4b 100644 --- a/src/input_common/sdl/sdl_impl.cpp +++ b/src/input_common/sdl/sdl_impl.cpp @@ -3,10 +3,13 @@  // Refer to the license.txt file included.  #include <algorithm> +#include <array>  #include <atomic>  #include <cmath>  #include <functional>  #include <mutex> +#include <optional> +#include <sstream>  #include <string>  #include <thread>  #include <tuple> @@ -15,15 +18,16 @@  #include <vector>  #include <SDL.h>  #include "common/logging/log.h" -#include "common/math_util.h"  #include "common/param_package.h"  #include "common/threadsafe_queue.h"  #include "core/frontend/input.h"  #include "input_common/sdl/sdl_impl.h" +#include "input_common/settings.h"  namespace InputCommon::SDL { -static std::string GetGUID(SDL_Joystick* joystick) { +namespace { +std::string GetGUID(SDL_Joystick* joystick) {      const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);      char guid_str[33];      SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str)); @@ -31,7 +35,8 @@ static std::string GetGUID(SDL_Joystick* joystick) {  }  /// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice -static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event); +Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event); +} // Anonymous namespace  static int SDLEventWatcher(void* user_data, SDL_Event* event) {      auto* const sdl_state = static_cast<SDLState*>(user_data); @@ -48,8 +53,10 @@ static int SDLEventWatcher(void* user_data, SDL_Event* event) {  class SDLJoystick {  public: -    SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick) -        : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose} {} +    SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick, +                SDL_GameController* gamecontroller) +        : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose}, +          sdl_controller{gamecontroller, &SDL_GameControllerClose} {}      void SetButton(int button, bool value) {          std::lock_guard lock{mutex}; @@ -66,14 +73,14 @@ public:          state.axes.insert_or_assign(axis, value);      } -    float GetAxis(int axis) const { +    float GetAxis(int axis, float range) const {          std::lock_guard lock{mutex}; -        return state.axes.at(axis) / 32767.0f; +        return state.axes.at(axis) / (32767.0f * range);      } -    std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const { -        float x = GetAxis(axis_x); -        float y = GetAxis(axis_y); +    std::tuple<float, float> GetAnalog(int axis_x, int axis_y, float range) const { +        float x = GetAxis(axis_x, range); +        float y = GetAxis(axis_y, range);          y = -y; // 3DS uses an y-axis inverse from SDL          // Make sure the coordinates are in the unit circle, @@ -115,10 +122,15 @@ public:          return sdl_joystick.get();      } -    void SetSDLJoystick(SDL_Joystick* joystick) { +    void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) { +        sdl_controller.reset(controller);          sdl_joystick.reset(joystick);      } +    SDL_GameController* GetSDLGameController() const { +        return sdl_controller.get(); +    } +  private:      struct State {          std::unordered_map<int, bool> buttons; @@ -128,6 +140,7 @@ private:      std::string guid;      int port;      std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick; +    std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller;      mutable std::mutex mutex;  }; @@ -136,18 +149,19 @@ std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& g      const auto it = joystick_map.find(guid);      if (it != joystick_map.end()) {          while (it->second.size() <= static_cast<std::size_t>(port)) { -            auto joystick = -                std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()), nullptr); +            auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()), +                                                          nullptr, nullptr);              it->second.emplace_back(std::move(joystick));          }          return it->second[port];      } -    auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr); +    auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr);      return joystick_map[guid].emplace_back(std::move(joystick));  }  std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {      auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); +    auto sdl_controller = SDL_GameControllerFromInstanceID(sdl_id);      const std::string guid = GetGUID(sdl_joystick);      std::lock_guard lock{joystick_map_mutex}; @@ -171,23 +185,27 @@ std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_                                               });          if (nullptr_it != map_it->second.end()) {              // ... and map it -            (*nullptr_it)->SetSDLJoystick(sdl_joystick); +            (*nullptr_it)->SetSDLJoystick(sdl_joystick, sdl_controller);              return *nullptr_it;          }          // There is no SDLJoystick without a mapped SDL_Joystick          // Create a new SDLJoystick          const int port = static_cast<int>(map_it->second.size()); -        auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick); +        auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_controller);          return map_it->second.emplace_back(std::move(joystick));      } -    auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick); +    auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_controller);      return joystick_map[guid].emplace_back(std::move(joystick));  }  void SDLState::InitJoystick(int joystick_index) {      SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); +    SDL_GameController* sdl_gamecontroller = nullptr; +    if (SDL_IsGameController(joystick_index)) { +        sdl_gamecontroller = SDL_GameControllerOpen(joystick_index); +    }      if (!sdl_joystick) {          LOG_ERROR(Input, "failed to open joystick {}", joystick_index);          return; @@ -196,7 +214,7 @@ void SDLState::InitJoystick(int joystick_index) {      std::lock_guard lock{joystick_map_mutex};      if (joystick_map.find(guid) == joystick_map.end()) { -        auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick); +        auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);          joystick_map[guid].emplace_back(std::move(joystick));          return;      } @@ -205,11 +223,11 @@ void SDLState::InitJoystick(int joystick_index) {          joystick_guid_list.begin(), joystick_guid_list.end(),          [](const std::shared_ptr<SDLJoystick>& joystick) { return !joystick->GetSDLJoystick(); });      if (it != joystick_guid_list.end()) { -        (*it)->SetSDLJoystick(sdl_joystick); +        (*it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller);          return;      }      const int port = static_cast<int>(joystick_guid_list.size()); -    auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick); +    auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller);      joystick_guid_list.emplace_back(std::move(joystick));  } @@ -231,7 +249,7 @@ void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) {      // Destruct SDL_Joystick outside the lock guard because SDL can internally call the      // event callback which locks the mutex again. -    joystick->SetSDLJoystick(nullptr); +    joystick->SetSDLJoystick(nullptr, nullptr);  }  void SDLState::HandleGameControllerEvent(const SDL_Event& event) { @@ -313,7 +331,7 @@ public:            trigger_if_greater(trigger_if_greater_) {}      bool GetStatus() const override { -        const float axis_value = joystick->GetAxis(axis); +        const float axis_value = joystick->GetAxis(axis, 1.0f);          if (trigger_if_greater) {              return axis_value > threshold;          } @@ -329,22 +347,24 @@ private:  class SDLAnalog final : public Input::AnalogDevice {  public: -    SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, float deadzone_) -        : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_) {} +    SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, float deadzone_, +              float range_) +        : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), +          range(range_) {}      std::tuple<float, float> GetStatus() const override { -        const auto [x, y] = joystick->GetAnalog(axis_x, axis_y); +        const auto [x, y] = joystick->GetAnalog(axis_x, axis_y, range);          const float r = std::sqrt((x * x) + (y * y));          if (r > deadzone) {              return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone),                                     y / r * (r - deadzone) / (1 - deadzone));          } -        return std::make_tuple<float, float>(0.0f, 0.0f); +        return {};      }      bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {          const auto [x, y] = GetStatus(); -        const float directional_deadzone = 0.4f; +        const float directional_deadzone = 0.5f;          switch (direction) {          case Input::AnalogDirection::RIGHT:              return x > directional_deadzone; @@ -363,6 +383,7 @@ private:      const int axis_x;      const int axis_y;      const float deadzone; +    const float range;  };  /// A button device factory that creates button devices from SDL joystick @@ -457,14 +478,14 @@ public:          const int port = params.Get("port", 0);          const int axis_x = params.Get("axis_x", 0);          const int axis_y = params.Get("axis_y", 1); -        const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f); - +        const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); +        const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);          auto joystick = state.GetSDLJoystickByGUID(guid, port);          // This is necessary so accessing GetAxis with axis_x and axis_y won't crash          joystick->SetAxis(axis_x, 0);          joystick->SetAxis(axis_y, 0); -        return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, deadzone); +        return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, deadzone, range);      }  private: @@ -473,8 +494,10 @@ private:  SDLState::SDLState() {      using namespace Input; -    RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>(*this)); -    RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>(*this)); +    analog_factory = std::make_shared<SDLAnalogFactory>(*this); +    button_factory = std::make_shared<SDLButtonFactory>(*this); +    RegisterFactory<AnalogDevice>("sdl", analog_factory); +    RegisterFactory<ButtonDevice>("sdl", button_factory);      // If the frontend is going to manage the event loop, then we dont start one here      start_thread = !SDL_WasInit(SDL_INIT_JOYSTICK); @@ -482,6 +505,7 @@ SDLState::SDLState() {          LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError());          return;      } +    has_gamecontroller = SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER);      if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) {          LOG_ERROR(Input, "Failed to set hint for background events with: {}", SDL_GetError());      } @@ -494,7 +518,7 @@ SDLState::SDLState() {              using namespace std::chrono_literals;              while (initialized) {                  SDL_PumpEvents(); -                std::this_thread::sleep_for(10ms); +                std::this_thread::sleep_for(5ms);              }          });      } @@ -520,65 +544,230 @@ SDLState::~SDLState() {      }  } -static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) { +std::vector<Common::ParamPackage> SDLState::GetInputDevices() { +    std::scoped_lock lock(joystick_map_mutex); +    std::vector<Common::ParamPackage> devices; +    for (const auto& [key, value] : joystick_map) { +        for (const auto& joystick : value) { +            auto joy = joystick->GetSDLJoystick(); +            if (auto controller = joystick->GetSDLGameController()) { +                std::string name = +                    fmt::format("{} {}", SDL_GameControllerName(controller), joystick->GetPort()); +                devices.emplace_back(Common::ParamPackage{ +                    {"class", "sdl"}, +                    {"display", std::move(name)}, +                    {"guid", joystick->GetGUID()}, +                    {"port", std::to_string(joystick->GetPort())}, +                }); +            } else if (joy) { +                std::string name = fmt::format("{} {}", SDL_JoystickName(joy), joystick->GetPort()); +                devices.emplace_back(Common::ParamPackage{ +                    {"class", "sdl"}, +                    {"display", std::move(name)}, +                    {"guid", joystick->GetGUID()}, +                    {"port", std::to_string(joystick->GetPort())}, +                }); +            } +        } +    } +    return devices; +} + +namespace { +Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, u8 axis, +                                                      float value = 0.1f) { +    Common::ParamPackage params({{"engine", "sdl"}}); +    params.Set("port", port); +    params.Set("guid", std::move(guid)); +    params.Set("axis", axis); +    if (value > 0) { +        params.Set("direction", "+"); +        params.Set("threshold", "0.5"); +    } else { +        params.Set("direction", "-"); +        params.Set("threshold", "-0.5"); +    } +    return params; +} + +Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid, u8 button) {      Common::ParamPackage params({{"engine", "sdl"}}); +    params.Set("port", port); +    params.Set("guid", std::move(guid)); +    params.Set("button", button); +    return params; +} + +Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, u8 hat, u8 value) { +    Common::ParamPackage params({{"engine", "sdl"}}); + +    params.Set("port", port); +    params.Set("guid", std::move(guid)); +    params.Set("hat", hat); +    switch (value) { +    case SDL_HAT_UP: +        params.Set("direction", "up"); +        break; +    case SDL_HAT_DOWN: +        params.Set("direction", "down"); +        break; +    case SDL_HAT_LEFT: +        params.Set("direction", "left"); +        break; +    case SDL_HAT_RIGHT: +        params.Set("direction", "right"); +        break; +    default: +        return {}; +    } +    return params; +} +Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) {      switch (event.type) {      case SDL_JOYAXISMOTION: {          const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); -        params.Set("port", joystick->GetPort()); -        params.Set("guid", joystick->GetGUID()); -        params.Set("axis", event.jaxis.axis); -        if (event.jaxis.value > 0) { -            params.Set("direction", "+"); -            params.Set("threshold", "0.5"); -        } else { -            params.Set("direction", "-"); -            params.Set("threshold", "-0.5"); -        } -        break; +        return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), +                                                event.jaxis.axis, event.jaxis.value);      }      case SDL_JOYBUTTONUP: {          const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which); -        params.Set("port", joystick->GetPort()); -        params.Set("guid", joystick->GetGUID()); -        params.Set("button", event.jbutton.button); -        break; +        return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), +                                                event.jbutton.button);      }      case SDL_JOYHATMOTION: {          const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which); -        params.Set("port", joystick->GetPort()); -        params.Set("guid", joystick->GetGUID()); -        params.Set("hat", event.jhat.hat); -        switch (event.jhat.value) { -        case SDL_HAT_UP: -            params.Set("direction", "up"); -            break; -        case SDL_HAT_DOWN: -            params.Set("direction", "down"); -            break; -        case SDL_HAT_LEFT: -            params.Set("direction", "left"); -            break; -        case SDL_HAT_RIGHT: -            params.Set("direction", "right"); -            break; -        default: -            return {}; -        } -        break; +        return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), +                                             event.jhat.hat, event.jhat.value);      }      } +    return {}; +} + +Common::ParamPackage BuildParamPackageForBinding(int port, const std::string& guid, +                                                 const SDL_GameControllerButtonBind& binding) { +    switch (binding.bindType) { +    case SDL_CONTROLLER_BINDTYPE_AXIS: +        return BuildAnalogParamPackageForButton(port, guid, binding.value.axis); +    case SDL_CONTROLLER_BINDTYPE_BUTTON: +        return BuildButtonParamPackageForButton(port, guid, binding.value.button); +    case SDL_CONTROLLER_BINDTYPE_HAT: +        return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat, +                                             binding.value.hat.hat_mask); +    } +    return {}; +} + +Common::ParamPackage BuildParamPackageForAnalog(int port, const std::string& guid, int axis_x, +                                                int axis_y) { +    Common::ParamPackage params; +    params.Set("engine", "sdl"); +    params.Set("port", port); +    params.Set("guid", guid); +    params.Set("axis_x", axis_x); +    params.Set("axis_y", axis_y);      return params;  } +} // Anonymous namespace -namespace Polling { +ButtonMapping SDLState::GetButtonMappingForDevice(const Common::ParamPackage& params) { +    if (!params.Has("guid") || !params.Has("port")) { +        return {}; +    } +    const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); +    auto* controller = joystick->GetSDLGameController(); +    if (controller == nullptr) { +        return {}; +    } + +    // This list is missing ZL/ZR since those are not considered buttons in SDL GameController. +    // We will add those afterwards +    // This list also excludes Screenshot since theres not really a mapping for that +    using ButtonBindings = +        std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 17>; +    static constexpr ButtonBindings switch_to_sdl_button{{ +        {Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B}, +        {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A}, +        {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y}, +        {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X}, +        {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK}, +        {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK}, +        {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, +        {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, +        {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START}, +        {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK}, +        {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT}, +        {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP}, +        {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, +        {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN}, +        {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, +        {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, +        {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, +    }}; + +    // Add the missing bindings for ZL/ZR +    using ZBindings = +        std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>; +    static constexpr ZBindings switch_to_sdl_axis{{ +        {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT}, +        {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT}, +    }}; + +    ButtonMapping mapping; +    mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size()); + +    for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { +        const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); +        mapping.insert_or_assign( +            switch_button, +            BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); +    } +    for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { +        const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); +        mapping.insert_or_assign( +            switch_button, +            BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); +    } + +    return mapping; +} +AnalogMapping SDLState::GetAnalogMappingForDevice(const Common::ParamPackage& params) { +    if (!params.Has("guid") || !params.Has("port")) { +        return {}; +    } +    const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); +    auto* controller = joystick->GetSDLGameController(); +    if (controller == nullptr) { +        return {}; +    } + +    AnalogMapping mapping = {}; +    const auto& binding_left_x = +        SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); +    const auto& binding_left_y = +        SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); +    mapping.insert_or_assign(Settings::NativeAnalog::LStick, +                             BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), +                                                        binding_left_x.value.axis, +                                                        binding_left_y.value.axis)); +    const auto& binding_right_x = +        SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); +    const auto& binding_right_y = +        SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); +    mapping.insert_or_assign(Settings::NativeAnalog::RStick, +                             BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), +                                                        binding_right_x.value.axis, +                                                        binding_right_y.value.axis)); +    return mapping; +} + +namespace Polling {  class SDLPoller : public InputCommon::Polling::DevicePoller {  public:      explicit SDLPoller(SDLState& state_) : state(state_) {} -    void Start() override { +    void Start(const std::string& device_id) override {          state.event_queue.Clear();          state.polling = true;      } @@ -598,71 +787,106 @@ public:      Common::ParamPackage GetNextInput() override {          SDL_Event event;          while (state.event_queue.Pop(event)) { -            switch (event.type) { -            case SDL_JOYAXISMOTION: -                if (std::abs(event.jaxis.value / 32767.0) < 0.5) { -                    break; -                } -                [[fallthrough]]; -            case SDL_JOYBUTTONUP: -            case SDL_JOYHATMOTION: -                return SDLEventToButtonParamPackage(state, event); +            const auto package = FromEvent(event); +            if (package) { +                return *package;              }          }          return {};      } +    [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(const SDL_Event& event) const { +        switch (event.type) { +        case SDL_JOYAXISMOTION: +            if (std::abs(event.jaxis.value / 32767.0) < 0.5) { +                break; +            } +            [[fallthrough]]; +        case SDL_JOYBUTTONUP: +        case SDL_JOYHATMOTION: +            return {SDLEventToButtonParamPackage(state, event)}; +        } +        return std::nullopt; +    }  }; -class SDLAnalogPoller final : public SDLPoller { +/** + * Attempts to match the press to a controller joy axis (left/right stick) and if a match + * isn't found, checks if the event matches anything from SDLButtonPoller and uses that + * instead + */ +class SDLAnalogPreferredPoller final : public SDLPoller {  public: -    explicit SDLAnalogPoller(SDLState& state_) : SDLPoller(state_) {} - -    void Start() override { -        SDLPoller::Start(); +    explicit SDLAnalogPreferredPoller(SDLState& state_) +        : SDLPoller(state_), button_poller(state_) {} +    void Start(const std::string& device_id) override { +        SDLPoller::Start(device_id); +        // Load the game controller          // Reset stored axes          analog_x_axis = -1;          analog_y_axis = -1; -        analog_axes_joystick = -1;      }      Common::ParamPackage GetNextInput() override {          SDL_Event event;          while (state.event_queue.Pop(event)) { -            if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) { +            // Filter out axis events that are below a threshold +            if (event.type == SDL_JOYAXISMOTION && std::abs(event.jaxis.value / 32767.0) < 0.5) {                  continue;              } -            // An analog device needs two axes, so we need to store the axis for later and wait for -            // a second SDL event. The axes also must be from the same joystick. -            const int axis = event.jaxis.axis; -            if (analog_x_axis == -1) { -                analog_x_axis = axis; -                analog_axes_joystick = event.jaxis.which; -            } else if (analog_y_axis == -1 && analog_x_axis != axis && -                       analog_axes_joystick == event.jaxis.which) { -                analog_y_axis = axis; +            // Simplify controller config by testing if game controller support is enabled. +            if (event.type == SDL_JOYAXISMOTION) { +                const auto axis = event.jaxis.axis; +                const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); +                const auto controller = joystick->GetSDLGameController(); +                if (controller) { +                    const auto axis_left_x = +                        SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX) +                            .value.axis; +                    const auto axis_left_y = +                        SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY) +                            .value.axis; +                    const auto axis_right_x = +                        SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX) +                            .value.axis; +                    const auto axis_right_y = +                        SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY) +                            .value.axis; + +                    if (axis == axis_left_x || axis == axis_left_y) { +                        analog_x_axis = axis_left_x; +                        analog_y_axis = axis_left_y; +                        break; +                    } else if (axis == axis_right_x || axis == axis_right_y) { +                        analog_x_axis = axis_right_x; +                        analog_y_axis = axis_right_y; +                        break; +                    } +                } +            } + +            // If the press wasn't accepted as a joy axis, check for a button press +            auto button_press = button_poller.FromEvent(event); +            if (button_press) { +                return *button_press;              }          } -        Common::ParamPackage params; +          if (analog_x_axis != -1 && analog_y_axis != -1) {              const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); -            params.Set("engine", "sdl"); -            params.Set("port", joystick->GetPort()); -            params.Set("guid", joystick->GetGUID()); -            params.Set("axis_x", analog_x_axis); -            params.Set("axis_y", analog_y_axis); +            auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), +                                                     analog_x_axis, analog_y_axis);              analog_x_axis = -1;              analog_y_axis = -1; -            analog_axes_joystick = -1;              return params;          } -        return params; +        return {};      }  private:      int analog_x_axis = -1;      int analog_y_axis = -1; -    SDL_JoystickID analog_axes_joystick = -1; +    SDLButtonPoller button_poller;  };  } // namespace Polling @@ -670,8 +894,8 @@ SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) {      Pollers pollers;      switch (type) { -    case InputCommon::Polling::DeviceType::Analog: -        pollers.emplace_back(std::make_unique<Polling::SDLAnalogPoller>(*this)); +    case InputCommon::Polling::DeviceType::AnalogPreferred: +        pollers.emplace_back(std::make_unique<Polling::SDLAnalogPreferredPoller>(*this));          break;      case InputCommon::Polling::DeviceType::Button:          pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this)); diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/sdl/sdl_impl.h index 606a32c5b..bd19ba61d 100644 --- a/src/input_common/sdl/sdl_impl.h +++ b/src/input_common/sdl/sdl_impl.h @@ -50,6 +50,11 @@ public:      std::atomic<bool> polling = false;      Common::SPSCQueue<SDL_Event> event_queue; +    std::vector<Common::ParamPackage> GetInputDevices() override; + +    ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; +    AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; +  private:      void InitJoystick(int joystick_index);      void CloseJoystick(SDL_Joystick* sdl_joystick); @@ -57,6 +62,9 @@ private:      /// Needs to be called before SDL_QuitSubSystem.      void CloseJoysticks(); +    // Set to true if SDL supports game controller subsystem +    bool has_gamecontroller = false; +      /// Map of GUID of a list of corresponding virtual Joysticks      std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;      std::mutex joystick_map_mutex; diff --git a/src/input_common/settings.cpp b/src/input_common/settings.cpp new file mode 100644 index 000000000..80c719cf4 --- /dev/null +++ b/src/input_common/settings.cpp @@ -0,0 +1,33 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "input_common/settings.h" + +namespace Settings { +namespace NativeButton { +const std::array<const char*, NumButtons> mapping = {{ +    "button_a",      "button_b",     "button_x",     "button_y",    "button_lstick", +    "button_rstick", "button_l",     "button_r",     "button_zl",   "button_zr", +    "button_plus",   "button_minus", "button_dleft", "button_dup",  "button_dright", +    "button_ddown",  "button_sl",    "button_sr",    "button_home", "button_screenshot", +}}; +} + +namespace NativeAnalog { +const std::array<const char*, NumAnalogs> mapping = {{ +    "lstick", +    "rstick", +}}; +} + +namespace NativeMouseButton { +const std::array<const char*, NumMouseButtons> mapping = {{ +    "left", +    "right", +    "middle", +    "forward", +    "back", +}}; +} +} // namespace Settings diff --git a/src/input_common/settings.h b/src/input_common/settings.h new file mode 100644 index 000000000..2d258960b --- /dev/null +++ b/src/input_common/settings.h @@ -0,0 +1,335 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <string> +#include "common/common_types.h" + +namespace Settings { +namespace NativeButton { +enum Values : int { +    A, +    B, +    X, +    Y, +    LStick, +    RStick, +    L, +    R, +    ZL, +    ZR, +    Plus, +    Minus, + +    DLeft, +    DUp, +    DRight, +    DDown, + +    SL, +    SR, + +    Home, +    Screenshot, + +    NumButtons, +}; + +constexpr int BUTTON_HID_BEGIN = A; +constexpr int BUTTON_NS_BEGIN = Home; + +constexpr int BUTTON_HID_END = BUTTON_NS_BEGIN; +constexpr int BUTTON_NS_END = NumButtons; + +constexpr int NUM_BUTTONS_HID = BUTTON_HID_END - BUTTON_HID_BEGIN; +constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN; + +extern const std::array<const char*, NumButtons> mapping; + +} // namespace NativeButton + +namespace NativeAnalog { +enum Values : int { +    LStick, +    RStick, + +    NumAnalogs, +}; + +constexpr int STICK_HID_BEGIN = LStick; +constexpr int STICK_HID_END = NumAnalogs; +constexpr int NUM_STICKS_HID = NumAnalogs; + +extern const std::array<const char*, NumAnalogs> mapping; +} // namespace NativeAnalog + +namespace NativeMouseButton { +enum Values { +    Left, +    Right, +    Middle, +    Forward, +    Back, + +    NumMouseButtons, +}; + +constexpr int MOUSE_HID_BEGIN = Left; +constexpr int MOUSE_HID_END = NumMouseButtons; +constexpr int NUM_MOUSE_HID = NumMouseButtons; + +extern const std::array<const char*, NumMouseButtons> mapping; +} // namespace NativeMouseButton + +namespace NativeKeyboard { +enum Keys { +    None, +    Error, + +    A = 4, +    B, +    C, +    D, +    E, +    F, +    G, +    H, +    I, +    J, +    K, +    L, +    M, +    N, +    O, +    P, +    Q, +    R, +    S, +    T, +    U, +    V, +    W, +    X, +    Y, +    Z, +    N1, +    N2, +    N3, +    N4, +    N5, +    N6, +    N7, +    N8, +    N9, +    N0, +    Enter, +    Escape, +    Backspace, +    Tab, +    Space, +    Minus, +    Equal, +    LeftBrace, +    RightBrace, +    Backslash, +    Tilde, +    Semicolon, +    Apostrophe, +    Grave, +    Comma, +    Dot, +    Slash, +    CapsLockKey, + +    F1, +    F2, +    F3, +    F4, +    F5, +    F6, +    F7, +    F8, +    F9, +    F10, +    F11, +    F12, + +    SystemRequest, +    ScrollLockKey, +    Pause, +    Insert, +    Home, +    PageUp, +    Delete, +    End, +    PageDown, +    Right, +    Left, +    Down, +    Up, + +    NumLockKey, +    KPSlash, +    KPAsterisk, +    KPMinus, +    KPPlus, +    KPEnter, +    KP1, +    KP2, +    KP3, +    KP4, +    KP5, +    KP6, +    KP7, +    KP8, +    KP9, +    KP0, +    KPDot, + +    Key102, +    Compose, +    Power, +    KPEqual, + +    F13, +    F14, +    F15, +    F16, +    F17, +    F18, +    F19, +    F20, +    F21, +    F22, +    F23, +    F24, + +    Open, +    Help, +    Properties, +    Front, +    Stop, +    Repeat, +    Undo, +    Cut, +    Copy, +    Paste, +    Find, +    Mute, +    VolumeUp, +    VolumeDown, +    CapsLockActive, +    NumLockActive, +    ScrollLockActive, +    KPComma, + +    KPLeftParenthesis, +    KPRightParenthesis, + +    LeftControlKey = 0xE0, +    LeftShiftKey, +    LeftAltKey, +    LeftMetaKey, +    RightControlKey, +    RightShiftKey, +    RightAltKey, +    RightMetaKey, + +    MediaPlayPause, +    MediaStopCD, +    MediaPrevious, +    MediaNext, +    MediaEject, +    MediaVolumeUp, +    MediaVolumeDown, +    MediaMute, +    MediaWebsite, +    MediaBack, +    MediaForward, +    MediaStop, +    MediaFind, +    MediaScrollUp, +    MediaScrollDown, +    MediaEdit, +    MediaSleep, +    MediaCoffee, +    MediaRefresh, +    MediaCalculator, + +    NumKeyboardKeys, +}; + +static_assert(NumKeyboardKeys == 0xFC, "Incorrect number of keyboard keys."); + +enum Modifiers { +    LeftControl, +    LeftShift, +    LeftAlt, +    LeftMeta, +    RightControl, +    RightShift, +    RightAlt, +    RightMeta, +    CapsLock, +    ScrollLock, +    NumLock, + +    NumKeyboardMods, +}; + +constexpr int KEYBOARD_KEYS_HID_BEGIN = None; +constexpr int KEYBOARD_KEYS_HID_END = NumKeyboardKeys; +constexpr int NUM_KEYBOARD_KEYS_HID = NumKeyboardKeys; + +constexpr int KEYBOARD_MODS_HID_BEGIN = LeftControl; +constexpr int KEYBOARD_MODS_HID_END = NumKeyboardMods; +constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods; + +} // namespace NativeKeyboard + +using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>; +using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>; +using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>; +using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>; +using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>; + +constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28; +constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A; +constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6; +constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E; + +enum class ControllerType { +    ProController, +    DualJoyconDetached, +    LeftJoycon, +    RightJoycon, +    Handheld, +}; + +struct PlayerInput { +    bool connected; +    ControllerType controller_type; +    ButtonsRaw buttons; +    AnalogsRaw analogs; +    std::string lstick_mod; +    std::string rstick_mod; + +    u32 body_color_left; +    u32 body_color_right; +    u32 button_color_left; +    u32 button_color_right; +}; + +struct TouchscreenInput { +    bool enabled; +    std::string device; + +    u32 finger; +    u32 diameter_x; +    u32 diameter_y; +    u32 rotation_angle; +}; +} // namespace Settings diff --git a/src/input_common/touch_from_button.cpp b/src/input_common/touch_from_button.cpp new file mode 100644 index 000000000..98da0ef1a --- /dev/null +++ b/src/input_common/touch_from_button.cpp @@ -0,0 +1,50 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/frontend/framebuffer_layout.h" +#include "core/settings.h" +#include "input_common/touch_from_button.h" + +namespace InputCommon { + +class TouchFromButtonDevice final : public Input::TouchDevice { +public: +    TouchFromButtonDevice() { +        for (const auto& config_entry : +             Settings::values.touch_from_button_maps[Settings::values.touch_from_button_map_index] +                 .buttons) { +            const Common::ParamPackage package{config_entry}; +            map.emplace_back( +                Input::CreateDevice<Input::ButtonDevice>(config_entry), +                std::clamp(package.Get("x", 0), 0, static_cast<int>(Layout::ScreenUndocked::Width)), +                std::clamp(package.Get("y", 0), 0, +                           static_cast<int>(Layout::ScreenUndocked::Height))); +        } +    } + +    std::tuple<float, float, bool> GetStatus() const override { +        for (const auto& m : map) { +            const bool state = std::get<0>(m)->GetStatus(); +            if (state) { +                const float x = static_cast<float>(std::get<1>(m)) / +                                static_cast<int>(Layout::ScreenUndocked::Width); +                const float y = static_cast<float>(std::get<2>(m)) / +                                static_cast<int>(Layout::ScreenUndocked::Height); +                return {x, y, true}; +            } +        } +        return {}; +    } + +private: +    // A vector of the mapped button, its x and its y-coordinate +    std::vector<std::tuple<std::unique_ptr<Input::ButtonDevice>, int, int>> map; +}; + +std::unique_ptr<Input::TouchDevice> TouchFromButtonFactory::Create( +    const Common::ParamPackage& params) { +    return std::make_unique<TouchFromButtonDevice>(); +} + +} // namespace InputCommon diff --git a/src/input_common/touch_from_button.h b/src/input_common/touch_from_button.h new file mode 100644 index 000000000..8b4d1aa96 --- /dev/null +++ b/src/input_common/touch_from_button.h @@ -0,0 +1,23 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include "core/frontend/input.h" + +namespace InputCommon { + +/** + * A touch device factory that takes a list of button devices and combines them into a touch device. + */ +class TouchFromButtonFactory final : public Input::Factory<Input::TouchDevice> { +public: +    /** +     * Creates a touch device from a list of button devices +     */ +    std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override; +}; + +} // namespace InputCommon diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp index e63c73c4f..3f4eaf448 100644 --- a/src/input_common/udp/client.cpp +++ b/src/input_common/udp/client.cpp @@ -9,7 +9,6 @@  #include <functional>  #include <thread>  #include <boost/asio.hpp> -#include <boost/bind.hpp>  #include "common/logging/log.h"  #include "input_common/udp/client.h"  #include "input_common/udp/protocol.h" @@ -225,8 +224,7 @@ void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 clie          } else {              failure_callback();          } -    }) -        .detach(); +    }).detach();  }  CalibrationConfigurationJob::CalibrationConfigurationJob( @@ -280,8 +278,7 @@ CalibrationConfigurationJob::CalibrationConfigurationJob(          complete_event.Wait();          socket.Stop();          worker_thread.join(); -    }) -        .detach(); +    }).detach();  }  CalibrationConfigurationJob::~CalibrationConfigurationJob() { diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp index 8c6ef1394..4b347e47e 100644 --- a/src/input_common/udp/udp.cpp +++ b/src/input_common/udp/udp.cpp @@ -77,10 +77,11 @@ State::State() {          std::make_unique<Client>(status, Settings::values.udp_input_address,                                   Settings::values.udp_input_port, Settings::values.udp_pad_index); -    Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", -                                               std::make_shared<UDPTouchFactory>(status)); -    Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", -                                                std::make_shared<UDPMotionFactory>(status)); +    motion_factory = std::make_shared<UDPMotionFactory>(status); +    touch_factory = std::make_shared<UDPTouchFactory>(status); + +    Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", motion_factory); +    Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", touch_factory);  }  State::~State() { @@ -88,6 +89,11 @@ State::~State() {      Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");  } +std::vector<Common::ParamPackage> State::GetInputDevices() const { +    // TODO support binding udp devices +    return {}; +} +  void State::ReloadUDPClient() {      client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port,                           Settings::values.udp_pad_index); diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h index 4f83f0441..672a5c812 100644 --- a/src/input_common/udp/udp.h +++ b/src/input_common/udp/udp.h @@ -5,19 +5,26 @@  #pragma once  #include <memory> +#include <vector> +#include "common/param_package.h"  namespace InputCommon::CemuhookUDP {  class Client; +class UDPMotionFactory; +class UDPTouchFactory;  class State {  public:      State();      ~State();      void ReloadUDPClient(); +    std::vector<Common::ParamPackage> GetInputDevices() const;  private:      std::unique_ptr<Client> client; +    std::shared_ptr<UDPMotionFactory> motion_factory; +    std::shared_ptr<UDPTouchFactory> touch_factory;  };  std::unique_ptr<State> Init();  | 
