diff options
| author | liamwhite <liamwhite@users.noreply.github.com> | 2023-01-24 09:29:37 -0500 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-01-24 09:29:37 -0500 | 
| commit | a68af583ea378b48e2ed5a19f519a815ba89e40f (patch) | |
| tree | 2983c14a7d4bc2797259c7d97462a439bec629f3 | |
| parent | f99f618d45ad862c4bc23fc28c91d1c48218a3cb (diff) | |
| parent | d9ee7c32975bb8d840cf93a086d6b4be39d7bfd2 (diff) | |
Merge pull request #9492 from german77/joycon_release
Input_common: Implement custom joycon driver v2
58 files changed, 5812 insertions, 408 deletions
| diff --git a/src/common/input.h b/src/common/input.h index d27b1d772..d61cd7ca8 100644 --- a/src/common/input.h +++ b/src/common/input.h @@ -51,6 +51,8 @@ enum class PollingMode {      NFC,      // Enable infrared camera polling      IR, +    // Enable ring controller polling +    Ring,  };  enum class CameraFormat { @@ -62,21 +64,22 @@ enum class CameraFormat {      None,  }; -// Vibration reply from the controller -enum class VibrationError { -    None, +// Different results that can happen from a device request +enum class DriverResult { +    Success, +    WrongReply, +    Timeout, +    UnsupportedControllerType, +    HandleInUse, +    ErrorReadingData, +    ErrorWritingData, +    NoDeviceDetected, +    InvalidHandle,      NotSupported,      Disabled,      Unknown,  }; -// Polling mode reply from the controller -enum class PollingError { -    None, -    NotSupported, -    Unknown, -}; -  // Nfc reply from the controller  enum class NfcState {      Success, @@ -90,13 +93,6 @@ enum class NfcState {      Unknown,  }; -// Ir camera reply from the controller -enum class CameraError { -    None, -    NotSupported, -    Unknown, -}; -  // Hint for amplification curve to be used  enum class VibrationAmplificationType {      Linear, @@ -190,6 +186,8 @@ struct TouchStatus {  struct BodyColorStatus {      u32 body{};      u32 buttons{}; +    u32 left_grip{}; +    u32 right_grip{};  };  // HD rumble data @@ -228,17 +226,31 @@ enum class ButtonNames {      Engine,      // This will display the button by value instead of the button name      Value, + +    // Joycon button names      ButtonLeft,      ButtonRight,      ButtonDown,      ButtonUp, -    TriggerZ, -    TriggerR, -    TriggerL,      ButtonA,      ButtonB,      ButtonX,      ButtonY, +    ButtonPlus, +    ButtonMinus, +    ButtonHome, +    ButtonCapture, +    ButtonStickL, +    ButtonStickR, +    TriggerL, +    TriggerZL, +    TriggerSL, +    TriggerR, +    TriggerZR, +    TriggerSR, + +    // GC button names +    TriggerZ,      ButtonStart,      // DS4 button names @@ -316,22 +328,24 @@ class OutputDevice {  public:      virtual ~OutputDevice() = default; -    virtual void SetLED([[maybe_unused]] const LedStatus& led_status) {} +    virtual DriverResult SetLED([[maybe_unused]] const LedStatus& led_status) { +        return DriverResult::NotSupported; +    } -    virtual VibrationError SetVibration([[maybe_unused]] const VibrationStatus& vibration_status) { -        return VibrationError::NotSupported; +    virtual DriverResult SetVibration([[maybe_unused]] const VibrationStatus& vibration_status) { +        return DriverResult::NotSupported;      }      virtual bool IsVibrationEnabled() {          return false;      } -    virtual PollingError SetPollingMode([[maybe_unused]] PollingMode polling_mode) { -        return PollingError::NotSupported; +    virtual DriverResult SetPollingMode([[maybe_unused]] PollingMode polling_mode) { +        return DriverResult::NotSupported;      } -    virtual CameraError SetCameraFormat([[maybe_unused]] CameraFormat camera_format) { -        return CameraError::NotSupported; +    virtual DriverResult SetCameraFormat([[maybe_unused]] CameraFormat camera_format) { +        return DriverResult::NotSupported;      }      virtual NfcState SupportsNfc() const { diff --git a/src/common/settings.h b/src/common/settings.h index 80b2eeabc..4b4da4da2 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -483,6 +483,7 @@ struct Values {      Setting<bool> enable_raw_input{false, "enable_raw_input"};      Setting<bool> controller_navigation{true, "controller_navigation"}; +    Setting<bool> enable_joycon_driver{true, "enable_joycon_driver"};      SwitchableSetting<bool> vibration_enabled{true, "vibration_enabled"};      SwitchableSetting<bool> enable_accurate_vibrations{false, "enable_accurate_vibrations"}; diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index a959c9db9..6e9812e6e 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp @@ -2,6 +2,7 @@  // SPDX-License-Identifier: GPL-2.0-or-later  #include <algorithm> +#include <common/scope_exit.h>  #include "common/polyfill_ranges.h"  #include "common/thread.h" @@ -94,6 +95,7 @@ void EmulatedController::ReloadFromSettings() {          motion_params[index] = Common::ParamPackage(player.motions[index]);      } +    controller.color_values = {};      controller.colors_state.fullkey = {          .body = GetNpadColor(player.body_color_left),          .button = GetNpadColor(player.button_color_left), @@ -107,6 +109,8 @@ void EmulatedController::ReloadFromSettings() {          .button = GetNpadColor(player.button_color_right),      }; +    ring_params[0] = Common::ParamPackage(Settings::values.ringcon_analogs); +      // Other or debug controller should always be a pro controller      if (npad_id_type != NpadIdType::Other) {          SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type)); @@ -133,18 +137,28 @@ void EmulatedController::LoadDevices() {      trigger_params[LeftIndex] = button_params[Settings::NativeButton::ZL];      trigger_params[RightIndex] = button_params[Settings::NativeButton::ZR]; +    color_params[LeftIndex] = left_joycon; +    color_params[RightIndex] = right_joycon; +    color_params[LeftIndex].Set("color", true); +    color_params[RightIndex].Set("color", true); +      battery_params[LeftIndex] = left_joycon;      battery_params[RightIndex] = right_joycon;      battery_params[LeftIndex].Set("battery", true);      battery_params[RightIndex].Set("battery", true); -    camera_params = Common::ParamPackage{"engine:camera,camera:1"}; -    nfc_params = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"}; +    camera_params[0] = right_joycon; +    camera_params[0].Set("camera", true); +    camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"}; +    ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"}; +    nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"}; +    nfc_params[1] = right_joycon; +    nfc_params[1].Set("nfc", true);      output_params[LeftIndex] = left_joycon;      output_params[RightIndex] = right_joycon; -    output_params[2] = camera_params; -    output_params[3] = nfc_params; +    output_params[2] = camera_params[1]; +    output_params[3] = nfc_params[0];      output_params[LeftIndex].Set("output", true);      output_params[RightIndex].Set("output", true);      output_params[2].Set("output", true); @@ -160,8 +174,11 @@ void EmulatedController::LoadDevices() {                             Common::Input::CreateInputDevice);      std::ranges::transform(battery_params, battery_devices.begin(),                             Common::Input::CreateInputDevice); -    camera_devices = Common::Input::CreateInputDevice(camera_params); -    nfc_devices = Common::Input::CreateInputDevice(nfc_params); +    std::ranges::transform(color_params, color_devices.begin(), Common::Input::CreateInputDevice); +    std::ranges::transform(camera_params, camera_devices.begin(), Common::Input::CreateInputDevice); +    std::ranges::transform(ring_params, ring_analog_devices.begin(), +                           Common::Input::CreateInputDevice); +    std::ranges::transform(nfc_params, nfc_devices.begin(), Common::Input::CreateInputDevice);      std::ranges::transform(output_params, output_devices.begin(),                             Common::Input::CreateOutputDevice); @@ -323,6 +340,19 @@ void EmulatedController::ReloadInput() {          battery_devices[index]->ForceUpdate();      } +    for (std::size_t index = 0; index < color_devices.size(); ++index) { +        if (!color_devices[index]) { +            continue; +        } +        color_devices[index]->SetCallback({ +            .on_change = +                [this, index](const Common::Input::CallbackStatus& callback) { +                    SetColors(callback, index); +                }, +        }); +        color_devices[index]->ForceUpdate(); +    } +      for (std::size_t index = 0; index < motion_devices.size(); ++index) {          if (!motion_devices[index]) {              continue; @@ -336,22 +366,37 @@ void EmulatedController::ReloadInput() {          motion_devices[index]->ForceUpdate();      } -    if (camera_devices) { -        camera_devices->SetCallback({ +    for (std::size_t index = 0; index < camera_devices.size(); ++index) { +        if (!camera_devices[index]) { +            continue; +        } +        camera_devices[index]->SetCallback({              .on_change =                  [this](const Common::Input::CallbackStatus& callback) { SetCamera(callback); },          }); -        camera_devices->ForceUpdate(); +        camera_devices[index]->ForceUpdate();      } -    if (nfc_devices) { -        if (npad_id_type == NpadIdType::Handheld || npad_id_type == NpadIdType::Player1) { -            nfc_devices->SetCallback({ -                .on_change = -                    [this](const Common::Input::CallbackStatus& callback) { SetNfc(callback); }, -            }); -            nfc_devices->ForceUpdate(); +    for (std::size_t index = 0; index < ring_analog_devices.size(); ++index) { +        if (!ring_analog_devices[index]) { +            continue;          } +        ring_analog_devices[index]->SetCallback({ +            .on_change = +                [this](const Common::Input::CallbackStatus& callback) { SetRingAnalog(callback); }, +        }); +        ring_analog_devices[index]->ForceUpdate(); +    } + +    for (std::size_t index = 0; index < nfc_devices.size(); ++index) { +        if (!nfc_devices[index]) { +            continue; +        } +        nfc_devices[index]->SetCallback({ +            .on_change = +                [this](const Common::Input::CallbackStatus& callback) { SetNfc(callback); }, +        }); +        nfc_devices[index]->ForceUpdate();      }      // Register TAS devices. No need to force update @@ -421,6 +466,9 @@ void EmulatedController::UnloadInput() {      for (auto& battery : battery_devices) {          battery.reset();      } +    for (auto& color : color_devices) { +        color.reset(); +    }      for (auto& output : output_devices) {          output.reset();      } @@ -436,8 +484,15 @@ void EmulatedController::UnloadInput() {      for (auto& stick : virtual_stick_devices) {          stick.reset();      } -    camera_devices.reset(); -    nfc_devices.reset(); +    for (auto& camera : camera_devices) { +        camera.reset(); +    } +    for (auto& ring : ring_analog_devices) { +        ring.reset(); +    } +    for (auto& nfc : nfc_devices) { +        nfc.reset(); +    }  }  void EmulatedController::EnableConfiguration() { @@ -449,6 +504,11 @@ void EmulatedController::EnableConfiguration() {  void EmulatedController::DisableConfiguration() {      is_configuring = false; +    // Get Joycon colors before turning on the controller +    for (const auto& color_device : color_devices) { +        color_device->ForceUpdate(); +    } +      // Apply temporary npad type to the real controller      if (tmp_npad_type != npad_type) {          if (is_connected) { @@ -502,6 +562,9 @@ void EmulatedController::SaveCurrentConfig() {      for (std::size_t index = 0; index < player.motions.size(); ++index) {          player.motions[index] = motion_params[index].Serialize();      } +    if (npad_id_type == NpadIdType::Player1) { +        Settings::values.ringcon_analogs = ring_params[0].Serialize(); +    }  }  void EmulatedController::RestoreConfig() { @@ -773,17 +836,21 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,      if (index >= controller.stick_values.size()) {          return;      } -    std::unique_lock lock{mutex}; +    auto trigger_guard = +        SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Stick, !is_configuring); }); +    std::scoped_lock lock{mutex};      const auto stick_value = TransformToStick(callback);      // Only read stick values that have the same uuid or are over the threshold to avoid flapping      if (controller.stick_values[index].uuid != uuid) {          const bool is_tas = uuid == TAS_UUID;          if (is_tas && stick_value.x.value == 0 && stick_value.y.value == 0) { +            trigger_guard.Cancel();              return;          }          if (!is_tas && !stick_value.down && !stick_value.up && !stick_value.left &&              !stick_value.right) { +            trigger_guard.Cancel();              return;          }      } @@ -794,8 +861,6 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,      if (is_configuring) {          controller.analog_stick_state.left = {};          controller.analog_stick_state.right = {}; -        lock.unlock(); -        TriggerOnChange(ControllerTriggerType::Stick, false);          return;      } @@ -827,9 +892,6 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,          controller.npad_button_state.stick_r_down.Assign(controller.stick_values[index].down);          break;      } - -    lock.unlock(); -    TriggerOnChange(ControllerTriggerType::Stick, true);  }  void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callback, @@ -837,7 +899,9 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac      if (index >= controller.trigger_values.size()) {          return;      } -    std::unique_lock lock{mutex}; +    auto trigger_guard = +        SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Trigger, !is_configuring); }); +    std::scoped_lock lock{mutex};      const auto trigger_value = TransformToTrigger(callback);      // Only read trigger values that have the same uuid or are pressed once @@ -853,13 +917,12 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac      if (is_configuring) {          controller.gc_trigger_state.left = 0;          controller.gc_trigger_state.right = 0; -        lock.unlock(); -        TriggerOnChange(ControllerTriggerType::Trigger, false);          return;      }      // Only GC controllers have analog triggers      if (npad_type != NpadStyleIndex::GameCube) { +        trigger_guard.Cancel();          return;      } @@ -876,9 +939,6 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac          controller.npad_button_state.zr.Assign(trigger.pressed.value);          break;      } - -    lock.unlock(); -    TriggerOnChange(ControllerTriggerType::Trigger, true);  }  void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback, @@ -886,7 +946,8 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback      if (index >= controller.motion_values.size()) {          return;      } -    std::unique_lock lock{mutex}; +    SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Motion, !is_configuring); }); +    std::scoped_lock lock{mutex};      auto& raw_status = controller.motion_values[index].raw_status;      auto& emulated = controller.motion_values[index].emulated; @@ -907,8 +968,6 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback      force_update_motion = raw_status.force_update;      if (is_configuring) { -        lock.unlock(); -        TriggerOnChange(ControllerTriggerType::Motion, false);          return;      } @@ -918,9 +977,56 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback      motion.rotation = emulated.GetRotations();      motion.orientation = emulated.GetOrientation();      motion.is_at_rest = !emulated.IsMoving(motion_sensitivity); +} -    lock.unlock(); -    TriggerOnChange(ControllerTriggerType::Motion, true); +void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback, +                                   std::size_t index) { +    if (index >= controller.color_values.size()) { +        return; +    } +    auto trigger_guard = +        SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Color, !is_configuring); }); +    std::scoped_lock lock{mutex}; +    controller.color_values[index] = TransformToColor(callback); + +    if (is_configuring) { +        return; +    } + +    if (controller.color_values[index].body == 0) { +        trigger_guard.Cancel(); +        return; +    } + +    controller.colors_state.fullkey = { +        .body = GetNpadColor(controller.color_values[index].body), +        .button = GetNpadColor(controller.color_values[index].buttons), +    }; +    if (npad_type == NpadStyleIndex::ProController) { +        controller.colors_state.left = { +            .body = GetNpadColor(controller.color_values[index].left_grip), +            .button = GetNpadColor(controller.color_values[index].buttons), +        }; +        controller.colors_state.right = { +            .body = GetNpadColor(controller.color_values[index].right_grip), +            .button = GetNpadColor(controller.color_values[index].buttons), +        }; +    } else { +        switch (index) { +        case LeftIndex: +            controller.colors_state.left = { +                .body = GetNpadColor(controller.color_values[index].body), +                .button = GetNpadColor(controller.color_values[index].buttons), +            }; +            break; +        case RightIndex: +            controller.colors_state.right = { +                .body = GetNpadColor(controller.color_values[index].body), +                .button = GetNpadColor(controller.color_values[index].buttons), +            }; +            break; +        } +    }  }  void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callback, @@ -928,12 +1034,11 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac      if (index >= controller.battery_values.size()) {          return;      } -    std::unique_lock lock{mutex}; +    SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Battery, !is_configuring); }); +    std::scoped_lock lock{mutex};      controller.battery_values[index] = TransformToBattery(callback);      if (is_configuring) { -        lock.unlock(); -        TriggerOnChange(ControllerTriggerType::Battery, false);          return;      } @@ -989,18 +1094,14 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac          };          break;      } - -    lock.unlock(); -    TriggerOnChange(ControllerTriggerType::Battery, true);  }  void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback) { -    std::unique_lock lock{mutex}; +    SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::IrSensor, !is_configuring); }); +    std::scoped_lock lock{mutex};      controller.camera_values = TransformToCamera(callback);      if (is_configuring) { -        lock.unlock(); -        TriggerOnChange(ControllerTriggerType::IrSensor, false);          return;      } @@ -1008,18 +1109,28 @@ void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback      controller.camera_state.format =          static_cast<Core::IrSensor::ImageTransferProcessorFormat>(controller.camera_values.format);      controller.camera_state.data = controller.camera_values.data; +} -    lock.unlock(); -    TriggerOnChange(ControllerTriggerType::IrSensor, true); +void EmulatedController::SetRingAnalog(const Common::Input::CallbackStatus& callback) { +    SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::RingController, !is_configuring); }); +    std::scoped_lock lock{mutex}; +    const auto force_value = TransformToStick(callback); + +    controller.ring_analog_value = force_value.x; + +    if (is_configuring) { +        return; +    } + +    controller.ring_analog_state.force = force_value.x.value;  }  void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) { -    std::unique_lock lock{mutex}; +    SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Nfc, !is_configuring); }); +    std::scoped_lock lock{mutex};      controller.nfc_values = TransformToNfc(callback);      if (is_configuring) { -        lock.unlock(); -        TriggerOnChange(ControllerTriggerType::Nfc, false);          return;      } @@ -1027,9 +1138,6 @@ void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {          controller.nfc_values.state,          controller.nfc_values.data,      }; - -    lock.unlock(); -    TriggerOnChange(ControllerTriggerType::Nfc, true);  }  bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) { @@ -1061,7 +1169,7 @@ bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue v          .type = type,      };      return output_devices[device_index]->SetVibration(status) == -           Common::Input::VibrationError::None; +           Common::Input::DriverResult::Success;  }  bool EmulatedController::IsVibrationEnabled(std::size_t device_index) { @@ -1083,16 +1191,32 @@ bool EmulatedController::IsVibrationEnabled(std::size_t device_index) {      return output_devices[device_index]->IsVibrationEnabled();  } -bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode) { -    LOG_INFO(Service_HID, "Set polling mode {}", polling_mode); -    auto& output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; +Common::Input::DriverResult EmulatedController::SetPollingMode( +    EmulatedDeviceIndex device_index, Common::Input::PollingMode polling_mode) { +    LOG_INFO(Service_HID, "Set polling mode {}, device_index={}", polling_mode, device_index); + +    auto& left_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Left)]; +    auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];      auto& nfc_output_device = output_devices[3]; -    const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode); -    const auto mapped_nfc_result = output_device->SetPollingMode(polling_mode); +    if (device_index == EmulatedDeviceIndex::LeftIndex) { +        return left_output_device->SetPollingMode(polling_mode); +    } + +    if (device_index == EmulatedDeviceIndex::RightIndex) { +        const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode); +        const auto mapped_nfc_result = right_output_device->SetPollingMode(polling_mode); + +        if (virtual_nfc_result == Common::Input::DriverResult::Success) { +            return virtual_nfc_result; +        } +        return mapped_nfc_result; +    } -    return virtual_nfc_result == Common::Input::PollingError::None || -           mapped_nfc_result == Common::Input::PollingError::None; +    left_output_device->SetPollingMode(polling_mode); +    right_output_device->SetPollingMode(polling_mode); +    nfc_output_device->SetPollingMode(polling_mode); +    return Common::Input::DriverResult::Success;  }  bool EmulatedController::SetCameraFormat( @@ -1103,13 +1227,22 @@ bool EmulatedController::SetCameraFormat(      auto& camera_output_device = output_devices[2];      if (right_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>( -            camera_format)) == Common::Input::CameraError::None) { +            camera_format)) == Common::Input::DriverResult::Success) {          return true;      }      // Fallback to Qt camera if native device doesn't have support      return camera_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>( -               camera_format)) == Common::Input::CameraError::None; +               camera_format)) == Common::Input::DriverResult::Success; +} + +Common::ParamPackage EmulatedController::GetRingParam() const { +    return ring_params[0]; +} + +void EmulatedController::SetRingParam(Common::ParamPackage param) { +    ring_params[0] = std::move(param); +    ReloadInput();  }  bool EmulatedController::HasNfc() const { @@ -1263,39 +1396,35 @@ void EmulatedController::Connect(bool use_temporary_value) {          return;      } -    std::unique_lock lock{mutex}; +    auto trigger_guard = +        SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Connected, !is_configuring); }); +    std::scoped_lock lock{mutex};      if (is_configuring) {          tmp_is_connected = true; -        lock.unlock(); -        TriggerOnChange(ControllerTriggerType::Connected, false);          return;      }      if (is_connected) { +        trigger_guard.Cancel();          return;      }      is_connected = true; - -    lock.unlock(); -    TriggerOnChange(ControllerTriggerType::Connected, true);  }  void EmulatedController::Disconnect() { -    std::unique_lock lock{mutex}; +    auto trigger_guard = +        SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Disconnected, !is_configuring); }); +    std::scoped_lock lock{mutex};      if (is_configuring) {          tmp_is_connected = false; -        lock.unlock(); -        TriggerOnChange(ControllerTriggerType::Disconnected, false);          return;      }      if (!is_connected) { +        trigger_guard.Cancel();          return;      }      is_connected = false; - -    lock.unlock(); -    TriggerOnChange(ControllerTriggerType::Disconnected, true);  }  bool EmulatedController::IsConnected(bool get_temporary_value) const { @@ -1320,19 +1449,21 @@ NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) c  }  void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) { -    std::unique_lock lock{mutex}; +    auto trigger_guard = +        SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Type, !is_configuring); }); +    std::scoped_lock lock{mutex};      if (is_configuring) {          if (tmp_npad_type == npad_type_) { +            trigger_guard.Cancel();              return;          }          tmp_npad_type = npad_type_; -        lock.unlock(); -        TriggerOnChange(ControllerTriggerType::Type, false);          return;      }      if (npad_type == npad_type_) { +        trigger_guard.Cancel();          return;      }      if (is_connected) { @@ -1340,9 +1471,6 @@ void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {                      NpadIdTypeToIndex(npad_id_type));      }      npad_type = npad_type_; - -    lock.unlock(); -    TriggerOnChange(ControllerTriggerType::Type, true);  }  LedPattern EmulatedController::GetLedPattern() const { @@ -1403,6 +1531,10 @@ CameraValues EmulatedController::GetCameraValues() const {      return controller.camera_values;  } +RingAnalogValue EmulatedController::GetRingSensorValues() const { +    return controller.ring_analog_value; +} +  HomeButtonState EmulatedController::GetHomeButtons() const {      std::scoped_lock lock{mutex};      if (is_configuring) { @@ -1436,7 +1568,7 @@ DebugPadButton EmulatedController::GetDebugPadButtons() const {  }  AnalogSticks EmulatedController::GetSticks() const { -    std::unique_lock lock{mutex}; +    std::scoped_lock lock{mutex};      if (is_configuring) {          return {}; @@ -1486,6 +1618,10 @@ const CameraState& EmulatedController::GetCamera() const {      return controller.camera_state;  } +RingSensorForce EmulatedController::GetRingSensorForce() const { +    return controller.ring_analog_state; +} +  const NfcState& EmulatedController::GetNfc() const {      std::scoped_lock lock{mutex};      return controller.nfc_state; diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h index a398543a6..3ac77b2b5 100644 --- a/src/core/hid/emulated_controller.h +++ b/src/core/hid/emulated_controller.h @@ -35,19 +35,27 @@ using ControllerMotionDevices =      std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeMotion::NumMotions>;  using TriggerDevices =      std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeTrigger::NumTriggers>; +using ColorDevices = +    std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;  using BatteryDevices =      std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>; -using CameraDevices = std::unique_ptr<Common::Input::InputDevice>; -using NfcDevices = std::unique_ptr<Common::Input::InputDevice>; +using CameraDevices = +    std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>; +using RingAnalogDevices = +    std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>; +using NfcDevices = +    std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;  using OutputDevices = std::array<std::unique_ptr<Common::Input::OutputDevice>, output_devices_size>;  using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>;  using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>;  using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>;  using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>; +using ColorParams = std::array<Common::ParamPackage, max_emulated_controllers>;  using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>; -using CameraParams = Common::ParamPackage; -using NfcParams = Common::ParamPackage; +using CameraParams = std::array<Common::ParamPackage, max_emulated_controllers>; +using RingAnalogParams = std::array<Common::ParamPackage, max_emulated_controllers>; +using NfcParams = std::array<Common::ParamPackage, max_emulated_controllers>;  using OutputParams = std::array<Common::ParamPackage, output_devices_size>;  using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>; @@ -58,6 +66,7 @@ using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::Native  using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>;  using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>;  using CameraValues = Common::Input::CameraStatus; +using RingAnalogValue = Common::Input::AnalogStatus;  using NfcValues = Common::Input::NfcStatus;  using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>; @@ -84,6 +93,10 @@ struct CameraState {      std::size_t sample{};  }; +struct RingSensorForce { +    f32 force; +}; +  struct NfcState {      Common::Input::NfcState state{};      std::vector<u8> data{}; @@ -116,6 +129,7 @@ struct ControllerStatus {      BatteryValues battery_values{};      VibrationValues vibration_values{};      CameraValues camera_values{}; +    RingAnalogValue ring_analog_value{};      NfcValues nfc_values{};      // Data for HID serices @@ -129,6 +143,7 @@ struct ControllerStatus {      ControllerColors colors_state{};      BatteryLevelState battery_state{};      CameraState camera_state{}; +    RingSensorForce ring_analog_state{};      NfcState nfc_state{};  }; @@ -141,6 +156,7 @@ enum class ControllerTriggerType {      Battery,      Vibration,      IrSensor, +    RingController,      Nfc,      Connected,      Disconnected, @@ -294,6 +310,9 @@ public:      /// Returns the latest camera status from the controller with parameters      CameraValues GetCameraValues() const; +    /// Returns the latest status of analog input from the ring sensor with parameters +    RingAnalogValue GetRingSensorValues() const; +      /// Returns the latest status of button input for the hid::HomeButton service      HomeButtonState GetHomeButtons() const; @@ -324,6 +343,9 @@ public:      /// Returns the latest camera status from the controller      const CameraState& GetCamera() const; +    /// Returns the latest ringcon force sensor value +    RingSensorForce GetRingSensorForce() const; +      /// Returns the latest ntag status from the controller      const NfcState& GetNfc() const; @@ -341,10 +363,12 @@ public:      /**       * Sets the desired data to be polled from a controller +     * @param device_index index of the controller to set the polling mode       * @param polling_mode type of input desired buttons, gyro, nfc, ir, etc. -     * @return true if SetPollingMode was successfull +     * @return driver result from this command       */ -    bool SetPollingMode(Common::Input::PollingMode polling_mode); +    Common::Input::DriverResult SetPollingMode(EmulatedDeviceIndex device_index, +                                               Common::Input::PollingMode polling_mode);      /**       * Sets the desired camera format to be polled from a controller @@ -353,6 +377,15 @@ public:       */      bool SetCameraFormat(Core::IrSensor::ImageTransferProcessorFormat camera_format); +    // Returns the current mapped ring device +    Common::ParamPackage GetRingParam() const; + +    /** +     * Updates the current mapped ring device +     * @param param ParamPackage with ring sensor data to be mapped +     */ +    void SetRingParam(Common::ParamPackage param); +      /// Returns true if the device has nfc support      bool HasNfc() const; @@ -433,9 +466,16 @@ private:      void SetMotion(const Common::Input::CallbackStatus& callback, std::size_t index);      /** +     * Updates the color status of the controller +     * @param callback A CallbackStatus containing the color status +     * @param index color ID of the to be updated +     */ +    void SetColors(const Common::Input::CallbackStatus& callback, std::size_t index); + +    /**       * Updates the battery status of the controller       * @param callback A CallbackStatus containing the battery status -     * @param index Button ID of the to be updated +     * @param index battery ID of the to be updated       */      void SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index); @@ -446,6 +486,12 @@ private:      void SetCamera(const Common::Input::CallbackStatus& callback);      /** +     * Updates the ring analog sensor status of the ring controller +     * @param callback A CallbackStatus containing the force status +     */ +    void SetRingAnalog(const Common::Input::CallbackStatus& callback); + +    /**       * Updates the nfc status of the controller       * @param callback A CallbackStatus containing the nfc status       */ @@ -484,7 +530,9 @@ private:      ControllerMotionParams motion_params;      TriggerParams trigger_params;      BatteryParams battery_params; +    ColorParams color_params;      CameraParams camera_params; +    RingAnalogParams ring_params;      NfcParams nfc_params;      OutputParams output_params; @@ -493,7 +541,9 @@ private:      ControllerMotionDevices motion_devices;      TriggerDevices trigger_devices;      BatteryDevices battery_devices; +    ColorDevices color_devices;      CameraDevices camera_devices; +    RingAnalogDevices ring_analog_devices;      NfcDevices nfc_devices;      OutputDevices output_devices; diff --git a/src/core/hid/emulated_devices.cpp b/src/core/hid/emulated_devices.cpp index e421828d2..836f32c0f 100644 --- a/src/core/hid/emulated_devices.cpp +++ b/src/core/hid/emulated_devices.cpp @@ -14,7 +14,6 @@ EmulatedDevices::EmulatedDevices() = default;  EmulatedDevices::~EmulatedDevices() = default;  void EmulatedDevices::ReloadFromSettings() { -    ring_params = Common::ParamPackage(Settings::values.ringcon_analogs);      ReloadInput();  } @@ -66,8 +65,6 @@ void EmulatedDevices::ReloadInput() {          key_index++;      } -    ring_analog_device = Common::Input::CreateInputDevice(ring_params); -      for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) {          if (!mouse_button_devices[index]) {              continue; @@ -122,13 +119,6 @@ void EmulatedDevices::ReloadInput() {                  },          });      } - -    if (ring_analog_device) { -        ring_analog_device->SetCallback({ -            .on_change = -                [this](const Common::Input::CallbackStatus& callback) { SetRingAnalog(callback); }, -        }); -    }  }  void EmulatedDevices::UnloadInput() { @@ -145,7 +135,6 @@ void EmulatedDevices::UnloadInput() {      for (auto& button : keyboard_modifier_devices) {          button.reset();      } -    ring_analog_device.reset();  }  void EmulatedDevices::EnableConfiguration() { @@ -165,7 +154,6 @@ void EmulatedDevices::SaveCurrentConfig() {      if (!is_configuring) {          return;      } -    Settings::values.ringcon_analogs = ring_params.Serialize();  }  void EmulatedDevices::RestoreConfig() { @@ -175,15 +163,6 @@ void EmulatedDevices::RestoreConfig() {      ReloadFromSettings();  } -Common::ParamPackage EmulatedDevices::GetRingParam() const { -    return ring_params; -} - -void EmulatedDevices::SetRingParam(Common::ParamPackage param) { -    ring_params = std::move(param); -    ReloadInput(); -} -  void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& callback,                                          std::size_t index) {      if (index >= device_status.keyboard_values.size()) { @@ -430,23 +409,6 @@ void EmulatedDevices::SetMouseStick(const Common::Input::CallbackStatus& callbac      TriggerOnChange(DeviceTriggerType::Mouse);  } -void EmulatedDevices::SetRingAnalog(const Common::Input::CallbackStatus& callback) { -    std::lock_guard lock{mutex}; -    const auto force_value = TransformToStick(callback); - -    device_status.ring_analog_value = force_value.x; - -    if (is_configuring) { -        device_status.ring_analog_value = {}; -        TriggerOnChange(DeviceTriggerType::RingController); -        return; -    } - -    device_status.ring_analog_state.force = force_value.x.value; - -    TriggerOnChange(DeviceTriggerType::RingController); -} -  KeyboardValues EmulatedDevices::GetKeyboardValues() const {      std::scoped_lock lock{mutex};      return device_status.keyboard_values; @@ -462,10 +424,6 @@ MouseButtonValues EmulatedDevices::GetMouseButtonsValues() const {      return device_status.mouse_button_values;  } -RingAnalogValue EmulatedDevices::GetRingSensorValues() const { -    return device_status.ring_analog_value; -} -  KeyboardKey EmulatedDevices::GetKeyboard() const {      std::scoped_lock lock{mutex};      return device_status.keyboard_state; @@ -491,10 +449,6 @@ AnalogStickState EmulatedDevices::GetMouseWheel() const {      return device_status.mouse_wheel_state;  } -RingSensorForce EmulatedDevices::GetRingSensorForce() const { -    return device_status.ring_analog_state; -} -  void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {      std::scoped_lock lock{callback_mutex};      for (const auto& poller_pair : callback_list) { diff --git a/src/core/hid/emulated_devices.h b/src/core/hid/emulated_devices.h index 4cdbf9dc6..76f9150df 100644 --- a/src/core/hid/emulated_devices.h +++ b/src/core/hid/emulated_devices.h @@ -26,11 +26,9 @@ using MouseButtonDevices = std::array<std::unique_ptr<Common::Input::InputDevice  using MouseAnalogDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,                                        Settings::NativeMouseWheel::NumMouseWheels>;  using MouseStickDevice = std::unique_ptr<Common::Input::InputDevice>; -using RingAnalogDevice = std::unique_ptr<Common::Input::InputDevice>;  using MouseButtonParams =      std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>; -using RingAnalogParams = Common::ParamPackage;  using KeyboardValues =      std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>; @@ -41,17 +39,12 @@ using MouseButtonValues =  using MouseAnalogValues =      std::array<Common::Input::AnalogStatus, Settings::NativeMouseWheel::NumMouseWheels>;  using MouseStickValue = Common::Input::TouchStatus; -using RingAnalogValue = Common::Input::AnalogStatus;  struct MousePosition {      f32 x;      f32 y;  }; -struct RingSensorForce { -    f32 force; -}; -  struct DeviceStatus {      // Data from input_common      KeyboardValues keyboard_values{}; @@ -59,7 +52,6 @@ struct DeviceStatus {      MouseButtonValues mouse_button_values{};      MouseAnalogValues mouse_analog_values{};      MouseStickValue mouse_stick_value{}; -    RingAnalogValue ring_analog_value{};      // Data for HID serices      KeyboardKey keyboard_state{}; @@ -67,7 +59,6 @@ struct DeviceStatus {      MouseButton mouse_button_state{};      MousePosition mouse_position_state{};      AnalogStickState mouse_wheel_state{}; -    RingSensorForce ring_analog_state{};  };  enum class DeviceTriggerType { @@ -138,9 +129,6 @@ public:      /// Returns the latest status of button input from the mouse with parameters      MouseButtonValues GetMouseButtonsValues() const; -    /// Returns the latest status of analog input from the ring sensor with parameters -    RingAnalogValue GetRingSensorValues() const; -      /// Returns the latest status of button input from the keyboard      KeyboardKey GetKeyboard() const; @@ -156,9 +144,6 @@ public:      /// Returns the latest mouse wheel change      AnalogStickState GetMouseWheel() const; -    /// Returns the latest ringcon force sensor value -    RingSensorForce GetRingSensorForce() const; -      /**       * Adds a callback to the list of events       * @param update_callback InterfaceUpdateCallback that will be triggered @@ -224,14 +209,11 @@ private:      bool is_configuring{false}; -    RingAnalogParams ring_params; -      KeyboardDevices keyboard_devices;      KeyboardModifierDevices keyboard_modifier_devices;      MouseButtonDevices mouse_button_devices;      MouseAnalogDevices mouse_analog_devices;      MouseStickDevice mouse_stick_device; -    RingAnalogDevice ring_analog_device;      mutable std::mutex mutex;      mutable std::mutex callback_mutex; diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp index 502692875..3f7b8c090 100644 --- a/src/core/hid/input_converter.cpp +++ b/src/core/hid/input_converter.cpp @@ -304,6 +304,18 @@ Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& cal      return nfc;  } +Common::Input::BodyColorStatus TransformToColor(const Common::Input::CallbackStatus& callback) { +    switch (callback.type) { +    case Common::Input::InputType::Color: +        return callback.color_status; +        break; +    default: +        LOG_ERROR(Input, "Conversion from type {} to color not implemented", callback.type); +        return {}; +        break; +    } +} +  void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) {      const auto& properties = analog.properties;      float& raw_value = analog.raw_value; diff --git a/src/core/hid/input_converter.h b/src/core/hid/input_converter.h index b7eb6e660..c51c03e57 100644 --- a/src/core/hid/input_converter.h +++ b/src/core/hid/input_converter.h @@ -88,11 +88,19 @@ Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatu   * Converts raw input data into a valid nfc status.   *   * @param callback Supported callbacks: Nfc. - * @return A valid CameraObject object. + * @return A valid data tag vector.   */  Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& callback);  /** + * Converts raw input data into a valid color status. + * + * @param callback Supported callbacks: Color. + * @return A valid Color object. + */ +Common::Input::BodyColorStatus TransformToColor(const Common::Input::CallbackStatus& callback); + +/**   * Converts raw analog data into a valid analog value   * @param analog An analog object containing raw data and properties   * @param clamp_value determines if the value needs to be clamped between -1.0f and 1.0f. diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 2f871de31..5713f1288 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -272,6 +272,8 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {          }          break;      case Core::HID::NpadStyleIndex::JoyconLeft: +        shared_memory->fullkey_color.attribute = ColorAttribute::Ok; +        shared_memory->fullkey_color.fullkey = body_colors.left;          shared_memory->joycon_color.attribute = ColorAttribute::Ok;          shared_memory->joycon_color.left = body_colors.left;          shared_memory->battery_level_dual = battery_level.left.battery_level; @@ -285,6 +287,8 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {          shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1);          break;      case Core::HID::NpadStyleIndex::JoyconRight: +        shared_memory->fullkey_color.attribute = ColorAttribute::Ok; +        shared_memory->fullkey_color.fullkey = body_colors.right;          shared_memory->joycon_color.attribute = ColorAttribute::Ok;          shared_memory->joycon_color.right = body_colors.right;          shared_memory->battery_level_right = battery_level.right.battery_level; @@ -332,6 +336,20 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {      controller.is_connected = true;      controller.device->Connect(); +    controller.device->SetLedPattern(); +    if (controller_type == Core::HID::NpadStyleIndex::JoyconDual) { +        if (controller.is_dual_left_connected) { +            controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::LeftIndex, +                                              Common::Input::PollingMode::Active); +        } +        if (controller.is_dual_right_connected) { +            controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, +                                              Common::Input::PollingMode::Active); +        } +    } else { +        controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::AllDevices, +                                          Common::Input::PollingMode::Active); +    }      SignalStyleSetChangedEvent(npad_id);      WriteEmptyEntry(controller.shared_memory);  } diff --git a/src/core/hle/service/hid/hidbus.cpp b/src/core/hle/service/hid/hidbus.cpp index e5e50845f..17252a84a 100644 --- a/src/core/hle/service/hid/hidbus.cpp +++ b/src/core/hle/service/hid/hidbus.cpp @@ -297,13 +297,13 @@ void HidBus::EnableExternalDevice(Kernel::HLERequestContext& ctx) {      const auto parameters{rp.PopRaw<Parameters>()}; -    LOG_INFO(Service_HID, -             "called, enable={}, abstracted_pad_id={}, bus_type={}, internal_index={}, " -             "player_number={}, is_valid={}, inval={}, applet_resource_user_id{}", -             parameters.enable, parameters.bus_handle.abstracted_pad_id, -             parameters.bus_handle.bus_type, parameters.bus_handle.internal_index, -             parameters.bus_handle.player_number, parameters.bus_handle.is_valid, parameters.inval, -             parameters.applet_resource_user_id); +    LOG_DEBUG(Service_HID, +              "called, enable={}, abstracted_pad_id={}, bus_type={}, internal_index={}, " +              "player_number={}, is_valid={}, inval={}, applet_resource_user_id{}", +              parameters.enable, parameters.bus_handle.abstracted_pad_id, +              parameters.bus_handle.bus_type, parameters.bus_handle.internal_index, +              parameters.bus_handle.player_number, parameters.bus_handle.is_valid, parameters.inval, +              parameters.applet_resource_user_id);      const auto device_index = GetDeviceIndexFromHandle(parameters.bus_handle); @@ -326,11 +326,11 @@ void HidBus::GetExternalDeviceId(Kernel::HLERequestContext& ctx) {      IPC::RequestParser rp{ctx};      const auto bus_handle_{rp.PopRaw<BusHandle>()}; -    LOG_INFO(Service_HID, -             "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, " -             "is_valid={}", -             bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, -             bus_handle_.player_number, bus_handle_.is_valid); +    LOG_DEBUG(Service_HID, +              "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, " +              "is_valid={}", +              bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, +              bus_handle_.player_number, bus_handle_.is_valid);      const auto device_index = GetDeviceIndexFromHandle(bus_handle_); diff --git a/src/core/hle/service/hid/hidbus/ringcon.cpp b/src/core/hle/service/hid/hidbus/ringcon.cpp index 57f1a2a26..78ed47014 100644 --- a/src/core/hle/service/hid/hidbus/ringcon.cpp +++ b/src/core/hle/service/hid/hidbus/ringcon.cpp @@ -1,7 +1,7 @@  // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project  // SPDX-License-Identifier: GPL-2.0-or-later -#include "core/hid/emulated_devices.h" +#include "core/hid/emulated_controller.h"  #include "core/hid/hid_core.h"  #include "core/hle/kernel/k_event.h"  #include "core/hle/kernel/k_readable_event.h" @@ -12,16 +12,20 @@ namespace Service::HID {  RingController::RingController(Core::HID::HIDCore& hid_core_,                                 KernelHelpers::ServiceContext& service_context_)      : HidbusBase(service_context_) { -    input = hid_core_.GetEmulatedDevices(); +    input = hid_core_.GetEmulatedController(Core::HID::NpadIdType::Player1);  }  RingController::~RingController() = default;  void RingController::OnInit() { +    input->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, +                          Common::Input::PollingMode::Ring);      return;  }  void RingController::OnRelease() { +    input->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, +                          Common::Input::PollingMode::Active);      return;  }; diff --git a/src/core/hle/service/hid/hidbus/ringcon.h b/src/core/hle/service/hid/hidbus/ringcon.h index b37df50ac..845ce85a5 100644 --- a/src/core/hle/service/hid/hidbus/ringcon.h +++ b/src/core/hle/service/hid/hidbus/ringcon.h @@ -9,7 +9,7 @@  #include "core/hle/service/hid/hidbus/hidbus_base.h"  namespace Core::HID { -class EmulatedDevices; +class EmulatedController;  } // namespace Core::HID  namespace Service::HID { @@ -248,6 +248,6 @@ private:          .zero = {.value = idle_value, .crc = 225},      }; -    Core::HID::EmulatedDevices* input; +    Core::HID::EmulatedController* input;  };  } // namespace Service::HID diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp index 6a3453457..52f402c56 100644 --- a/src/core/hle/service/hid/irs.cpp +++ b/src/core/hle/service/hid/irs.cpp @@ -108,6 +108,8 @@ void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) {      auto result = IsIrCameraHandleValid(parameters.camera_handle);      if (result.IsSuccess()) {          // TODO: Stop Image processor +        npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, +                                    Common::Input::PollingMode::Active);          result = ResultSuccess;      } @@ -139,6 +141,8 @@ void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) {          MakeProcessor<MomentProcessor>(parameters.camera_handle, device);          auto& image_transfer_processor = GetProcessor<MomentProcessor>(parameters.camera_handle);          image_transfer_processor.SetConfig(parameters.processor_config); +        npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, +                                    Common::Input::PollingMode::IR);      }      IPC::ResponseBuilder rb{ctx, 2}; @@ -170,6 +174,8 @@ void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) {          auto& image_transfer_processor =              GetProcessor<ClusteringProcessor>(parameters.camera_handle);          image_transfer_processor.SetConfig(parameters.processor_config); +        npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, +                                    Common::Input::PollingMode::IR);      }      IPC::ResponseBuilder rb{ctx, 2}; @@ -219,6 +225,8 @@ void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) {              GetProcessor<ImageTransferProcessor>(parameters.camera_handle);          image_transfer_processor.SetConfig(parameters.processor_config);          image_transfer_processor.SetTransferMemoryPointer(transfer_memory); +        npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, +                                    Common::Input::PollingMode::IR);      }      IPC::ResponseBuilder rb{ctx, 2}; @@ -294,6 +302,8 @@ void IRS::RunTeraPluginProcessor(Kernel::HLERequestContext& ctx) {          auto& image_transfer_processor =              GetProcessor<TeraPluginProcessor>(parameters.camera_handle);          image_transfer_processor.SetConfig(parameters.processor_config); +        npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, +                                    Common::Input::PollingMode::IR);      }      IPC::ResponseBuilder rb{ctx, 2}; @@ -343,6 +353,8 @@ void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) {          MakeProcessor<PointingProcessor>(camera_handle, device);          auto& image_transfer_processor = GetProcessor<PointingProcessor>(camera_handle);          image_transfer_processor.SetConfig(processor_config); +        npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, +                                    Common::Input::PollingMode::IR);      }      IPC::ResponseBuilder rb{ctx, 2}; @@ -453,6 +465,8 @@ void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) {              GetProcessor<ImageTransferProcessor>(parameters.camera_handle);          image_transfer_processor.SetConfig(parameters.processor_config);          image_transfer_processor.SetTransferMemoryPointer(transfer_memory); +        npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, +                                    Common::Input::PollingMode::IR);      }      IPC::ResponseBuilder rb{ctx, 2}; @@ -479,6 +493,8 @@ void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) {          MakeProcessor<IrLedProcessor>(camera_handle, device);          auto& image_transfer_processor = GetProcessor<IrLedProcessor>(camera_handle);          image_transfer_processor.SetConfig(processor_config); +        npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, +                                    Common::Input::PollingMode::IR);      }      IPC::ResponseBuilder rb{ctx, 2}; @@ -504,6 +520,8 @@ void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) {      auto result = IsIrCameraHandleValid(parameters.camera_handle);      if (result.IsSuccess()) {          // TODO: Stop image processor async +        npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, +                                    Common::Input::PollingMode::Active);          result = ResultSuccess;      } diff --git a/src/core/hle/service/nfc/nfc_device.cpp b/src/core/hle/service/nfc/nfc_device.cpp index 78578f723..9a3234e8c 100644 --- a/src/core/hle/service/nfc/nfc_device.cpp +++ b/src/core/hle/service/nfc/nfc_device.cpp @@ -130,7 +130,9 @@ Result NfcDevice::StartDetection(NFP::TagProtocol allowed_protocol) {          return WrongDeviceState;      } -    if (!npad_device->SetPollingMode(Common::Input::PollingMode::NFC)) { +    if (npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, +                                    Common::Input::PollingMode::NFC) != +        Common::Input::DriverResult::Success) {          LOG_ERROR(Service_NFC, "Nfc not supported");          return NfcDisabled;      } @@ -141,7 +143,8 @@ Result NfcDevice::StartDetection(NFP::TagProtocol allowed_protocol) {  }  Result NfcDevice::StopDetection() { -    npad_device->SetPollingMode(Common::Input::PollingMode::Active); +    npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, +                                Common::Input::PollingMode::Active);      if (device_state == NFP::DeviceState::Initialized) {          return ResultSuccess; diff --git a/src/core/hle/service/nfp/nfp_device.cpp b/src/core/hle/service/nfp/nfp_device.cpp index c860fd1a1..e67a76f55 100644 --- a/src/core/hle/service/nfp/nfp_device.cpp +++ b/src/core/hle/service/nfp/nfp_device.cpp @@ -152,7 +152,9 @@ Result NfpDevice::StartDetection(TagProtocol allowed_protocol) {          return WrongDeviceState;      } -    if (!npad_device->SetPollingMode(Common::Input::PollingMode::NFC)) { +    if (npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, +                                    Common::Input::PollingMode::NFC) != +        Common::Input::DriverResult::Success) {          LOG_ERROR(Service_NFP, "Nfc not supported");          return NfcDisabled;      } @@ -163,7 +165,8 @@ Result NfpDevice::StartDetection(TagProtocol allowed_protocol) {  }  Result NfpDevice::StopDetection() { -    npad_device->SetPollingMode(Common::Input::PollingMode::Active); +    npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, +                                Common::Input::PollingMode::Active);      if (device_state == DeviceState::Initialized) {          return ResultSuccess; diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index cef2c4d52..e3b627e4f 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -51,8 +51,29 @@ endif()  if (ENABLE_SDL2)      target_sources(input_common PRIVATE +        drivers/joycon.cpp +        drivers/joycon.h          drivers/sdl_driver.cpp          drivers/sdl_driver.h +        helpers/joycon_driver.cpp +        helpers/joycon_driver.h +        helpers/joycon_protocol/calibration.cpp +        helpers/joycon_protocol/calibration.h +        helpers/joycon_protocol/common_protocol.cpp +        helpers/joycon_protocol/common_protocol.h +        helpers/joycon_protocol/generic_functions.cpp +        helpers/joycon_protocol/generic_functions.h +        helpers/joycon_protocol/joycon_types.h +        helpers/joycon_protocol/irs.cpp +        helpers/joycon_protocol/irs.h +        helpers/joycon_protocol/nfc.cpp +        helpers/joycon_protocol/nfc.h +        helpers/joycon_protocol/poller.cpp +        helpers/joycon_protocol/poller.h +        helpers/joycon_protocol/ringcon.cpp +        helpers/joycon_protocol/ringcon.h +        helpers/joycon_protocol/rumble.cpp +        helpers/joycon_protocol/rumble.h      )      target_link_libraries(input_common PRIVATE SDL2::SDL2)      target_compile_definitions(input_common PRIVATE HAVE_SDL2) diff --git a/src/input_common/drivers/camera.cpp b/src/input_common/drivers/camera.cpp index fad9177dc..04970f635 100644 --- a/src/input_common/drivers/camera.cpp +++ b/src/input_common/drivers/camera.cpp @@ -72,11 +72,11 @@ std::size_t Camera::getImageHeight() const {      }  } -Common::Input::CameraError Camera::SetCameraFormat( +Common::Input::DriverResult Camera::SetCameraFormat(      [[maybe_unused]] const PadIdentifier& identifier_,      const Common::Input::CameraFormat camera_format) {      status.format = camera_format; -    return Common::Input::CameraError::None; +    return Common::Input::DriverResult::Success;  }  } // namespace InputCommon diff --git a/src/input_common/drivers/camera.h b/src/input_common/drivers/camera.h index ead3e0fde..24b27e325 100644 --- a/src/input_common/drivers/camera.h +++ b/src/input_common/drivers/camera.h @@ -22,8 +22,8 @@ public:      std::size_t getImageWidth() const;      std::size_t getImageHeight() const; -    Common::Input::CameraError SetCameraFormat(const PadIdentifier& identifier_, -                                               Common::Input::CameraFormat camera_format) override; +    Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier_, +                                                Common::Input::CameraFormat camera_format) override;  private:      Common::Input::CameraStatus status{}; diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp index 826fa2109..ecb3e9dc2 100644 --- a/src/input_common/drivers/gc_adapter.cpp +++ b/src/input_common/drivers/gc_adapter.cpp @@ -324,7 +324,7 @@ bool GCAdapter::GetGCEndpoint(libusb_device* device) {      return true;  } -Common::Input::VibrationError GCAdapter::SetVibration( +Common::Input::DriverResult GCAdapter::SetVibration(      const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {      const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f;      const auto processed_amplitude = @@ -333,9 +333,9 @@ Common::Input::VibrationError GCAdapter::SetVibration(      pads[identifier.port].rumble_amplitude = processed_amplitude;      if (!rumble_enabled) { -        return Common::Input::VibrationError::Disabled; +        return Common::Input::DriverResult::Disabled;      } -    return Common::Input::VibrationError::None; +    return Common::Input::DriverResult::Success;  }  bool GCAdapter::IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) { diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h index b5270fd0b..3c2eb376d 100644 --- a/src/input_common/drivers/gc_adapter.h +++ b/src/input_common/drivers/gc_adapter.h @@ -25,7 +25,7 @@ public:      explicit GCAdapter(std::string input_engine_);      ~GCAdapter() override; -    Common::Input::VibrationError SetVibration( +    Common::Input::DriverResult SetVibration(          const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;      bool IsVibrationEnabled(const PadIdentifier& identifier) override; diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp new file mode 100644 index 000000000..7122093c6 --- /dev/null +++ b/src/input_common/drivers/joycon.cpp @@ -0,0 +1,677 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <fmt/format.h> + +#include "common/param_package.h" +#include "common/settings.h" +#include "common/thread.h" +#include "input_common/drivers/joycon.h" +#include "input_common/helpers/joycon_driver.h" +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon { + +Joycons::Joycons(const std::string& input_engine_) : InputEngine(input_engine_) { +    // Avoid conflicting with SDL driver +    if (!Settings::values.enable_joycon_driver) { +        return; +    } +    LOG_INFO(Input, "Joycon driver Initialization started"); +    const int init_res = SDL_hid_init(); +    if (init_res == 0) { +        Setup(); +    } else { +        LOG_ERROR(Input, "Hidapi could not be initialized. failed with error = {}", init_res); +    } +} + +Joycons::~Joycons() { +    Reset(); +} + +void Joycons::Reset() { +    scan_thread = {}; +    for (const auto& device : left_joycons) { +        if (!device) { +            continue; +        } +        device->Stop(); +    } +    for (const auto& device : right_joycons) { +        if (!device) { +            continue; +        } +        device->Stop(); +    } +    SDL_hid_exit(); +} + +void Joycons::Setup() { +    u32 port = 0; +    PreSetController(GetIdentifier(0, Joycon::ControllerType::None)); +    for (auto& device : left_joycons) { +        PreSetController(GetIdentifier(port, Joycon::ControllerType::Left)); +        device = std::make_shared<Joycon::JoyconDriver>(port++); +    } +    port = 0; +    for (auto& device : right_joycons) { +        PreSetController(GetIdentifier(port, Joycon::ControllerType::Right)); +        device = std::make_shared<Joycon::JoyconDriver>(port++); +    } + +    scan_thread = std::jthread([this](std::stop_token stop_token) { ScanThread(stop_token); }); +} + +void Joycons::ScanThread(std::stop_token stop_token) { +    constexpr u16 nintendo_vendor_id = 0x057e; +    Common::SetCurrentThreadName("JoyconScanThread"); +    while (!stop_token.stop_requested()) { +        SDL_hid_device_info* devs = SDL_hid_enumerate(nintendo_vendor_id, 0x0); +        SDL_hid_device_info* cur_dev = devs; + +        while (cur_dev) { +            if (IsDeviceNew(cur_dev)) { +                LOG_DEBUG(Input, "Device Found,type : {:04X} {:04X}", cur_dev->vendor_id, +                          cur_dev->product_id); +                RegisterNewDevice(cur_dev); +            } +            cur_dev = cur_dev->next; +        } + +        SDL_hid_free_enumeration(devs); +        std::this_thread::sleep_for(std::chrono::seconds(5)); +    } +} + +bool Joycons::IsDeviceNew(SDL_hid_device_info* device_info) const { +    Joycon::ControllerType type{}; +    Joycon::SerialNumber serial_number{}; + +    const auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type); +    if (result != Joycon::DriverResult::Success) { +        return false; +    } + +    const auto result2 = Joycon::JoyconDriver::GetSerialNumber(device_info, serial_number); +    if (result2 != Joycon::DriverResult::Success) { +        return false; +    } + +    auto is_handle_identical = [serial_number](std::shared_ptr<Joycon::JoyconDriver> device) { +        if (!device) { +            return false; +        } +        if (!device->IsConnected()) { +            return false; +        } +        if (device->GetHandleSerialNumber() != serial_number) { +            return false; +        } +        return true; +    }; + +    // Check if device already exist +    switch (type) { +    case Joycon::ControllerType::Left: +        for (const auto& device : left_joycons) { +            if (is_handle_identical(device)) { +                return false; +            } +        } +        break; +    case Joycon::ControllerType::Right: +        for (const auto& device : right_joycons) { +            if (is_handle_identical(device)) { +                return false; +            } +        } +        break; +    default: +        return false; +    } + +    return true; +} + +void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) { +    Joycon::ControllerType type{}; +    auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type); +    auto handle = GetNextFreeHandle(type); +    if (handle == nullptr) { +        LOG_WARNING(Input, "No free handles available"); +        return; +    } +    if (result == Joycon::DriverResult::Success) { +        result = handle->RequestDeviceAccess(device_info); +    } +    if (result == Joycon::DriverResult::Success) { +        LOG_WARNING(Input, "Initialize device"); + +        const std::size_t port = handle->GetDevicePort(); +        const Joycon::JoyconCallbacks callbacks{ +            .on_battery_data = {[this, port, type](Joycon::Battery value) { +                OnBatteryUpdate(port, type, value); +            }}, +            .on_color_data = {[this, port, type](Joycon::Color value) { +                OnColorUpdate(port, type, value); +            }}, +            .on_button_data = {[this, port, type](int id, bool value) { +                OnButtonUpdate(port, type, id, value); +            }}, +            .on_stick_data = {[this, port, type](int id, f32 value) { +                OnStickUpdate(port, type, id, value); +            }}, +            .on_motion_data = {[this, port, type](int id, const Joycon::MotionData& value) { +                OnMotionUpdate(port, type, id, value); +            }}, +            .on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }}, +            .on_amiibo_data = {[this, port](const std::vector<u8>& amiibo_data) { +                OnAmiiboUpdate(port, amiibo_data); +            }}, +            .on_camera_data = {[this, port](const std::vector<u8>& camera_data, +                                            Joycon::IrsResolution format) { +                OnCameraUpdate(port, camera_data, format); +            }}, +        }; + +        handle->InitializeDevice(); +        handle->SetCallbacks(callbacks); +    } +} + +std::shared_ptr<Joycon::JoyconDriver> Joycons::GetNextFreeHandle( +    Joycon::ControllerType type) const { +    if (type == Joycon::ControllerType::Left) { +        const auto unconnected_device = +            std::ranges::find_if(left_joycons, [](auto& device) { return !device->IsConnected(); }); +        if (unconnected_device != left_joycons.end()) { +            return *unconnected_device; +        } +    } +    if (type == Joycon::ControllerType::Right) { +        const auto unconnected_device = std::ranges::find_if( +            right_joycons, [](auto& device) { return !device->IsConnected(); }); + +        if (unconnected_device != right_joycons.end()) { +            return *unconnected_device; +        } +    } +    return nullptr; +} + +bool Joycons::IsVibrationEnabled(const PadIdentifier& identifier) { +    const auto handle = GetHandle(identifier); +    if (handle == nullptr) { +        return false; +    } +    return handle->IsVibrationEnabled(); +} + +Common::Input::DriverResult Joycons::SetVibration(const PadIdentifier& identifier, +                                                  const Common::Input::VibrationStatus& vibration) { +    const Joycon::VibrationValue native_vibration{ +        .low_amplitude = vibration.low_amplitude, +        .low_frequency = vibration.low_frequency, +        .high_amplitude = vibration.high_amplitude, +        .high_frequency = vibration.high_frequency, +    }; +    auto handle = GetHandle(identifier); +    if (handle == nullptr) { +        return Common::Input::DriverResult::InvalidHandle; +    } + +    handle->SetVibration(native_vibration); +    return Common::Input::DriverResult::Success; +} + +Common::Input::DriverResult Joycons::SetLeds(const PadIdentifier& identifier, +                                             const Common::Input::LedStatus& led_status) { +    auto handle = GetHandle(identifier); +    if (handle == nullptr) { +        return Common::Input::DriverResult::InvalidHandle; +    } +    int led_config = led_status.led_1 ? 1 : 0; +    led_config += led_status.led_2 ? 2 : 0; +    led_config += led_status.led_3 ? 4 : 0; +    led_config += led_status.led_4 ? 8 : 0; + +    return static_cast<Common::Input::DriverResult>( +        handle->SetLedConfig(static_cast<u8>(led_config))); +} + +Common::Input::DriverResult Joycons::SetCameraFormat(const PadIdentifier& identifier, +                                                     Common::Input::CameraFormat camera_format) { +    auto handle = GetHandle(identifier); +    if (handle == nullptr) { +        return Common::Input::DriverResult::InvalidHandle; +    } +    return static_cast<Common::Input::DriverResult>(handle->SetIrsConfig( +        Joycon::IrsMode::ImageTransfer, static_cast<Joycon::IrsResolution>(camera_format))); +}; + +Common::Input::NfcState Joycons::SupportsNfc(const PadIdentifier& identifier_) const { +    return Common::Input::NfcState::Success; +}; + +Common::Input::NfcState Joycons::WriteNfcData(const PadIdentifier& identifier_, +                                              const std::vector<u8>& data) { +    return Common::Input::NfcState::NotSupported; +}; + +Common::Input::DriverResult Joycons::SetPollingMode(const PadIdentifier& identifier, +                                                    const Common::Input::PollingMode polling_mode) { +    auto handle = GetHandle(identifier); +    if (handle == nullptr) { +        LOG_ERROR(Input, "Invalid handle {}", identifier.port); +        return Common::Input::DriverResult::InvalidHandle; +    } + +    switch (polling_mode) { +    case Common::Input::PollingMode::Active: +        return static_cast<Common::Input::DriverResult>(handle->SetActiveMode()); +    case Common::Input::PollingMode::Pasive: +        return static_cast<Common::Input::DriverResult>(handle->SetPasiveMode()); +    case Common::Input::PollingMode::IR: +        return static_cast<Common::Input::DriverResult>(handle->SetIrMode()); +    case Common::Input::PollingMode::NFC: +        return static_cast<Common::Input::DriverResult>(handle->SetNfcMode()); +    case Common::Input::PollingMode::Ring: +        return static_cast<Common::Input::DriverResult>(handle->SetRingConMode()); +    default: +        return Common::Input::DriverResult::NotSupported; +    } +} + +void Joycons::OnBatteryUpdate(std::size_t port, Joycon::ControllerType type, +                              Joycon::Battery value) { +    const auto identifier = GetIdentifier(port, type); +    if (value.charging != 0) { +        SetBattery(identifier, Common::Input::BatteryLevel::Charging); +        return; +    } + +    Common::Input::BatteryLevel battery{}; +    switch (value.status) { +    case 0: +        battery = Common::Input::BatteryLevel::Empty; +        break; +    case 1: +        battery = Common::Input::BatteryLevel::Critical; +        break; +    case 2: +        battery = Common::Input::BatteryLevel::Low; +        break; +    case 3: +        battery = Common::Input::BatteryLevel::Medium; +        break; +    case 4: +    default: +        battery = Common::Input::BatteryLevel::Full; +        break; +    } +    SetBattery(identifier, battery); +} + +void Joycons::OnColorUpdate(std::size_t port, Joycon::ControllerType type, +                            const Joycon::Color& value) { +    const auto identifier = GetIdentifier(port, type); +    Common::Input::BodyColorStatus color{ +        .body = value.body, +        .buttons = value.buttons, +        .left_grip = value.left_grip, +        .right_grip = value.right_grip, +    }; +    SetColor(identifier, color); +} + +void Joycons::OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value) { +    const auto identifier = GetIdentifier(port, type); +    SetButton(identifier, id, value); +} + +void Joycons::OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value) { +    const auto identifier = GetIdentifier(port, type); +    SetAxis(identifier, id, value); +} + +void Joycons::OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id, +                             const Joycon::MotionData& value) { +    const auto identifier = GetIdentifier(port, type); +    BasicMotion motion_data{ +        .gyro_x = value.gyro_x, +        .gyro_y = value.gyro_y, +        .gyro_z = value.gyro_z, +        .accel_x = value.accel_x, +        .accel_y = value.accel_y, +        .accel_z = value.accel_z, +        .delta_timestamp = 15000, +    }; +    SetMotion(identifier, id, motion_data); +} + +void Joycons::OnRingConUpdate(f32 ring_data) { +    // To simplify ring detection it will always be mapped to an empty identifier for all +    // controllers +    constexpr PadIdentifier identifier = { +        .guid = Common::UUID{}, +        .port = 0, +        .pad = 0, +    }; +    SetAxis(identifier, 100, ring_data); +} + +void Joycons::OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data) { +    const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right); +    const auto nfc_state = amiibo_data.empty() ? Common::Input::NfcState::AmiiboRemoved +                                               : Common::Input::NfcState::NewAmiibo; +    SetNfc(identifier, {nfc_state, amiibo_data}); +} + +void Joycons::OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data, +                             Joycon::IrsResolution format) { +    const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right); +    SetCamera(identifier, {static_cast<Common::Input::CameraFormat>(format), camera_data}); +} + +std::shared_ptr<Joycon::JoyconDriver> Joycons::GetHandle(PadIdentifier identifier) const { +    auto is_handle_active = [&](std::shared_ptr<Joycon::JoyconDriver> device) { +        if (!device) { +            return false; +        } +        if (!device->IsConnected()) { +            return false; +        } +        if (device->GetDevicePort() == identifier.port) { +            return true; +        } +        return false; +    }; +    const auto type = static_cast<Joycon::ControllerType>(identifier.pad); + +    if (type == Joycon::ControllerType::Left) { +        const auto matching_device = std::ranges::find_if( +            left_joycons, [is_handle_active](auto& device) { return is_handle_active(device); }); + +        if (matching_device != left_joycons.end()) { +            return *matching_device; +        } +    } + +    if (type == Joycon::ControllerType::Right) { +        const auto matching_device = std::ranges::find_if( +            right_joycons, [is_handle_active](auto& device) { return is_handle_active(device); }); + +        if (matching_device != right_joycons.end()) { +            return *matching_device; +        } +    } + +    return nullptr; +} + +PadIdentifier Joycons::GetIdentifier(std::size_t port, Joycon::ControllerType type) const { +    const std::array<u8, 16> guid{0, 0, 0, 0, 0, 0, 0, 0, +                                  0, 0, 0, 0, 0, 0, 0, static_cast<u8>(type)}; +    return { +        .guid = Common::UUID{guid}, +        .port = port, +        .pad = static_cast<std::size_t>(type), +    }; +} + +Common::ParamPackage Joycons::GetParamPackage(std::size_t port, Joycon::ControllerType type) const { +    const auto identifier = GetIdentifier(port, type); +    return { +        {"engine", GetEngineName()}, +        {"guid", identifier.guid.RawString()}, +        {"port", std::to_string(identifier.port)}, +        {"pad", std::to_string(identifier.pad)}, +    }; +} + +std::vector<Common::ParamPackage> Joycons::GetInputDevices() const { +    std::vector<Common::ParamPackage> devices{}; + +    auto add_entry = [&](std::shared_ptr<Joycon::JoyconDriver> device) { +        if (!device) { +            return; +        } +        if (!device->IsConnected()) { +            return; +        } +        auto param = GetParamPackage(device->GetDevicePort(), device->GetHandleDeviceType()); +        std::string name = fmt::format("{} {}", JoyconName(device->GetHandleDeviceType()), +                                       device->GetDevicePort() + 1); +        param.Set("display", std::move(name)); +        devices.emplace_back(param); +    }; + +    for (const auto& controller : left_joycons) { +        add_entry(controller); +    } +    for (const auto& controller : right_joycons) { +        add_entry(controller); +    } + +    // List dual joycon pairs +    for (std::size_t i = 0; i < MaxSupportedControllers; i++) { +        if (!left_joycons[i] || !right_joycons[i]) { +            continue; +        } +        if (!left_joycons[i]->IsConnected() || !right_joycons[i]->IsConnected()) { +            continue; +        } +        auto main_param = GetParamPackage(i, left_joycons[i]->GetHandleDeviceType()); +        const auto second_param = GetParamPackage(i, right_joycons[i]->GetHandleDeviceType()); +        const auto type = Joycon::ControllerType::Dual; +        std::string name = fmt::format("{} {}", JoyconName(type), i + 1); + +        main_param.Set("display", std::move(name)); +        main_param.Set("guid2", second_param.Get("guid", "")); +        main_param.Set("pad", std::to_string(static_cast<size_t>(type))); +        devices.emplace_back(main_param); +    } + +    return devices; +} + +ButtonMapping Joycons::GetButtonMappingForDevice(const Common::ParamPackage& params) { +    static constexpr std::array<std::tuple<Settings::NativeButton::Values, Joycon::PadButton, bool>, +                                18> +        switch_to_joycon_button = { +            std::tuple{Settings::NativeButton::A, Joycon::PadButton::A, true}, +            {Settings::NativeButton::B, Joycon::PadButton::B, true}, +            {Settings::NativeButton::X, Joycon::PadButton::X, true}, +            {Settings::NativeButton::Y, Joycon::PadButton::Y, true}, +            {Settings::NativeButton::DLeft, Joycon::PadButton::Left, false}, +            {Settings::NativeButton::DUp, Joycon::PadButton::Up, false}, +            {Settings::NativeButton::DRight, Joycon::PadButton::Right, false}, +            {Settings::NativeButton::DDown, Joycon::PadButton::Down, false}, +            {Settings::NativeButton::L, Joycon::PadButton::L, false}, +            {Settings::NativeButton::R, Joycon::PadButton::R, true}, +            {Settings::NativeButton::ZL, Joycon::PadButton::ZL, false}, +            {Settings::NativeButton::ZR, Joycon::PadButton::ZR, true}, +            {Settings::NativeButton::Plus, Joycon::PadButton::Plus, true}, +            {Settings::NativeButton::Minus, Joycon::PadButton::Minus, false}, +            {Settings::NativeButton::Home, Joycon::PadButton::Home, true}, +            {Settings::NativeButton::Screenshot, Joycon::PadButton::Capture, false}, +            {Settings::NativeButton::LStick, Joycon::PadButton::StickL, false}, +            {Settings::NativeButton::RStick, Joycon::PadButton::StickR, true}, +        }; + +    if (!params.Has("port")) { +        return {}; +    } + +    ButtonMapping mapping{}; +    for (const auto& [switch_button, joycon_button, side] : switch_to_joycon_button) { +        const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); +        auto pad = static_cast<Joycon::ControllerType>(params.Get("pad", 0)); +        if (pad == Joycon::ControllerType::Dual) { +            pad = side ? Joycon::ControllerType::Right : Joycon::ControllerType::Left; +        } + +        Common::ParamPackage button_params = GetParamPackage(port, pad); +        button_params.Set("button", static_cast<int>(joycon_button)); +        mapping.insert_or_assign(switch_button, std::move(button_params)); +    } + +    // Map SL and SR buttons for left joycons +    if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Left)) { +        const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); +        Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Left); + +        Common::ParamPackage sl_button_params = button_params; +        Common::ParamPackage sr_button_params = button_params; +        sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSL)); +        sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSR)); +        mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params)); +        mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params)); +    } + +    // Map SL and SR buttons for right joycons +    if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Right)) { +        const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); +        Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Right); + +        Common::ParamPackage sl_button_params = button_params; +        Common::ParamPackage sr_button_params = button_params; +        sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSL)); +        sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSR)); +        mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params)); +        mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params)); +    } + +    return mapping; +} + +AnalogMapping Joycons::GetAnalogMappingForDevice(const Common::ParamPackage& params) { +    if (!params.Has("port")) { +        return {}; +    } + +    const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); +    auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0)); +    auto pad_right = pad_left; +    if (pad_left == Joycon::ControllerType::Dual) { +        pad_left = Joycon::ControllerType::Left; +        pad_right = Joycon::ControllerType::Right; +    } + +    AnalogMapping mapping = {}; +    Common::ParamPackage left_analog_params = GetParamPackage(port, pad_left); +    left_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::LeftStickX)); +    left_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::LeftStickY)); +    mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params)); +    Common::ParamPackage right_analog_params = GetParamPackage(port, pad_right); +    right_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::RightStickX)); +    right_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::RightStickY)); +    mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params)); +    return mapping; +} + +MotionMapping Joycons::GetMotionMappingForDevice(const Common::ParamPackage& params) { +    if (!params.Has("port")) { +        return {}; +    } + +    const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); +    auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0)); +    auto pad_right = pad_left; +    if (pad_left == Joycon::ControllerType::Dual) { +        pad_left = Joycon::ControllerType::Left; +        pad_right = Joycon::ControllerType::Right; +    } + +    MotionMapping mapping = {}; +    Common::ParamPackage left_motion_params = GetParamPackage(port, pad_left); +    left_motion_params.Set("motion", 0); +    mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(left_motion_params)); +    Common::ParamPackage right_Motion_params = GetParamPackage(port, pad_right); +    right_Motion_params.Set("motion", 1); +    mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(right_Motion_params)); +    return mapping; +} + +Common::Input::ButtonNames Joycons::GetUIButtonName(const Common::ParamPackage& params) const { +    const auto button = static_cast<Joycon::PadButton>(params.Get("button", 0)); +    switch (button) { +    case Joycon::PadButton::Left: +        return Common::Input::ButtonNames::ButtonLeft; +    case Joycon::PadButton::Right: +        return Common::Input::ButtonNames::ButtonRight; +    case Joycon::PadButton::Down: +        return Common::Input::ButtonNames::ButtonDown; +    case Joycon::PadButton::Up: +        return Common::Input::ButtonNames::ButtonUp; +    case Joycon::PadButton::LeftSL: +    case Joycon::PadButton::RightSL: +        return Common::Input::ButtonNames::TriggerSL; +    case Joycon::PadButton::LeftSR: +    case Joycon::PadButton::RightSR: +        return Common::Input::ButtonNames::TriggerSR; +    case Joycon::PadButton::L: +        return Common::Input::ButtonNames::TriggerL; +    case Joycon::PadButton::R: +        return Common::Input::ButtonNames::TriggerR; +    case Joycon::PadButton::ZL: +        return Common::Input::ButtonNames::TriggerZL; +    case Joycon::PadButton::ZR: +        return Common::Input::ButtonNames::TriggerZR; +    case Joycon::PadButton::A: +        return Common::Input::ButtonNames::ButtonA; +    case Joycon::PadButton::B: +        return Common::Input::ButtonNames::ButtonB; +    case Joycon::PadButton::X: +        return Common::Input::ButtonNames::ButtonX; +    case Joycon::PadButton::Y: +        return Common::Input::ButtonNames::ButtonY; +    case Joycon::PadButton::Plus: +        return Common::Input::ButtonNames::ButtonPlus; +    case Joycon::PadButton::Minus: +        return Common::Input::ButtonNames::ButtonMinus; +    case Joycon::PadButton::Home: +        return Common::Input::ButtonNames::ButtonHome; +    case Joycon::PadButton::Capture: +        return Common::Input::ButtonNames::ButtonCapture; +    case Joycon::PadButton::StickL: +        return Common::Input::ButtonNames::ButtonStickL; +    case Joycon::PadButton::StickR: +        return Common::Input::ButtonNames::ButtonStickR; +    default: +        return Common::Input::ButtonNames::Undefined; +    } +} + +Common::Input::ButtonNames Joycons::GetUIName(const Common::ParamPackage& params) const { +    if (params.Has("button")) { +        return GetUIButtonName(params); +    } +    if (params.Has("axis")) { +        return Common::Input::ButtonNames::Value; +    } +    if (params.Has("motion")) { +        return Common::Input::ButtonNames::Engine; +    } + +    return Common::Input::ButtonNames::Invalid; +} + +std::string Joycons::JoyconName(Joycon::ControllerType type) const { +    switch (type) { +    case Joycon::ControllerType::Left: +        return "Left Joycon"; +    case Joycon::ControllerType::Right: +        return "Right Joycon"; +    case Joycon::ControllerType::Pro: +        return "Pro Controller"; +    case Joycon::ControllerType::Grip: +        return "Grip Controller"; +    case Joycon::ControllerType::Dual: +        return "Dual Joycon"; +    default: +        return "Unknown Joycon"; +    } +} +} // namespace InputCommon diff --git a/src/input_common/drivers/joycon.h b/src/input_common/drivers/joycon.h new file mode 100644 index 000000000..316d383d8 --- /dev/null +++ b/src/input_common/drivers/joycon.h @@ -0,0 +1,111 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <span> +#include <thread> +#include <SDL_hidapi.h> + +#include "input_common/input_engine.h" + +namespace InputCommon::Joycon { +using SerialNumber = std::array<u8, 15>; +struct Battery; +struct Color; +struct MotionData; +enum class ControllerType; +enum class DriverResult; +enum class IrsResolution; +class JoyconDriver; +} // namespace InputCommon::Joycon + +namespace InputCommon { + +class Joycons final : public InputCommon::InputEngine { +public: +    explicit Joycons(const std::string& input_engine_); + +    ~Joycons(); + +    bool IsVibrationEnabled(const PadIdentifier& identifier) override; +    Common::Input::DriverResult SetVibration( +        const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; + +    Common::Input::DriverResult SetLeds(const PadIdentifier& identifier, +                                        const Common::Input::LedStatus& led_status) override; + +    Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier, +                                                Common::Input::CameraFormat camera_format) override; + +    Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override; +    Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_, +                                         const std::vector<u8>& data) override; + +    Common::Input::DriverResult SetPollingMode( +        const PadIdentifier& identifier, const Common::Input::PollingMode polling_mode) override; + +    /// Used for automapping features +    std::vector<Common::ParamPackage> GetInputDevices() const override; +    ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; +    AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; +    MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override; +    Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; + +private: +    static constexpr std::size_t MaxSupportedControllers = 8; + +    /// For shutting down, clear all data, join all threads, release usb devices +    void Reset(); + +    /// Registers controllers, clears all data and starts the scan thread +    void Setup(); + +    /// Actively searchs for new devices +    void ScanThread(std::stop_token stop_token); + +    /// Returns true if device is valid and not registered +    bool IsDeviceNew(SDL_hid_device_info* device_info) const; + +    /// Tries to connect to the new device +    void RegisterNewDevice(SDL_hid_device_info* device_info); + +    /// Returns the next free handle +    std::shared_ptr<Joycon::JoyconDriver> GetNextFreeHandle(Joycon::ControllerType type) const; + +    void OnBatteryUpdate(std::size_t port, Joycon::ControllerType type, Joycon::Battery value); +    void OnColorUpdate(std::size_t port, Joycon::ControllerType type, const Joycon::Color& value); +    void OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value); +    void OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value); +    void OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id, +                        const Joycon::MotionData& value); +    void OnRingConUpdate(f32 ring_data); +    void OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data); +    void OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data, +                        Joycon::IrsResolution format); + +    /// Returns a JoyconHandle corresponding to a PadIdentifier +    std::shared_ptr<Joycon::JoyconDriver> GetHandle(PadIdentifier identifier) const; + +    /// Returns a PadIdentifier corresponding to the port number and joycon type +    PadIdentifier GetIdentifier(std::size_t port, Joycon::ControllerType type) const; + +    /// Returns a ParamPackage corresponding to the port number and joycon type +    Common::ParamPackage GetParamPackage(std::size_t port, Joycon::ControllerType type) const; + +    std::string JoyconName(std::size_t port) const; + +    Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const; + +    /// Returns the name of the device in text format +    std::string JoyconName(Joycon::ControllerType type) const; + +    std::jthread scan_thread; + +    // Joycon types are split by type to ease supporting dualjoycon configurations +    std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> left_joycons{}; +    std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> right_joycons{}; +}; + +} // namespace InputCommon diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index 9835d99d2..d975eb815 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -334,6 +334,15 @@ void SDLDriver::InitJoystick(int joystick_index) {      const auto guid = GetGUID(sdl_joystick); +    if (Settings::values.enable_joycon_driver) { +        if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e && +            (guid.uuid[8] == 0x06 || guid.uuid[8] == 0x07)) { +            LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_index); +            SDL_JoystickClose(sdl_joystick); +            return; +        } +    } +      std::scoped_lock lock{joystick_map_mutex};      if (joystick_map.find(guid) == joystick_map.end()) {          auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller); @@ -456,9 +465,13 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en      SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");      SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); -    // Use hidapi driver for joycons. This will allow joycons to be detected as a GameController and -    // not a generic one -    SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); +    // Disable hidapi drivers for switch controllers when the custom joycon driver is enabled +    if (Settings::values.enable_joycon_driver) { +        SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "0"); +    } else { +        SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); +    } +    SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1");      // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native      // driver on Linux. @@ -548,7 +561,7 @@ std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const {      return devices;  } -Common::Input::VibrationError SDLDriver::SetVibration( +Common::Input::DriverResult SDLDriver::SetVibration(      const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {      const auto joystick =          GetSDLJoystickByGUID(identifier.guid.RawString(), static_cast<int>(identifier.port)); @@ -582,7 +595,7 @@ Common::Input::VibrationError SDLDriver::SetVibration(          .vibration = new_vibration,      }); -    return Common::Input::VibrationError::None; +    return Common::Input::DriverResult::Success;  }  bool SDLDriver::IsVibrationEnabled(const PadIdentifier& identifier) { diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h index 366bcc496..ffde169b3 100644 --- a/src/input_common/drivers/sdl_driver.h +++ b/src/input_common/drivers/sdl_driver.h @@ -63,7 +63,7 @@ public:      bool IsStickInverted(const Common::ParamPackage& params) override; -    Common::Input::VibrationError SetVibration( +    Common::Input::DriverResult SetVibration(          const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;      bool IsVibrationEnabled(const PadIdentifier& identifier) override; diff --git a/src/input_common/drivers/virtual_amiibo.cpp b/src/input_common/drivers/virtual_amiibo.cpp index 63ffaca67..4a0268a4d 100644 --- a/src/input_common/drivers/virtual_amiibo.cpp +++ b/src/input_common/drivers/virtual_amiibo.cpp @@ -22,22 +22,23 @@ VirtualAmiibo::VirtualAmiibo(std::string input_engine_) : InputEngine(std::move(  VirtualAmiibo::~VirtualAmiibo() = default; -Common::Input::PollingError VirtualAmiibo::SetPollingMode( +Common::Input::DriverResult VirtualAmiibo::SetPollingMode(      [[maybe_unused]] const PadIdentifier& identifier_,      const Common::Input::PollingMode polling_mode_) {      polling_mode = polling_mode_; -    if (polling_mode == Common::Input::PollingMode::NFC) { +    switch (polling_mode) { +    case Common::Input::PollingMode::NFC:          if (state == State::Initialized) {              state = State::WaitingForAmiibo;          } -    } else { +        return Common::Input::DriverResult::Success; +    default:          if (state == State::AmiiboIsOpen) {              CloseAmiibo();          } +        return Common::Input::DriverResult::NotSupported;      } - -    return Common::Input::PollingError::None;  }  Common::Input::NfcState VirtualAmiibo::SupportsNfc( diff --git a/src/input_common/drivers/virtual_amiibo.h b/src/input_common/drivers/virtual_amiibo.h index 0f9dad333..13cacfc0a 100644 --- a/src/input_common/drivers/virtual_amiibo.h +++ b/src/input_common/drivers/virtual_amiibo.h @@ -36,7 +36,7 @@ public:      ~VirtualAmiibo() override;      // Sets polling mode to a controller -    Common::Input::PollingError SetPollingMode( +    Common::Input::DriverResult SetPollingMode(          const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) override;      Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override; diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp new file mode 100644 index 000000000..4159e5717 --- /dev/null +++ b/src/input_common/helpers/joycon_driver.cpp @@ -0,0 +1,572 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "common/swap.h" +#include "common/thread.h" +#include "input_common/helpers/joycon_driver.h" +#include "input_common/helpers/joycon_protocol/calibration.h" +#include "input_common/helpers/joycon_protocol/generic_functions.h" +#include "input_common/helpers/joycon_protocol/irs.h" +#include "input_common/helpers/joycon_protocol/nfc.h" +#include "input_common/helpers/joycon_protocol/poller.h" +#include "input_common/helpers/joycon_protocol/ringcon.h" +#include "input_common/helpers/joycon_protocol/rumble.h" + +namespace InputCommon::Joycon { +JoyconDriver::JoyconDriver(std::size_t port_) : port{port_} { +    hidapi_handle = std::make_shared<JoyconHandle>(); +} + +JoyconDriver::~JoyconDriver() { +    Stop(); +} + +void JoyconDriver::Stop() { +    is_connected = false; +    input_thread = {}; +} + +DriverResult JoyconDriver::RequestDeviceAccess(SDL_hid_device_info* device_info) { +    std::scoped_lock lock{mutex}; + +    handle_device_type = ControllerType::None; +    GetDeviceType(device_info, handle_device_type); +    if (handle_device_type == ControllerType::None) { +        return DriverResult::UnsupportedControllerType; +    } + +    hidapi_handle->handle = +        SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number); +    std::memcpy(&handle_serial_number, device_info->serial_number, 15); +    if (!hidapi_handle->handle) { +        LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.", +                  device_info->vendor_id, device_info->product_id); +        return DriverResult::HandleInUse; +    } +    SDL_hid_set_nonblocking(hidapi_handle->handle, 1); +    return DriverResult::Success; +} + +DriverResult JoyconDriver::InitializeDevice() { +    if (!hidapi_handle->handle) { +        return DriverResult::InvalidHandle; +    } +    std::scoped_lock lock{mutex}; +    disable_input_thread = true; + +    // Reset Counters +    error_counter = 0; +    hidapi_handle->packet_counter = 0; + +    // Reset external device status +    starlink_connected = false; +    ring_connected = false; +    amiibo_detected = false; + +    // Set HW default configuration +    vibration_enabled = true; +    motion_enabled = true; +    hidbus_enabled = false; +    nfc_enabled = false; +    passive_enabled = false; +    irs_enabled = false; +    gyro_sensitivity = Joycon::GyroSensitivity::DPS2000; +    gyro_performance = Joycon::GyroPerformance::HZ833; +    accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8; +    accelerometer_performance = Joycon::AccelerometerPerformance::HZ100; + +    // Initialize HW Protocols +    calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle); +    generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle); +    irs_protocol = std::make_unique<IrsProtocol>(hidapi_handle); +    nfc_protocol = std::make_unique<NfcProtocol>(hidapi_handle); +    ring_protocol = std::make_unique<RingConProtocol>(hidapi_handle); +    rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle); + +    // Get fixed joycon info +    generic_protocol->GetVersionNumber(version); +    generic_protocol->GetColor(color); +    if (handle_device_type == ControllerType::Pro) { +        // Some 3rd party controllers aren't pro controllers +        generic_protocol->GetControllerType(device_type); +    } else { +        device_type = handle_device_type; +    } +    generic_protocol->GetSerialNumber(serial_number); +    supported_features = GetSupportedFeatures(); + +    // Get Calibration data +    calibration_protocol->GetLeftJoyStickCalibration(left_stick_calibration); +    calibration_protocol->GetRightJoyStickCalibration(right_stick_calibration); +    calibration_protocol->GetImuCalibration(motion_calibration); + +    // Set led status +    generic_protocol->SetLedBlinkPattern(static_cast<u8>(1 + port)); + +    // Apply HW configuration +    SetPollingMode(); + +    // Initialize joycon poller +    joycon_poller = std::make_unique<JoyconPoller>(device_type, left_stick_calibration, +                                                   right_stick_calibration, motion_calibration); + +    // Start pooling for data +    is_connected = true; +    if (!input_thread_running) { +        input_thread = +            std::jthread([this](std::stop_token stop_token) { InputThread(stop_token); }); +    } + +    disable_input_thread = false; +    return DriverResult::Success; +} + +void JoyconDriver::InputThread(std::stop_token stop_token) { +    LOG_INFO(Input, "Joycon Adapter input thread started"); +    Common::SetCurrentThreadName("JoyconInput"); +    input_thread_running = true; + +    // Max update rate is 5ms, ensure we are always able to read a bit faster +    constexpr int ThreadDelay = 2; +    std::vector<u8> buffer(MaxBufferSize); + +    while (!stop_token.stop_requested()) { +        int status = 0; + +        if (!IsInputThreadValid()) { +            input_thread.request_stop(); +            continue; +        } + +        // By disabling the input thread we can ensure custom commands will succeed as no package is +        // skipped +        if (!disable_input_thread) { +            status = SDL_hid_read_timeout(hidapi_handle->handle, buffer.data(), buffer.size(), +                                          ThreadDelay); +        } else { +            std::this_thread::sleep_for(std::chrono::milliseconds(ThreadDelay)); +        } + +        if (IsPayloadCorrect(status, buffer)) { +            OnNewData(buffer); +        } + +        std::this_thread::yield(); +    } + +    is_connected = false; +    input_thread_running = false; +    LOG_INFO(Input, "Joycon Adapter input thread stopped"); +} + +void JoyconDriver::OnNewData(std::span<u8> buffer) { +    const auto report_mode = static_cast<InputReport>(buffer[0]); + +    // Packages can be a litte bit inconsistent. Average the delta time to provide a smoother motion +    // experience +    switch (report_mode) { +    case InputReport::STANDARD_FULL_60HZ: +    case InputReport::NFC_IR_MODE_60HZ: +    case InputReport::SIMPLE_HID_MODE: { +        const auto now = std::chrono::steady_clock::now(); +        const auto new_delta_time = static_cast<u64>( +            std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count()); +        delta_time = ((delta_time * 8) + (new_delta_time * 2)) / 10; +        last_update = now; +        joycon_poller->UpdateColor(color); +        break; +    } +    default: +        break; +    } + +    const MotionStatus motion_status{ +        .is_enabled = motion_enabled, +        .delta_time = delta_time, +        .gyro_sensitivity = gyro_sensitivity, +        .accelerometer_sensitivity = accelerometer_sensitivity, +    }; + +    // TODO: Remove this when calibration is properly loaded and not calculated +    if (ring_connected && report_mode == InputReport::STANDARD_FULL_60HZ) { +        InputReportActive data{}; +        memcpy(&data, buffer.data(), sizeof(InputReportActive)); +        calibration_protocol->GetRingCalibration(ring_calibration, data.ring_input); +    } + +    const RingStatus ring_status{ +        .is_enabled = ring_connected, +        .default_value = ring_calibration.default_value, +        .max_value = ring_calibration.max_value, +        .min_value = ring_calibration.min_value, +    }; + +    if (irs_protocol->IsEnabled()) { +        irs_protocol->RequestImage(buffer); +        joycon_poller->UpdateCamera(irs_protocol->GetImage(), irs_protocol->GetIrsFormat()); +    } + +    if (nfc_protocol->IsEnabled()) { +        if (amiibo_detected) { +            if (!nfc_protocol->HasAmiibo()) { +                joycon_poller->UpdateAmiibo({}); +                amiibo_detected = false; +                return; +            } +        } + +        if (!amiibo_detected) { +            std::vector<u8> data(0x21C); +            const auto result = nfc_protocol->ScanAmiibo(data); +            if (result == DriverResult::Success) { +                joycon_poller->UpdateAmiibo(data); +                amiibo_detected = true; +            } +        } +    } + +    switch (report_mode) { +    case InputReport::STANDARD_FULL_60HZ: +        joycon_poller->ReadActiveMode(buffer, motion_status, ring_status); +        break; +    case InputReport::NFC_IR_MODE_60HZ: +        joycon_poller->ReadNfcIRMode(buffer, motion_status); +        break; +    case InputReport::SIMPLE_HID_MODE: +        joycon_poller->ReadPassiveMode(buffer); +        break; +    case InputReport::SUBCMD_REPLY: +        LOG_DEBUG(Input, "Unhandled command reply"); +        break; +    default: +        LOG_ERROR(Input, "Report mode not Implemented {}", report_mode); +        break; +    } +} + +DriverResult JoyconDriver::SetPollingMode() { +    disable_input_thread = true; + +    rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration); + +    if (motion_enabled && supported_features.motion) { +        generic_protocol->EnableImu(true); +        generic_protocol->SetImuConfig(gyro_sensitivity, gyro_performance, +                                       accelerometer_sensitivity, accelerometer_performance); +    } else { +        generic_protocol->EnableImu(false); +    } + +    if (irs_protocol->IsEnabled()) { +        irs_protocol->DisableIrs(); +    } + +    if (nfc_protocol->IsEnabled()) { +        amiibo_detected = false; +        nfc_protocol->DisableNfc(); +    } + +    if (ring_protocol->IsEnabled()) { +        ring_connected = false; +        ring_protocol->DisableRingCon(); +    } + +    if (irs_enabled && supported_features.irs) { +        auto result = irs_protocol->EnableIrs(); +        if (result == DriverResult::Success) { +            disable_input_thread = false; +            return result; +        } +        irs_protocol->DisableIrs(); +        LOG_ERROR(Input, "Error enabling IRS"); +    } + +    if (nfc_enabled && supported_features.nfc) { +        auto result = nfc_protocol->EnableNfc(); +        if (result == DriverResult::Success) { +            result = nfc_protocol->StartNFCPollingMode(); +        } +        if (result == DriverResult::Success) { +            disable_input_thread = false; +            return result; +        } +        nfc_protocol->DisableNfc(); +        LOG_ERROR(Input, "Error enabling NFC"); +    } + +    if (hidbus_enabled && supported_features.hidbus) { +        auto result = ring_protocol->EnableRingCon(); +        if (result == DriverResult::Success) { +            result = ring_protocol->StartRingconPolling(); +        } +        if (result == DriverResult::Success) { +            ring_connected = true; +            disable_input_thread = false; +            return result; +        } +        ring_connected = false; +        ring_protocol->DisableRingCon(); +        LOG_ERROR(Input, "Error enabling Ringcon"); +    } + +    if (passive_enabled && supported_features.passive) { +        const auto result = generic_protocol->EnablePassiveMode(); +        if (result == DriverResult::Success) { +            disable_input_thread = false; +            return result; +        } +        LOG_ERROR(Input, "Error enabling passive mode"); +    } + +    // Default Mode +    const auto result = generic_protocol->EnableActiveMode(); +    if (result != DriverResult::Success) { +        LOG_ERROR(Input, "Error enabling active mode"); +    } + +    disable_input_thread = false; +    return result; +} + +JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() { +    SupportedFeatures features{ +        .passive = true, +        .motion = true, +        .vibration = true, +    }; + +    if (device_type == ControllerType::Right) { +        features.nfc = true; +        features.irs = true; +        features.hidbus = true; +    } + +    if (device_type == ControllerType::Pro) { +        features.nfc = true; +    } +    return features; +} + +bool JoyconDriver::IsInputThreadValid() const { +    if (!is_connected.load()) { +        return false; +    } +    if (hidapi_handle->handle == nullptr) { +        return false; +    } +    // Controller is not responding. Terminate connection +    if (error_counter > MaxErrorCount) { +        return false; +    } +    return true; +} + +bool JoyconDriver::IsPayloadCorrect(int status, std::span<const u8> buffer) { +    if (status <= -1) { +        error_counter++; +        return false; +    } +    // There's no new data +    if (status == 0) { +        return false; +    } +    // No reply ever starts with zero +    if (buffer[0] == 0x00) { +        error_counter++; +        return false; +    } +    error_counter = 0; +    return true; +} + +DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) { +    std::scoped_lock lock{mutex}; +    if (disable_input_thread) { +        return DriverResult::HandleInUse; +    } +    return rumble_protocol->SendVibration(vibration); +} + +DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) { +    std::scoped_lock lock{mutex}; +    if (disable_input_thread) { +        return DriverResult::HandleInUse; +    } +    return generic_protocol->SetLedPattern(led_pattern); +} + +DriverResult JoyconDriver::SetIrsConfig(IrsMode mode_, IrsResolution format_) { +    std::scoped_lock lock{mutex}; +    if (disable_input_thread) { +        return DriverResult::HandleInUse; +    } +    disable_input_thread = true; +    const auto result = irs_protocol->SetIrsConfig(mode_, format_); +    disable_input_thread = false; +    return result; +} + +DriverResult JoyconDriver::SetPasiveMode() { +    std::scoped_lock lock{mutex}; +    motion_enabled = false; +    hidbus_enabled = false; +    nfc_enabled = false; +    passive_enabled = true; +    irs_enabled = false; +    return SetPollingMode(); +} + +DriverResult JoyconDriver::SetActiveMode() { +    if (is_ring_disabled_by_irs) { +        is_ring_disabled_by_irs = false; +        SetActiveMode(); +        return SetRingConMode(); +    } + +    std::scoped_lock lock{mutex}; +    motion_enabled = true; +    hidbus_enabled = false; +    nfc_enabled = false; +    passive_enabled = false; +    irs_enabled = false; +    return SetPollingMode(); +} + +DriverResult JoyconDriver::SetIrMode() { +    std::scoped_lock lock{mutex}; + +    if (!supported_features.irs) { +        return DriverResult::NotSupported; +    } + +    if (ring_connected) { +        is_ring_disabled_by_irs = true; +    } + +    motion_enabled = false; +    hidbus_enabled = false; +    nfc_enabled = false; +    passive_enabled = false; +    irs_enabled = true; +    return SetPollingMode(); +} + +DriverResult JoyconDriver::SetNfcMode() { +    std::scoped_lock lock{mutex}; + +    if (!supported_features.nfc) { +        return DriverResult::NotSupported; +    } + +    motion_enabled = true; +    hidbus_enabled = false; +    nfc_enabled = true; +    passive_enabled = false; +    irs_enabled = false; +    return SetPollingMode(); +} + +DriverResult JoyconDriver::SetRingConMode() { +    std::scoped_lock lock{mutex}; + +    if (!supported_features.hidbus) { +        return DriverResult::NotSupported; +    } + +    motion_enabled = true; +    hidbus_enabled = true; +    nfc_enabled = false; +    passive_enabled = false; +    irs_enabled = false; + +    const auto result = SetPollingMode(); + +    if (!ring_connected) { +        return DriverResult::NoDeviceDetected; +    } + +    return result; +} + +bool JoyconDriver::IsConnected() const { +    std::scoped_lock lock{mutex}; +    return is_connected.load(); +} + +bool JoyconDriver::IsVibrationEnabled() const { +    std::scoped_lock lock{mutex}; +    return vibration_enabled; +} + +FirmwareVersion JoyconDriver::GetDeviceVersion() const { +    std::scoped_lock lock{mutex}; +    return version; +} + +Color JoyconDriver::GetDeviceColor() const { +    std::scoped_lock lock{mutex}; +    return color; +} + +std::size_t JoyconDriver::GetDevicePort() const { +    std::scoped_lock lock{mutex}; +    return port; +} + +ControllerType JoyconDriver::GetDeviceType() const { +    std::scoped_lock lock{mutex}; +    return device_type; +} + +ControllerType JoyconDriver::GetHandleDeviceType() const { +    std::scoped_lock lock{mutex}; +    return handle_device_type; +} + +SerialNumber JoyconDriver::GetSerialNumber() const { +    std::scoped_lock lock{mutex}; +    return serial_number; +} + +SerialNumber JoyconDriver::GetHandleSerialNumber() const { +    std::scoped_lock lock{mutex}; +    return handle_serial_number; +} + +void JoyconDriver::SetCallbacks(const JoyconCallbacks& callbacks) { +    joycon_poller->SetCallbacks(callbacks); +} + +DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info, +                                         ControllerType& controller_type) { +    static constexpr std::array<std::pair<u32, ControllerType>, 2> supported_devices{ +        std::pair<u32, ControllerType>{0x2006, ControllerType::Left}, +        {0x2007, ControllerType::Right}, +    }; +    constexpr u16 nintendo_vendor_id = 0x057e; + +    controller_type = ControllerType::None; +    if (device_info->vendor_id != nintendo_vendor_id) { +        return DriverResult::UnsupportedControllerType; +    } + +    for (const auto& [product_id, type] : supported_devices) { +        if (device_info->product_id == static_cast<u16>(product_id)) { +            controller_type = type; +            return Joycon::DriverResult::Success; +        } +    } +    return Joycon::DriverResult::UnsupportedControllerType; +} + +DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info, +                                           SerialNumber& serial_number) { +    if (device_info->serial_number == nullptr) { +        return DriverResult::Unknown; +    } +    std::memcpy(&serial_number, device_info->serial_number, 15); +    return Joycon::DriverResult::Success; +} + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h new file mode 100644 index 000000000..c1e189fa5 --- /dev/null +++ b/src/input_common/helpers/joycon_driver.h @@ -0,0 +1,150 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <atomic> +#include <functional> +#include <mutex> +#include <span> +#include <thread> + +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon::Joycon { +class CalibrationProtocol; +class GenericProtocol; +class IrsProtocol; +class NfcProtocol; +class JoyconPoller; +class RingConProtocol; +class RumbleProtocol; + +class JoyconDriver final { +public: +    explicit JoyconDriver(std::size_t port_); + +    ~JoyconDriver(); + +    DriverResult RequestDeviceAccess(SDL_hid_device_info* device_info); +    DriverResult InitializeDevice(); +    void Stop(); + +    bool IsConnected() const; +    bool IsVibrationEnabled() const; + +    FirmwareVersion GetDeviceVersion() const; +    Color GetDeviceColor() const; +    std::size_t GetDevicePort() const; +    ControllerType GetDeviceType() const; +    ControllerType GetHandleDeviceType() const; +    SerialNumber GetSerialNumber() const; +    SerialNumber GetHandleSerialNumber() const; + +    DriverResult SetVibration(const VibrationValue& vibration); +    DriverResult SetLedConfig(u8 led_pattern); +    DriverResult SetIrsConfig(IrsMode mode_, IrsResolution format_); +    DriverResult SetPasiveMode(); +    DriverResult SetActiveMode(); +    DriverResult SetIrMode(); +    DriverResult SetNfcMode(); +    DriverResult SetRingConMode(); + +    void SetCallbacks(const JoyconCallbacks& callbacks); + +    // Returns device type from hidapi handle +    static DriverResult GetDeviceType(SDL_hid_device_info* device_info, +                                      ControllerType& controller_type); + +    // Returns serial number from hidapi handle +    static DriverResult GetSerialNumber(SDL_hid_device_info* device_info, +                                        SerialNumber& serial_number); + +private: +    struct SupportedFeatures { +        bool passive{}; +        bool hidbus{}; +        bool irs{}; +        bool motion{}; +        bool nfc{}; +        bool vibration{}; +    }; + +    /// Main thread, actively request new data from the handle +    void InputThread(std::stop_token stop_token); + +    /// Called everytime a valid package arrives +    void OnNewData(std::span<u8> buffer); + +    /// Updates device configuration to enable or disable features +    DriverResult SetPollingMode(); + +    /// Returns true if input thread is valid and doesn't need to be stopped +    bool IsInputThreadValid() const; + +    /// Returns true if the data should be interpreted. Otherwise the error counter is incremented +    bool IsPayloadCorrect(int status, std::span<const u8> buffer); + +    /// Returns a list of supported features that can be enabled on this device +    SupportedFeatures GetSupportedFeatures(); + +    // Protocol Features +    std::unique_ptr<CalibrationProtocol> calibration_protocol; +    std::unique_ptr<GenericProtocol> generic_protocol; +    std::unique_ptr<IrsProtocol> irs_protocol; +    std::unique_ptr<NfcProtocol> nfc_protocol; +    std::unique_ptr<JoyconPoller> joycon_poller; +    std::unique_ptr<RingConProtocol> ring_protocol; +    std::unique_ptr<RumbleProtocol> rumble_protocol; + +    // Connection status +    std::atomic<bool> is_connected{}; +    u64 delta_time; +    std::size_t error_counter{}; +    std::shared_ptr<JoyconHandle> hidapi_handle; +    std::chrono::time_point<std::chrono::steady_clock> last_update; + +    // External device status +    bool starlink_connected{}; +    bool ring_connected{}; +    bool amiibo_detected{}; +    bool is_ring_disabled_by_irs{}; + +    // Harware configuration +    u8 leds{}; +    ReportMode mode{}; +    bool passive_enabled{};   // Low power mode, Ideal for multiple controllers at the same time +    bool hidbus_enabled{};    // External device support +    bool irs_enabled{};       // Infrared camera input +    bool motion_enabled{};    // Enables motion input +    bool nfc_enabled{};       // Enables Amiibo detection +    bool vibration_enabled{}; // Allows vibrations + +    // Calibration data +    GyroSensitivity gyro_sensitivity{}; +    GyroPerformance gyro_performance{}; +    AccelerometerSensitivity accelerometer_sensitivity{}; +    AccelerometerPerformance accelerometer_performance{}; +    JoyStickCalibration left_stick_calibration{}; +    JoyStickCalibration right_stick_calibration{}; +    MotionCalibration motion_calibration{}; +    RingCalibration ring_calibration{}; + +    // Fixed joycon info +    FirmwareVersion version{}; +    Color color{}; +    std::size_t port{}; +    ControllerType device_type{};        // Device type reported by controller +    ControllerType handle_device_type{}; // Device type reported by hidapi +    SerialNumber serial_number{};        // Serial number reported by controller +    SerialNumber handle_serial_number{}; // Serial number type reported by hidapi +    SupportedFeatures supported_features{}; + +    // Thread related +    mutable std::mutex mutex; +    std::jthread input_thread; +    bool input_thread_running{}; +    bool disable_input_thread{}; +}; + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/calibration.cpp b/src/input_common/helpers/joycon_protocol/calibration.cpp new file mode 100644 index 000000000..f6e7e97d5 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/calibration.cpp @@ -0,0 +1,184 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <cstring> + +#include "input_common/helpers/joycon_protocol/calibration.h" +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon::Joycon { + +CalibrationProtocol::CalibrationProtocol(std::shared_ptr<JoyconHandle> handle) +    : JoyconCommonProtocol(std::move(handle)) {} + +DriverResult CalibrationProtocol::GetLeftJoyStickCalibration(JoyStickCalibration& calibration) { +    ScopedSetBlocking sb(this); +    std::vector<u8> buffer; +    DriverResult result{DriverResult::Success}; +    calibration = {}; + +    result = ReadSPI(CalAddr::USER_LEFT_MAGIC, sizeof(u16), buffer); + +    if (result == DriverResult::Success) { +        const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1; +        if (has_user_calibration) { +            result = ReadSPI(CalAddr::USER_LEFT_DATA, 9, buffer); +        } else { +            result = ReadSPI(CalAddr::FACT_LEFT_DATA, 9, buffer); +        } +    } + +    if (result == DriverResult::Success) { +        calibration.x.max = static_cast<u16>(((buffer[1] & 0x0F) << 8) | buffer[0]); +        calibration.y.max = static_cast<u16>((buffer[2] << 4) | (buffer[1] >> 4)); +        calibration.x.center = static_cast<u16>(((buffer[4] & 0x0F) << 8) | buffer[3]); +        calibration.y.center = static_cast<u16>((buffer[5] << 4) | (buffer[4] >> 4)); +        calibration.x.min = static_cast<u16>(((buffer[7] & 0x0F) << 8) | buffer[6]); +        calibration.y.min = static_cast<u16>((buffer[8] << 4) | (buffer[7] >> 4)); +    } + +    // Nintendo fix for drifting stick +    // result = ReadSPI(0x60, 0x86 ,buffer, 16); +    // calibration.deadzone = (u16)((buffer[4] << 8) & 0xF00 | buffer[3]); + +    // Set a valid default calibration if data is missing +    ValidateCalibration(calibration); + +    return result; +} + +DriverResult CalibrationProtocol::GetRightJoyStickCalibration(JoyStickCalibration& calibration) { +    ScopedSetBlocking sb(this); +    std::vector<u8> buffer; +    DriverResult result{DriverResult::Success}; +    calibration = {}; + +    result = ReadSPI(CalAddr::USER_RIGHT_MAGIC, sizeof(u16), buffer); + +    if (result == DriverResult::Success) { +        const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1; +        if (has_user_calibration) { +            result = ReadSPI(CalAddr::USER_RIGHT_DATA, 9, buffer); +        } else { +            result = ReadSPI(CalAddr::FACT_RIGHT_DATA, 9, buffer); +        } +    } + +    if (result == DriverResult::Success) { +        calibration.x.center = static_cast<u16>(((buffer[1] & 0x0F) << 8) | buffer[0]); +        calibration.y.center = static_cast<u16>((buffer[2] << 4) | (buffer[1] >> 4)); +        calibration.x.min = static_cast<u16>(((buffer[4] & 0x0F) << 8) | buffer[3]); +        calibration.y.min = static_cast<u16>((buffer[5] << 4) | (buffer[4] >> 4)); +        calibration.x.max = static_cast<u16>(((buffer[7] & 0x0F) << 8) | buffer[6]); +        calibration.y.max = static_cast<u16>((buffer[8] << 4) | (buffer[7] >> 4)); +    } + +    // Nintendo fix for drifting stick +    // buffer = ReadSPI(0x60, 0x98 , 16); +    // joystick.deadzone = (u16)((buffer[4] << 8) & 0xF00 | buffer[3]); + +    // Set a valid default calibration if data is missing +    ValidateCalibration(calibration); + +    return result; +} + +DriverResult CalibrationProtocol::GetImuCalibration(MotionCalibration& calibration) { +    ScopedSetBlocking sb(this); +    std::vector<u8> buffer; +    DriverResult result{DriverResult::Success}; +    calibration = {}; + +    result = ReadSPI(CalAddr::USER_IMU_MAGIC, sizeof(u16), buffer); + +    if (result == DriverResult::Success) { +        const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1; +        if (has_user_calibration) { +            result = ReadSPI(CalAddr::USER_IMU_DATA, sizeof(IMUCalibration), buffer); +        } else { +            result = ReadSPI(CalAddr::FACT_IMU_DATA, sizeof(IMUCalibration), buffer); +        } +    } + +    if (result == DriverResult::Success) { +        IMUCalibration device_calibration{}; +        memcpy(&device_calibration, buffer.data(), sizeof(IMUCalibration)); +        calibration.accelerometer[0].offset = device_calibration.accelerometer_offset[0]; +        calibration.accelerometer[1].offset = device_calibration.accelerometer_offset[1]; +        calibration.accelerometer[2].offset = device_calibration.accelerometer_offset[2]; + +        calibration.accelerometer[0].scale = device_calibration.accelerometer_scale[0]; +        calibration.accelerometer[1].scale = device_calibration.accelerometer_scale[1]; +        calibration.accelerometer[2].scale = device_calibration.accelerometer_scale[2]; + +        calibration.gyro[0].offset = device_calibration.gyroscope_offset[0]; +        calibration.gyro[1].offset = device_calibration.gyroscope_offset[1]; +        calibration.gyro[2].offset = device_calibration.gyroscope_offset[2]; + +        calibration.gyro[0].scale = device_calibration.gyroscope_scale[0]; +        calibration.gyro[1].scale = device_calibration.gyroscope_scale[1]; +        calibration.gyro[2].scale = device_calibration.gyroscope_scale[2]; +    } + +    ValidateCalibration(calibration); + +    return result; +} + +DriverResult CalibrationProtocol::GetRingCalibration(RingCalibration& calibration, +                                                     s16 current_value) { +    // TODO: Get default calibration form ring itself +    if (ring_data_max == 0 && ring_data_min == 0) { +        ring_data_max = current_value + 800; +        ring_data_min = current_value - 800; +        ring_data_default = current_value; +    } +    ring_data_max = std::max(ring_data_max, current_value); +    ring_data_min = std::min(ring_data_min, current_value); +    calibration = { +        .default_value = ring_data_default, +        .max_value = ring_data_max, +        .min_value = ring_data_min, +    }; +    return DriverResult::Success; +} + +void CalibrationProtocol::ValidateCalibration(JoyStickCalibration& calibration) { +    constexpr u16 DefaultStickCenter{2048}; +    constexpr u16 DefaultStickRange{1740}; + +    if (calibration.x.center == 0xFFF || calibration.x.center == 0) { +        calibration.x.center = DefaultStickCenter; +    } +    if (calibration.x.max == 0xFFF || calibration.x.max == 0) { +        calibration.x.max = DefaultStickRange; +    } +    if (calibration.x.min == 0xFFF || calibration.x.min == 0) { +        calibration.x.min = DefaultStickRange; +    } + +    if (calibration.y.center == 0xFFF || calibration.y.center == 0) { +        calibration.y.center = DefaultStickCenter; +    } +    if (calibration.y.max == 0xFFF || calibration.y.max == 0) { +        calibration.y.max = DefaultStickRange; +    } +    if (calibration.y.min == 0xFFF || calibration.y.min == 0) { +        calibration.y.min = DefaultStickRange; +    } +} + +void CalibrationProtocol::ValidateCalibration(MotionCalibration& calibration) { +    for (auto& sensor : calibration.accelerometer) { +        if (sensor.scale == 0) { +            sensor.scale = 0x4000; +        } +    } +    for (auto& sensor : calibration.gyro) { +        if (sensor.scale == 0) { +            sensor.scale = 0x3be7; +        } +    } +} + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/calibration.h b/src/input_common/helpers/joycon_protocol/calibration.h new file mode 100644 index 000000000..afb52a36a --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/calibration.h @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse +// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c +// https://github.com/CTCaer/jc_toolkit +// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering + +#pragma once + +#include <vector> + +#include "input_common/helpers/joycon_protocol/common_protocol.h" + +namespace InputCommon::Joycon { +enum class DriverResult; +struct JoyStickCalibration; +struct IMUCalibration; +struct JoyconHandle; +} // namespace InputCommon::Joycon + +namespace InputCommon::Joycon { + +/// Driver functions related to retrieving calibration data from the device +class CalibrationProtocol final : private JoyconCommonProtocol { +public: +    explicit CalibrationProtocol(std::shared_ptr<JoyconHandle> handle); + +    /** +     * Sends a request to obtain the left stick calibration from memory +     * @param is_factory_calibration if true factory values will be returned +     * @returns JoyStickCalibration of the left joystick +     */ +    DriverResult GetLeftJoyStickCalibration(JoyStickCalibration& calibration); + +    /** +     * Sends a request to obtain the right stick calibration from memory +     * @param is_factory_calibration if true factory values will be returned +     * @returns JoyStickCalibration of the right joystick +     */ +    DriverResult GetRightJoyStickCalibration(JoyStickCalibration& calibration); + +    /** +     * Sends a request to obtain the motion calibration from memory +     * @returns ImuCalibration of the motion sensor +     */ +    DriverResult GetImuCalibration(MotionCalibration& calibration); + +    /** +     * Calculates on run time the proper calibration of the ring controller +     * @returns RingCalibration of the ring sensor +     */ +    DriverResult GetRingCalibration(RingCalibration& calibration, s16 current_value); + +private: +    void ValidateCalibration(JoyStickCalibration& calibration); +    void ValidateCalibration(MotionCalibration& calibration); + +    s16 ring_data_max = 0; +    s16 ring_data_default = 0; +    s16 ring_data_min = 0; +}; + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.cpp b/src/input_common/helpers/joycon_protocol/common_protocol.cpp new file mode 100644 index 000000000..417d0dcc5 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/common_protocol.cpp @@ -0,0 +1,299 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "input_common/helpers/joycon_protocol/common_protocol.h" + +namespace InputCommon::Joycon { +JoyconCommonProtocol::JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_) +    : hidapi_handle{std::move(hidapi_handle_)} {} + +u8 JoyconCommonProtocol::GetCounter() { +    hidapi_handle->packet_counter = (hidapi_handle->packet_counter + 1) & 0x0F; +    return hidapi_handle->packet_counter; +} + +void JoyconCommonProtocol::SetBlocking() { +    SDL_hid_set_nonblocking(hidapi_handle->handle, 0); +} + +void JoyconCommonProtocol::SetNonBlocking() { +    SDL_hid_set_nonblocking(hidapi_handle->handle, 1); +} + +DriverResult JoyconCommonProtocol::GetDeviceType(ControllerType& controller_type) { +    std::vector<u8> buffer; +    const auto result = ReadSPI(CalAddr::DEVICE_TYPE, 1, buffer); +    controller_type = ControllerType::None; + +    if (result == DriverResult::Success) { +        controller_type = static_cast<ControllerType>(buffer[0]); +        // Fallback to 3rd party pro controllers +        if (controller_type == ControllerType::None) { +            controller_type = ControllerType::Pro; +        } +    } + +    return result; +} + +DriverResult JoyconCommonProtocol::CheckDeviceAccess(SDL_hid_device_info* device_info) { +    ControllerType controller_type{ControllerType::None}; +    const auto result = GetDeviceType(controller_type); +    if (result != DriverResult::Success || controller_type == ControllerType::None) { +        return DriverResult::UnsupportedControllerType; +    } + +    hidapi_handle->handle = +        SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number); + +    if (!hidapi_handle->handle) { +        LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.", +                  device_info->vendor_id, device_info->product_id); +        return DriverResult::HandleInUse; +    } + +    SetNonBlocking(); +    return DriverResult::Success; +} + +DriverResult JoyconCommonProtocol::SetReportMode(ReportMode report_mode) { +    const std::array<u8, 1> buffer{static_cast<u8>(report_mode)}; +    return SendSubCommand(SubCommand::SET_REPORT_MODE, buffer); +} + +DriverResult JoyconCommonProtocol::SendData(std::span<const u8> buffer) { +    const auto result = SDL_hid_write(hidapi_handle->handle, buffer.data(), buffer.size()); + +    if (result == -1) { +        return DriverResult::ErrorWritingData; +    } + +    return DriverResult::Success; +} + +DriverResult JoyconCommonProtocol::GetSubCommandResponse(SubCommand sc, std::vector<u8>& output) { +    constexpr int timeout_mili = 66; +    constexpr int MaxTries = 15; +    int tries = 0; +    output.resize(MaxSubCommandResponseSize); + +    do { +        int result = SDL_hid_read_timeout(hidapi_handle->handle, output.data(), +                                          MaxSubCommandResponseSize, timeout_mili); + +        if (result < 1) { +            LOG_ERROR(Input, "No response from joycon"); +        } +        if (tries++ > MaxTries) { +            return DriverResult::Timeout; +        } +    } while (output[0] != 0x21 && output[14] != static_cast<u8>(sc)); + +    if (output[0] != 0x21 && output[14] != static_cast<u8>(sc)) { +        return DriverResult::WrongReply; +    } + +    return DriverResult::Success; +} + +DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer, +                                                  std::vector<u8>& output) { +    std::vector<u8> local_buffer(MaxResponseSize); + +    local_buffer[0] = static_cast<u8>(OutputReport::RUMBLE_AND_SUBCMD); +    local_buffer[1] = GetCounter(); +    local_buffer[10] = static_cast<u8>(sc); +    for (std::size_t i = 0; i < buffer.size(); ++i) { +        local_buffer[11 + i] = buffer[i]; +    } + +    auto result = SendData(local_buffer); + +    if (result != DriverResult::Success) { +        return result; +    } + +    result = GetSubCommandResponse(sc, output); + +    return DriverResult::Success; +} + +DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer) { +    std::vector<u8> output; +    return SendSubCommand(sc, buffer, output); +} + +DriverResult JoyconCommonProtocol::SendMCUCommand(SubCommand sc, std::span<const u8> buffer) { +    std::vector<u8> local_buffer(MaxResponseSize); + +    local_buffer[0] = static_cast<u8>(OutputReport::MCU_DATA); +    local_buffer[1] = GetCounter(); +    local_buffer[10] = static_cast<u8>(sc); +    for (std::size_t i = 0; i < buffer.size(); ++i) { +        local_buffer[11 + i] = buffer[i]; +    } + +    return SendData(local_buffer); +} + +DriverResult JoyconCommonProtocol::SendVibrationReport(std::span<const u8> buffer) { +    std::vector<u8> local_buffer(MaxResponseSize); + +    local_buffer[0] = static_cast<u8>(Joycon::OutputReport::RUMBLE_ONLY); +    local_buffer[1] = GetCounter(); + +    memcpy(local_buffer.data() + 2, buffer.data(), buffer.size()); + +    return SendData(local_buffer); +} + +DriverResult JoyconCommonProtocol::ReadSPI(CalAddr addr, u8 size, std::vector<u8>& output) { +    constexpr std::size_t MaxTries = 10; +    std::size_t tries = 0; +    std::array<u8, 5> buffer = {0x00, 0x00, 0x00, 0x00, size}; +    std::vector<u8> local_buffer(size + 20); + +    buffer[0] = static_cast<u8>(static_cast<u16>(addr) & 0x00FF); +    buffer[1] = static_cast<u8>((static_cast<u16>(addr) & 0xFF00) >> 8); +    do { +        const auto result = SendSubCommand(SubCommand::SPI_FLASH_READ, buffer, local_buffer); +        if (result != DriverResult::Success) { +            return result; +        } + +        if (tries++ > MaxTries) { +            return DriverResult::Timeout; +        } +    } while (local_buffer[15] != buffer[0] || local_buffer[16] != buffer[1]); + +    // Remove header from output +    output = std::vector<u8>(local_buffer.begin() + 20, local_buffer.begin() + 20 + size); +    return DriverResult::Success; +} + +DriverResult JoyconCommonProtocol::EnableMCU(bool enable) { +    const std::array<u8, 1> mcu_state{static_cast<u8>(enable ? 1 : 0)}; +    const auto result = SendSubCommand(SubCommand::SET_MCU_STATE, mcu_state); + +    if (result != DriverResult::Success) { +        LOG_ERROR(Input, "SendMCUData failed with error {}", result); +    } + +    return result; +} + +DriverResult JoyconCommonProtocol::ConfigureMCU(const MCUConfig& config) { +    LOG_DEBUG(Input, "ConfigureMCU"); +    std::array<u8, sizeof(MCUConfig)> config_buffer; +    memcpy(config_buffer.data(), &config, sizeof(MCUConfig)); +    config_buffer[37] = CalculateMCU_CRC8(config_buffer.data() + 1, 36); + +    const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, config_buffer); + +    if (result != DriverResult::Success) { +        LOG_ERROR(Input, "Set MCU config failed with error {}", result); +    } + +    return result; +} + +DriverResult JoyconCommonProtocol::GetMCUDataResponse(ReportMode report_mode_, +                                                      std::vector<u8>& output) { +    const int report_mode = static_cast<u8>(report_mode_); +    constexpr int TimeoutMili = 200; +    constexpr int MaxTries = 9; +    int tries = 0; +    output.resize(0x170); + +    do { +        int result = SDL_hid_read_timeout(hidapi_handle->handle, output.data(), 0x170, TimeoutMili); + +        if (result < 1) { +            LOG_ERROR(Input, "No response from joycon attempt {}", tries); +        } +        if (tries++ > MaxTries) { +            return DriverResult::Timeout; +        } +    } while (output[0] != report_mode || output[49] == 0xFF); + +    if (output[0] != report_mode || output[49] == 0xFF) { +        return DriverResult::WrongReply; +    } + +    return DriverResult::Success; +} + +DriverResult JoyconCommonProtocol::SendMCUData(ReportMode report_mode, SubCommand sc, +                                               std::span<const u8> buffer, +                                               std::vector<u8>& output) { +    std::vector<u8> local_buffer(MaxResponseSize); + +    local_buffer[0] = static_cast<u8>(OutputReport::MCU_DATA); +    local_buffer[1] = GetCounter(); +    local_buffer[9] = static_cast<u8>(sc); +    for (std::size_t i = 0; i < buffer.size(); ++i) { +        local_buffer[10 + i] = buffer[i]; +    } + +    auto result = SendData(local_buffer); + +    if (result != DriverResult::Success) { +        return result; +    } + +    result = GetMCUDataResponse(report_mode, output); + +    return DriverResult::Success; +} + +DriverResult JoyconCommonProtocol::WaitSetMCUMode(ReportMode report_mode, MCUMode mode) { +    std::vector<u8> output; +    constexpr std::size_t MaxTries{8}; +    std::size_t tries{}; + +    do { +        const std::vector<u8> mcu_data{static_cast<u8>(MCUMode::Standby)}; +        const auto result = SendMCUData(report_mode, SubCommand::STATE, mcu_data, output); + +        if (result != DriverResult::Success) { +            return result; +        } + +        if (tries++ > MaxTries) { +            return DriverResult::WrongReply; +        } +    } while (output[49] != 1 || output[56] != static_cast<u8>(mode)); + +    return DriverResult::Success; +} + +// crc-8-ccitt / polynomial 0x07 look up table +constexpr std::array<u8, 256> mcu_crc8_table = { +    0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, +    0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, +    0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, +    0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, +    0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, +    0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, +    0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, +    0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, +    0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, +    0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, +    0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, +    0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, +    0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, +    0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, +    0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, +    0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3}; + +u8 JoyconCommonProtocol::CalculateMCU_CRC8(u8* buffer, u8 size) const { +    u8 crc8 = 0x0; + +    for (int i = 0; i < size; ++i) { +        crc8 = mcu_crc8_table[static_cast<u8>(crc8 ^ buffer[i])]; +    } +    return crc8; +} + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.h b/src/input_common/helpers/joycon_protocol/common_protocol.h new file mode 100644 index 000000000..903bcf402 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/common_protocol.h @@ -0,0 +1,173 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse +// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c +// https://github.com/CTCaer/jc_toolkit +// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering + +#pragma once + +#include <memory> +#include <span> +#include <vector> + +#include "common/common_types.h" +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon::Joycon { + +/// Joycon driver functions that handle low level communication +class JoyconCommonProtocol { +public: +    explicit JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_); + +    /** +     * Sets handle to blocking. In blocking mode, SDL_hid_read() will wait (block) until there is +     * data to read before returning. +     */ +    void SetBlocking(); + +    /** +     * Sets handle to non blocking. In non-blocking mode calls to SDL_hid_read() will return +     * immediately with a value of 0 if there is no data to be read +     */ +    void SetNonBlocking(); + +    /** +     * Sends a request to obtain the joycon type from device +     * @returns controller type of the joycon +     */ +    DriverResult GetDeviceType(ControllerType& controller_type); + +    /** +     * Verifies and sets the joycon_handle if device is valid +     * @param device info from the driver +     * @returns success if the device is valid +     */ +    DriverResult CheckDeviceAccess(SDL_hid_device_info* device); + +    /** +     * Sends a request to set the polling mode of the joycon +     * @param report_mode polling mode to be set +     */ +    DriverResult SetReportMode(Joycon::ReportMode report_mode); + +    /** +     * Sends data to the joycon device +     * @param buffer data to be send +     */ +    DriverResult SendData(std::span<const u8> buffer); + +    /** +     * Waits for incoming data of the joycon device that matchs the subcommand +     * @param sub_command type of data to be returned +     * @returns a buffer containing the responce +     */ +    DriverResult GetSubCommandResponse(SubCommand sub_command, std::vector<u8>& output); + +    /** +     * Sends a sub command to the device and waits for it's reply +     * @param sc sub command to be send +     * @param buffer data to be send +     * @returns output buffer containing the responce +     */ +    DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer, std::vector<u8>& output); + +    /** +     * Sends a sub command to the device and waits for it's reply and ignores the output +     * @param sc sub command to be send +     * @param buffer data to be send +     */ +    DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer); + +    /** +     * Sends a mcu command to the device +     * @param sc sub command to be send +     * @param buffer data to be send +     */ +    DriverResult SendMCUCommand(SubCommand sc, std::span<const u8> buffer); + +    /** +     * Sends vibration data to the joycon +     * @param buffer data to be send +     */ +    DriverResult SendVibrationReport(std::span<const u8> buffer); + +    /** +     * Reads the SPI memory stored on the joycon +     * @param Initial address location +     * @param size in bytes to be read +     * @returns output buffer containing the responce +     */ +    DriverResult ReadSPI(CalAddr addr, u8 size, std::vector<u8>& output); + +    /** +     * Enables MCU chip on the joycon +     * @param enable if true the chip will be enabled +     */ +    DriverResult EnableMCU(bool enable); + +    /** +     * Configures the MCU to the correspoinding mode +     * @param MCUConfig configuration +     */ +    DriverResult ConfigureMCU(const MCUConfig& config); + +    /** +     * Waits until there's MCU data available. On timeout returns error +     * @param report mode of the expected reply +     * @returns a buffer containing the responce +     */ +    DriverResult GetMCUDataResponse(ReportMode report_mode_, std::vector<u8>& output); + +    /** +     * Sends data to the MCU chip and waits for it's reply +     * @param report mode of the expected reply +     * @param sub command to be send +     * @param buffer data to be send +     * @returns output buffer containing the responce +     */ +    DriverResult SendMCUData(ReportMode report_mode, SubCommand sc, std::span<const u8> buffer, +                             std::vector<u8>& output); + +    /** +     * Wait's until the MCU chip is on the specified mode +     * @param report mode of the expected reply +     * @param MCUMode configuration +     */ +    DriverResult WaitSetMCUMode(ReportMode report_mode, MCUMode mode); + +    /** +     * Calculates the checksum from the MCU data +     * @param buffer containing the data to be send +     * @param size of the buffer in bytes +     * @returns byte with the correct checksum +     */ +    u8 CalculateMCU_CRC8(u8* buffer, u8 size) const; + +private: +    /** +     * Increments and returns the packet counter of the handle +     * @param joycon_handle device to send the data +     * @returns packet counter value +     */ +    u8 GetCounter(); + +    std::shared_ptr<JoyconHandle> hidapi_handle; +}; + +class ScopedSetBlocking { +public: +    explicit ScopedSetBlocking(JoyconCommonProtocol* self) : m_self{self} { +        m_self->SetBlocking(); +    } + +    ~ScopedSetBlocking() { +        m_self->SetNonBlocking(); +    } + +private: +    JoyconCommonProtocol* m_self{}; +}; +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.cpp b/src/input_common/helpers/joycon_protocol/generic_functions.cpp new file mode 100644 index 000000000..52bb8b61a --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/generic_functions.cpp @@ -0,0 +1,125 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "input_common/helpers/joycon_protocol/generic_functions.h" + +namespace InputCommon::Joycon { + +GenericProtocol::GenericProtocol(std::shared_ptr<JoyconHandle> handle) +    : JoyconCommonProtocol(std::move(handle)) {} + +DriverResult GenericProtocol::EnablePassiveMode() { +    ScopedSetBlocking sb(this); +    return SetReportMode(ReportMode::SIMPLE_HID_MODE); +} + +DriverResult GenericProtocol::EnableActiveMode() { +    ScopedSetBlocking sb(this); +    return SetReportMode(ReportMode::STANDARD_FULL_60HZ); +} + +DriverResult GenericProtocol::GetDeviceInfo(DeviceInfo& device_info) { +    ScopedSetBlocking sb(this); +    std::vector<u8> output; + +    const auto result = SendSubCommand(SubCommand::REQ_DEV_INFO, {}, output); + +    device_info = {}; +    if (result == DriverResult::Success) { +        memcpy(&device_info, output.data(), sizeof(DeviceInfo)); +    } + +    return result; +} + +DriverResult GenericProtocol::GetControllerType(ControllerType& controller_type) { +    return GetDeviceType(controller_type); +} + +DriverResult GenericProtocol::EnableImu(bool enable) { +    ScopedSetBlocking sb(this); +    const std::array<u8, 1> buffer{static_cast<u8>(enable ? 1 : 0)}; +    return SendSubCommand(SubCommand::ENABLE_IMU, buffer); +} + +DriverResult GenericProtocol::SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec, +                                           AccelerometerSensitivity asen, +                                           AccelerometerPerformance afrec) { +    ScopedSetBlocking sb(this); +    const std::array<u8, 4> buffer{static_cast<u8>(gsen), static_cast<u8>(asen), +                                   static_cast<u8>(gfrec), static_cast<u8>(afrec)}; +    return SendSubCommand(SubCommand::SET_IMU_SENSITIVITY, buffer); +} + +DriverResult GenericProtocol::GetBattery(u32& battery_level) { +    // This function is meant to request the high resolution battery status +    battery_level = 0; +    return DriverResult::NotSupported; +} + +DriverResult GenericProtocol::GetColor(Color& color) { +    ScopedSetBlocking sb(this); +    std::vector<u8> buffer; +    const auto result = ReadSPI(CalAddr::COLOR_DATA, 12, buffer); + +    color = {}; +    if (result == DriverResult::Success) { +        color.body = static_cast<u32>((buffer[0] << 16) | (buffer[1] << 8) | buffer[2]); +        color.buttons = static_cast<u32>((buffer[3] << 16) | (buffer[4] << 8) | buffer[5]); +        color.left_grip = static_cast<u32>((buffer[6] << 16) | (buffer[7] << 8) | buffer[8]); +        color.right_grip = static_cast<u32>((buffer[9] << 16) | (buffer[10] << 8) | buffer[11]); +    } + +    return result; +} + +DriverResult GenericProtocol::GetSerialNumber(SerialNumber& serial_number) { +    ScopedSetBlocking sb(this); +    std::vector<u8> buffer; +    const auto result = ReadSPI(CalAddr::SERIAL_NUMBER, 16, buffer); + +    serial_number = {}; +    if (result == DriverResult::Success) { +        memcpy(serial_number.data(), buffer.data() + 1, sizeof(SerialNumber)); +    } + +    return result; +} + +DriverResult GenericProtocol::GetTemperature(u32& temperature) { +    // Not all devices have temperature sensor +    temperature = 25; +    return DriverResult::NotSupported; +} + +DriverResult GenericProtocol::GetVersionNumber(FirmwareVersion& version) { +    DeviceInfo device_info{}; + +    const auto result = GetDeviceInfo(device_info); +    version = device_info.firmware; + +    return result; +} + +DriverResult GenericProtocol::SetHomeLight() { +    ScopedSetBlocking sb(this); +    static constexpr std::array<u8, 3> buffer{0x0f, 0xf0, 0x00}; +    return SendSubCommand(SubCommand::SET_HOME_LIGHT, buffer); +} + +DriverResult GenericProtocol::SetLedBusy() { +    return DriverResult::NotSupported; +} + +DriverResult GenericProtocol::SetLedPattern(u8 leds) { +    ScopedSetBlocking sb(this); +    const std::array<u8, 1> buffer{leds}; +    return SendSubCommand(SubCommand::SET_PLAYER_LIGHTS, buffer); +} + +DriverResult GenericProtocol::SetLedBlinkPattern(u8 leds) { +    return SetLedPattern(static_cast<u8>(leds << 4)); +} + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.h b/src/input_common/helpers/joycon_protocol/generic_functions.h new file mode 100644 index 000000000..239bb7dbf --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/generic_functions.h @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse +// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c +// https://github.com/CTCaer/jc_toolkit +// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering + +#pragma once + +#include "input_common/helpers/joycon_protocol/common_protocol.h" +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon::Joycon { + +/// Joycon driver functions that easily implemented +class GenericProtocol final : private JoyconCommonProtocol { +public: +    explicit GenericProtocol(std::shared_ptr<JoyconHandle> handle); + +    /// Enables passive mode. This mode only sends button data on change. Sticks will return digital +    /// data instead of analog. Motion will be disabled +    DriverResult EnablePassiveMode(); + +    /// Enables active mode. This mode will return the current status every 5-15ms +    DriverResult EnableActiveMode(); + +    /** +     * Sends a request to obtain the joycon firmware and mac from handle +     * @returns controller device info +     */ +    DriverResult GetDeviceInfo(DeviceInfo& controller_type); + +    /** +     * Sends a request to obtain the joycon type from handle +     * @returns controller type of the joycon +     */ +    DriverResult GetControllerType(ControllerType& controller_type); + +    /** +     * Enables motion input +     * @param enable if true motion data will be enabled +     */ +    DriverResult EnableImu(bool enable); + +    /** +     * Configures the motion sensor with the specified parameters +     * @param gsen gyroscope sensor sensitvity in degrees per second +     * @param gfrec gyroscope sensor frequency in hertz +     * @param asen accelerometer sensitivity in G force +     * @param afrec accelerometer frequency in hertz +     */ +    DriverResult SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec, +                              AccelerometerSensitivity asen, AccelerometerPerformance afrec); + +    /** +     * Request battery level from the device +     * @returns battery level +     */ +    DriverResult GetBattery(u32& battery_level); + +    /** +     * Request joycon colors from the device +     * @returns colors of the body and buttons +     */ +    DriverResult GetColor(Color& color); + +    /** +     * Request joycon serial number from the device +     * @returns 16 byte serial number +     */ +    DriverResult GetSerialNumber(SerialNumber& serial_number); + +    /** +     * Request joycon serial number from the device +     * @returns 16 byte serial number +     */ +    DriverResult GetTemperature(u32& temperature); + +    /** +     * Request joycon serial number from the device +     * @returns 16 byte serial number +     */ +    DriverResult GetVersionNumber(FirmwareVersion& version); + +    /** +     * Sets home led behaviour +     */ +    DriverResult SetHomeLight(); + +    /** +     * Sets home led into a slow breathing state +     */ +    DriverResult SetLedBusy(); + +    /** +     * Sets the 4 player leds on the joycon on a solid state +     * @params bit flag containing the led state +     */ +    DriverResult SetLedPattern(u8 leds); + +    /** +     * Sets the 4 player leds on the joycon on a blinking state +     * @returns bit flag containing the led state +     */ +    DriverResult SetLedBlinkPattern(u8 leds); +}; +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/irs.cpp b/src/input_common/helpers/joycon_protocol/irs.cpp new file mode 100644 index 000000000..09e17bc5b --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/irs.cpp @@ -0,0 +1,298 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <thread> +#include "common/logging/log.h" +#include "input_common/helpers/joycon_protocol/irs.h" + +namespace InputCommon::Joycon { + +IrsProtocol::IrsProtocol(std::shared_ptr<JoyconHandle> handle) +    : JoyconCommonProtocol(std::move(handle)) {} + +DriverResult IrsProtocol::EnableIrs() { +    LOG_INFO(Input, "Enable IRS"); +    ScopedSetBlocking sb(this); +    DriverResult result{DriverResult::Success}; + +    if (result == DriverResult::Success) { +        result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ); +    } +    if (result == DriverResult::Success) { +        result = EnableMCU(true); +    } +    if (result == DriverResult::Success) { +        result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby); +    } +    if (result == DriverResult::Success) { +        const MCUConfig config{ +            .command = MCUCommand::ConfigureMCU, +            .sub_command = MCUSubCommand::SetMCUMode, +            .mode = MCUMode::IR, +            .crc = {}, +        }; + +        result = ConfigureMCU(config); +    } +    if (result == DriverResult::Success) { +        result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::IR); +    } +    if (result == DriverResult::Success) { +        result = ConfigureIrs(); +    } +    if (result == DriverResult::Success) { +        result = WriteRegistersStep1(); +    } +    if (result == DriverResult::Success) { +        result = WriteRegistersStep2(); +    } + +    is_enabled = true; + +    return result; +} + +DriverResult IrsProtocol::DisableIrs() { +    LOG_DEBUG(Input, "Disable IRS"); +    ScopedSetBlocking sb(this); +    DriverResult result{DriverResult::Success}; + +    if (result == DriverResult::Success) { +        result = EnableMCU(false); +    } + +    is_enabled = false; + +    return result; +} + +DriverResult IrsProtocol::SetIrsConfig(IrsMode mode, IrsResolution format) { +    irs_mode = mode; +    switch (format) { +    case IrsResolution::Size320x240: +        resolution_code = IrsResolutionCode::Size320x240; +        fragments = IrsFragments::Size320x240; +        resolution = IrsResolution::Size320x240; +        break; +    case IrsResolution::Size160x120: +        resolution_code = IrsResolutionCode::Size160x120; +        fragments = IrsFragments::Size160x120; +        resolution = IrsResolution::Size160x120; +        break; +    case IrsResolution::Size80x60: +        resolution_code = IrsResolutionCode::Size80x60; +        fragments = IrsFragments::Size80x60; +        resolution = IrsResolution::Size80x60; +        break; +    case IrsResolution::Size20x15: +        resolution_code = IrsResolutionCode::Size20x15; +        fragments = IrsFragments::Size20x15; +        resolution = IrsResolution::Size20x15; +        break; +    case IrsResolution::Size40x30: +    default: +        resolution_code = IrsResolutionCode::Size40x30; +        fragments = IrsFragments::Size40x30; +        resolution = IrsResolution::Size40x30; +        break; +    } + +    // Restart feature +    if (is_enabled) { +        DisableIrs(); +        return EnableIrs(); +    } + +    return DriverResult::Success; +} + +DriverResult IrsProtocol::RequestImage(std::span<u8> buffer) { +    const u8 next_packet_fragment = +        static_cast<u8>((packet_fragment + 1) % (static_cast<u8>(fragments) + 1)); + +    if (buffer[0] == 0x31 && buffer[49] == 0x03) { +        u8 new_packet_fragment = buffer[52]; +        if (new_packet_fragment == next_packet_fragment) { +            packet_fragment = next_packet_fragment; +            memcpy(buf_image.data() + (300 * packet_fragment), buffer.data() + 59, 300); + +            return RequestFrame(packet_fragment); +        } + +        if (new_packet_fragment == packet_fragment) { +            return RequestFrame(packet_fragment); +        } + +        return ResendFrame(next_packet_fragment); +    } + +    return RequestFrame(packet_fragment); +} + +DriverResult IrsProtocol::ConfigureIrs() { +    LOG_DEBUG(Input, "Configure IRS"); +    constexpr std::size_t max_tries = 28; +    std::vector<u8> output; +    std::size_t tries = 0; + +    const IrsConfigure irs_configuration{ +        .command = MCUCommand::ConfigureIR, +        .sub_command = MCUSubCommand::SetDeviceMode, +        .irs_mode = IrsMode::ImageTransfer, +        .number_of_fragments = fragments, +        .mcu_major_version = 0x0500, +        .mcu_minor_version = 0x1800, +        .crc = {}, +    }; +    buf_image.resize((static_cast<u8>(fragments) + 1) * 300); + +    std::array<u8, sizeof(IrsConfigure)> request_data{}; +    memcpy(request_data.data(), &irs_configuration, sizeof(IrsConfigure)); +    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); +    do { +        const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output); + +        if (result != DriverResult::Success) { +            return result; +        } +        if (tries++ >= max_tries) { +            return DriverResult::WrongReply; +        } +    } while (output[15] != 0x0b); + +    return DriverResult::Success; +} + +DriverResult IrsProtocol::WriteRegistersStep1() { +    LOG_DEBUG(Input, "WriteRegistersStep1"); +    DriverResult result{DriverResult::Success}; +    constexpr std::size_t max_tries = 28; +    std::vector<u8> output; +    std::size_t tries = 0; + +    const IrsWriteRegisters irs_registers{ +        .command = MCUCommand::ConfigureIR, +        .sub_command = MCUSubCommand::WriteDeviceRegisters, +        .number_of_registers = 0x9, +        .registers = +            { +                IrsRegister{IrRegistersAddress::Resolution, static_cast<u8>(resolution_code)}, +                {IrRegistersAddress::ExposureLSB, static_cast<u8>(exposure & 0xff)}, +                {IrRegistersAddress::ExposureMSB, static_cast<u8>(exposure >> 8)}, +                {IrRegistersAddress::ExposureTime, 0x00}, +                {IrRegistersAddress::Leds, static_cast<u8>(leds)}, +                {IrRegistersAddress::DigitalGainLSB, static_cast<u8>((digital_gain & 0x0f) << 4)}, +                {IrRegistersAddress::DigitalGainMSB, static_cast<u8>((digital_gain & 0xf0) >> 4)}, +                {IrRegistersAddress::LedFilter, static_cast<u8>(led_filter)}, +                {IrRegistersAddress::WhitePixelThreshold, 0xc8}, +            }, +        .crc = {}, +    }; + +    std::array<u8, sizeof(IrsWriteRegisters)> request_data{}; +    memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters)); +    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); + +    std::array<u8, 38> mcu_request{0x02}; +    mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36); +    mcu_request[37] = 0xFF; + +    if (result != DriverResult::Success) { +        return result; +    } + +    do { +        result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output); + +        // First time we need to set the report mode +        if (result == DriverResult::Success && tries == 0) { +            result = SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request); +        } +        if (result == DriverResult::Success && tries == 0) { +            GetSubCommandResponse(SubCommand::SET_MCU_CONFIG, output); +        } + +        if (result != DriverResult::Success) { +            return result; +        } +        if (tries++ >= max_tries) { +            return DriverResult::WrongReply; +        } +    } while (!(output[15] == 0x13 && output[17] == 0x07) && output[15] != 0x23); + +    return DriverResult::Success; +} + +DriverResult IrsProtocol::WriteRegistersStep2() { +    LOG_DEBUG(Input, "WriteRegistersStep2"); +    constexpr std::size_t max_tries = 28; +    std::vector<u8> output; +    std::size_t tries = 0; + +    const IrsWriteRegisters irs_registers{ +        .command = MCUCommand::ConfigureIR, +        .sub_command = MCUSubCommand::WriteDeviceRegisters, +        .number_of_registers = 0x8, +        .registers = +            { +                IrsRegister{IrRegistersAddress::LedIntensitiyMSB, +                            static_cast<u8>(led_intensity >> 8)}, +                {IrRegistersAddress::LedIntensitiyLSB, static_cast<u8>(led_intensity & 0xff)}, +                {IrRegistersAddress::ImageFlip, static_cast<u8>(image_flip)}, +                {IrRegistersAddress::DenoiseSmoothing, static_cast<u8>((denoise >> 16) & 0xff)}, +                {IrRegistersAddress::DenoiseEdge, static_cast<u8>((denoise >> 8) & 0xff)}, +                {IrRegistersAddress::DenoiseColor, static_cast<u8>(denoise & 0xff)}, +                {IrRegistersAddress::UpdateTime, 0x2d}, +                {IrRegistersAddress::FinalizeConfig, 0x01}, +            }, +        .crc = {}, +    }; + +    std::array<u8, sizeof(IrsWriteRegisters)> request_data{}; +    memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters)); +    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); +    do { +        const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output); + +        if (result != DriverResult::Success) { +            return result; +        } +        if (tries++ >= max_tries) { +            return DriverResult::WrongReply; +        } +    } while (output[15] != 0x13 && output[15] != 0x23); + +    return DriverResult::Success; +} + +DriverResult IrsProtocol::RequestFrame(u8 frame) { +    std::array<u8, 38> mcu_request{}; +    mcu_request[3] = frame; +    mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36); +    mcu_request[37] = 0xFF; +    return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request); +} + +DriverResult IrsProtocol::ResendFrame(u8 frame) { +    std::array<u8, 38> mcu_request{}; +    mcu_request[1] = 0x1; +    mcu_request[2] = frame; +    mcu_request[3] = 0x0; +    mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36); +    mcu_request[37] = 0xFF; +    return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request); +} + +std::vector<u8> IrsProtocol::GetImage() const { +    return buf_image; +} + +IrsResolution IrsProtocol::GetIrsFormat() const { +    return resolution; +} + +bool IrsProtocol::IsEnabled() const { +    return is_enabled; +} + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/irs.h b/src/input_common/helpers/joycon_protocol/irs.h new file mode 100644 index 000000000..76dfa02ea --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/irs.h @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse +// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c +// https://github.com/CTCaer/jc_toolkit +// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering + +#pragma once + +#include <vector> + +#include "input_common/helpers/joycon_protocol/common_protocol.h" +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon::Joycon { + +class IrsProtocol final : private JoyconCommonProtocol { +public: +    explicit IrsProtocol(std::shared_ptr<JoyconHandle> handle); + +    DriverResult EnableIrs(); + +    DriverResult DisableIrs(); + +    DriverResult SetIrsConfig(IrsMode mode, IrsResolution format); + +    DriverResult RequestImage(std::span<u8> buffer); + +    std::vector<u8> GetImage() const; + +    IrsResolution GetIrsFormat() const; + +    bool IsEnabled() const; + +private: +    DriverResult ConfigureIrs(); + +    DriverResult WriteRegistersStep1(); +    DriverResult WriteRegistersStep2(); + +    DriverResult RequestFrame(u8 frame); +    DriverResult ResendFrame(u8 frame); + +    IrsMode irs_mode{IrsMode::ImageTransfer}; +    IrsResolution resolution{IrsResolution::Size40x30}; +    IrsResolutionCode resolution_code{IrsResolutionCode::Size40x30}; +    IrsFragments fragments{IrsFragments::Size40x30}; +    IrLeds leds{IrLeds::BrightAndDim}; +    IrExLedFilter led_filter{IrExLedFilter::Enabled}; +    IrImageFlip image_flip{IrImageFlip::Normal}; +    u8 digital_gain{0x01}; +    u16 exposure{0x2490}; +    u16 led_intensity{0x0f10}; +    u32 denoise{0x012344}; + +    u8 packet_fragment{}; +    std::vector<u8> buf_image; // 8bpp greyscale image. + +    bool is_enabled{}; +}; + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/joycon_types.h b/src/input_common/helpers/joycon_protocol/joycon_types.h new file mode 100644 index 000000000..e2d47349f --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/joycon_types.h @@ -0,0 +1,612 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse +// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c +// https://github.com/CTCaer/jc_toolkit +// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering + +#pragma once + +#include <array> +#include <functional> +#include <SDL_hidapi.h> + +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace InputCommon::Joycon { +constexpr u32 MaxErrorCount = 50; +constexpr u32 MaxBufferSize = 368; +constexpr u32 MaxResponseSize = 49; +constexpr u32 MaxSubCommandResponseSize = 64; +constexpr std::array<u8, 8> DefaultVibrationBuffer{0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40}; + +using MacAddress = std::array<u8, 6>; +using SerialNumber = std::array<u8, 15>; + +enum class ControllerType { +    None, +    Left, +    Right, +    Pro, +    Grip, +    Dual, +}; + +enum class PadAxes { +    LeftStickX, +    LeftStickY, +    RightStickX, +    RightStickY, +    Undefined, +}; + +enum class PadMotion { +    LeftMotion, +    RightMotion, +    Undefined, +}; + +enum class PadButton : u32 { +    Down = 0x000001, +    Up = 0x000002, +    Right = 0x000004, +    Left = 0x000008, +    LeftSR = 0x000010, +    LeftSL = 0x000020, +    L = 0x000040, +    ZL = 0x000080, +    Y = 0x000100, +    X = 0x000200, +    B = 0x000400, +    A = 0x000800, +    RightSR = 0x001000, +    RightSL = 0x002000, +    R = 0x004000, +    ZR = 0x008000, +    Minus = 0x010000, +    Plus = 0x020000, +    StickR = 0x040000, +    StickL = 0x080000, +    Home = 0x100000, +    Capture = 0x200000, +}; + +enum class PasivePadButton : u32 { +    Down_A = 0x0001, +    Right_X = 0x0002, +    Left_B = 0x0004, +    Up_Y = 0x0008, +    SL = 0x0010, +    SR = 0x0020, +    Minus = 0x0100, +    Plus = 0x0200, +    StickL = 0x0400, +    StickR = 0x0800, +    Home = 0x1000, +    Capture = 0x2000, +    L_R = 0x4000, +    ZL_ZR = 0x8000, +}; + +enum class OutputReport : u8 { +    RUMBLE_AND_SUBCMD = 0x01, +    FW_UPDATE_PKT = 0x03, +    RUMBLE_ONLY = 0x10, +    MCU_DATA = 0x11, +    USB_CMD = 0x80, +}; + +enum class InputReport : u8 { +    SUBCMD_REPLY = 0x21, +    STANDARD_FULL_60HZ = 0x30, +    NFC_IR_MODE_60HZ = 0x31, +    SIMPLE_HID_MODE = 0x3F, +    INPUT_USB_RESPONSE = 0x81, +}; + +enum class FeatureReport : u8 { +    Last_SUBCMD = 0x02, +    OTA_GW_UPGRADE = 0x70, +    SETUP_MEM_READ = 0x71, +    MEM_READ = 0x72, +    ERASE_MEM_SECTOR = 0x73, +    MEM_WRITE = 0x74, +    LAUNCH = 0x75, +}; + +enum class SubCommand : u8 { +    STATE = 0x00, +    MANUAL_BT_PAIRING = 0x01, +    REQ_DEV_INFO = 0x02, +    SET_REPORT_MODE = 0x03, +    TRIGGERS_ELAPSED = 0x04, +    GET_PAGE_LIST_STATE = 0x05, +    SET_HCI_STATE = 0x06, +    RESET_PAIRING_INFO = 0x07, +    LOW_POWER_MODE = 0x08, +    SPI_FLASH_READ = 0x10, +    SPI_FLASH_WRITE = 0x11, +    RESET_MCU = 0x20, +    SET_MCU_CONFIG = 0x21, +    SET_MCU_STATE = 0x22, +    SET_PLAYER_LIGHTS = 0x30, +    GET_PLAYER_LIGHTS = 0x31, +    SET_HOME_LIGHT = 0x38, +    ENABLE_IMU = 0x40, +    SET_IMU_SENSITIVITY = 0x41, +    WRITE_IMU_REG = 0x42, +    READ_IMU_REG = 0x43, +    ENABLE_VIBRATION = 0x48, +    GET_REGULATED_VOLTAGE = 0x50, +    SET_EXTERNAL_CONFIG = 0x58, +    UNKNOWN_RINGCON = 0x59, +    UNKNOWN_RINGCON2 = 0x5A, +    UNKNOWN_RINGCON3 = 0x5C, +}; + +enum class UsbSubCommand : u8 { +    CONN_STATUS = 0x01, +    HADSHAKE = 0x02, +    BAUDRATE_3M = 0x03, +    NO_TIMEOUT = 0x04, +    EN_TIMEOUT = 0x05, +    RESET = 0x06, +    PRE_HANDSHAKE = 0x91, +    SEND_UART = 0x92, +}; + +enum class CalMagic : u8 { +    USR_MAGIC_0 = 0xB2, +    USR_MAGIC_1 = 0xA1, +    USRR_MAGI_SIZE = 2, +}; + +enum class CalAddr { +    SERIAL_NUMBER = 0X6000, +    DEVICE_TYPE = 0X6012, +    COLOR_EXIST = 0X601B, +    FACT_LEFT_DATA = 0X603d, +    FACT_RIGHT_DATA = 0X6046, +    COLOR_DATA = 0X6050, +    FACT_IMU_DATA = 0X6020, +    USER_LEFT_MAGIC = 0X8010, +    USER_LEFT_DATA = 0X8012, +    USER_RIGHT_MAGIC = 0X801B, +    USER_RIGHT_DATA = 0X801D, +    USER_IMU_MAGIC = 0X8026, +    USER_IMU_DATA = 0X8028, +}; + +enum class ReportMode : u8 { +    ACTIVE_POLLING_NFC_IR_CAMERA_DATA = 0x00, +    ACTIVE_POLLING_NFC_IR_CAMERA_CONFIGURATION = 0x01, +    ACTIVE_POLLING_NFC_IR_CAMERA_DATA_CONFIGURATION = 0x02, +    ACTIVE_POLLING_IR_CAMERA_DATA = 0x03, +    MCU_UPDATE_STATE = 0x23, +    STANDARD_FULL_60HZ = 0x30, +    NFC_IR_MODE_60HZ = 0x31, +    SIMPLE_HID_MODE = 0x3F, +}; + +enum class GyroSensitivity : u8 { +    DPS250, +    DPS500, +    DPS1000, +    DPS2000, // Default +}; + +enum class AccelerometerSensitivity : u8 { +    G8, // Default +    G4, +    G2, +    G16, +}; + +enum class GyroPerformance : u8 { +    HZ833, +    HZ208, // Default +}; + +enum class AccelerometerPerformance : u8 { +    HZ200, +    HZ100, // Default +}; + +enum class MCUCommand : u8 { +    ConfigureMCU = 0x21, +    ConfigureIR = 0x23, +}; + +enum class MCUSubCommand : u8 { +    SetMCUMode = 0x0, +    SetDeviceMode = 0x1, +    ReadDeviceMode = 0x02, +    WriteDeviceRegisters = 0x4, +}; + +enum class MCUMode : u8 { +    Suspend = 0, +    Standby = 1, +    Ringcon = 3, +    NFC = 4, +    IR = 5, +    MaybeFWUpdate = 6, +}; + +enum class MCURequest : u8 { +    GetMCUStatus = 1, +    GetNFCData = 2, +    GetIRData = 3, +}; + +enum class MCUReport : u8 { +    Empty = 0x00, +    StateReport = 0x01, +    IRData = 0x03, +    BusyInitializing = 0x0b, +    IRStatus = 0x13, +    IRRegisters = 0x1b, +    NFCState = 0x2a, +    NFCReadData = 0x3a, +    EmptyAwaitingCmd = 0xff, +}; + +enum class MCUPacketFlag : u8 { +    MorePacketsRemaining = 0x00, +    LastCommandPacket = 0x08, +}; + +enum class NFCReadCommand : u8 { +    CancelAll = 0x00, +    StartPolling = 0x01, +    StopPolling = 0x02, +    StartWaitingRecieve = 0x04, +    Ntag = 0x06, +    Mifare = 0x0F, +}; + +enum class NFCTagType : u8 { +    AllTags = 0x00, +    Ntag215 = 0x01, +}; + +enum class NFCPages { +    Block0 = 0, +    Block45 = 45, +    Block135 = 135, +    Block231 = 231, +}; + +enum class NFCStatus : u8 { +    LastPackage = 0x04, +    TagLost = 0x07, +}; + +enum class IrsMode : u8 { +    None = 0x02, +    Moment = 0x03, +    Dpd = 0x04, +    Clustering = 0x06, +    ImageTransfer = 0x07, +    Silhouette = 0x08, +    TeraImage = 0x09, +    SilhouetteTeraImage = 0x0A, +}; + +enum class IrsResolution { +    Size320x240, +    Size160x120, +    Size80x60, +    Size40x30, +    Size20x15, +    None, +}; + +enum class IrsResolutionCode : u8 { +    Size320x240 = 0x00, // Full pixel array +    Size160x120 = 0x50, // Sensor Binning [2 X 2] +    Size80x60 = 0x64,   // Sensor Binning [4 x 2] and Skipping [1 x 2] +    Size40x30 = 0x69,   // Sensor Binning [4 x 2] and Skipping [2 x 4] +    Size20x15 = 0x6A,   // Sensor Binning [4 x 2] and Skipping [4 x 4] +}; + +// Size of image divided by 300 +enum class IrsFragments : u8 { +    Size20x15 = 0x00, +    Size40x30 = 0x03, +    Size80x60 = 0x0f, +    Size160x120 = 0x3f, +    Size320x240 = 0xFF, +}; + +enum class IrLeds : u8 { +    BrightAndDim = 0x00, +    Bright = 0x20, +    Dim = 0x10, +    None = 0x30, +}; + +enum class IrExLedFilter : u8 { +    Disabled = 0x00, +    Enabled = 0x03, +}; + +enum class IrImageFlip : u8 { +    Normal = 0x00, +    Inverted = 0x02, +}; + +enum class IrRegistersAddress : u16 { +    UpdateTime = 0x0400, +    FinalizeConfig = 0x0700, +    LedFilter = 0x0e00, +    Leds = 0x1000, +    LedIntensitiyMSB = 0x1100, +    LedIntensitiyLSB = 0x1200, +    ImageFlip = 0x2d00, +    Resolution = 0x2e00, +    DigitalGainLSB = 0x2e01, +    DigitalGainMSB = 0x2f01, +    ExposureLSB = 0x3001, +    ExposureMSB = 0x3101, +    ExposureTime = 0x3201, +    WhitePixelThreshold = 0x4301, +    DenoiseSmoothing = 0x6701, +    DenoiseEdge = 0x6801, +    DenoiseColor = 0x6901, +}; + +enum class DriverResult { +    Success, +    WrongReply, +    Timeout, +    UnsupportedControllerType, +    HandleInUse, +    ErrorReadingData, +    ErrorWritingData, +    NoDeviceDetected, +    InvalidHandle, +    NotSupported, +    Disabled, +    Unknown, +}; + +struct MotionSensorCalibration { +    s16 offset; +    s16 scale; +}; + +struct MotionCalibration { +    std::array<MotionSensorCalibration, 3> accelerometer; +    std::array<MotionSensorCalibration, 3> gyro; +}; + +// Basic motion data containing data from the sensors and a timestamp in microseconds +struct MotionData { +    float gyro_x{}; +    float gyro_y{}; +    float gyro_z{}; +    float accel_x{}; +    float accel_y{}; +    float accel_z{}; +    u64 delta_timestamp{}; +}; + +struct JoyStickAxisCalibration { +    u16 max{1}; +    u16 min{1}; +    u16 center{0}; +}; + +struct JoyStickCalibration { +    JoyStickAxisCalibration x; +    JoyStickAxisCalibration y; +}; + +struct RingCalibration { +    s16 default_value; +    s16 max_value; +    s16 min_value; +}; + +struct Color { +    u32 body; +    u32 buttons; +    u32 left_grip; +    u32 right_grip; +}; + +struct Battery { +    union { +        u8 raw{}; + +        BitField<0, 4, u8> unknown; +        BitField<4, 1, u8> charging; +        BitField<5, 3, u8> status; +    }; +}; + +struct VibrationValue { +    f32 low_amplitude; +    f32 low_frequency; +    f32 high_amplitude; +    f32 high_frequency; +}; + +struct JoyconHandle { +    SDL_hid_device* handle = nullptr; +    u8 packet_counter{}; +}; + +struct MCUConfig { +    MCUCommand command; +    MCUSubCommand sub_command; +    MCUMode mode; +    INSERT_PADDING_BYTES(0x22); +    u8 crc; +}; +static_assert(sizeof(MCUConfig) == 0x26, "MCUConfig is an invalid size"); + +#pragma pack(push, 1) +struct InputReportPassive { +    InputReport report_mode; +    u16 button_input; +    u8 stick_state; +    std::array<u8, 10> unknown_data; +}; +static_assert(sizeof(InputReportPassive) == 0xE, "InputReportPassive is an invalid size"); + +struct InputReportActive { +    InputReport report_mode; +    u8 packet_id; +    Battery battery_status; +    std::array<u8, 3> button_input; +    std::array<u8, 3> left_stick_state; +    std::array<u8, 3> right_stick_state; +    u8 vibration_code; +    std::array<s16, 6 * 2> motion_input; +    INSERT_PADDING_BYTES(0x2); +    s16 ring_input; +}; +static_assert(sizeof(InputReportActive) == 0x29, "InputReportActive is an invalid size"); + +struct InputReportNfcIr { +    InputReport report_mode; +    u8 packet_id; +    Battery battery_status; +    std::array<u8, 3> button_input; +    std::array<u8, 3> left_stick_state; +    std::array<u8, 3> right_stick_state; +    u8 vibration_code; +    std::array<s16, 6 * 2> motion_input; +    INSERT_PADDING_BYTES(0x4); +}; +static_assert(sizeof(InputReportNfcIr) == 0x29, "InputReportNfcIr is an invalid size"); +#pragma pack(pop) + +struct IMUCalibration { +    std::array<s16, 3> accelerometer_offset; +    std::array<s16, 3> accelerometer_scale; +    std::array<s16, 3> gyroscope_offset; +    std::array<s16, 3> gyroscope_scale; +}; +static_assert(sizeof(IMUCalibration) == 0x18, "IMUCalibration is an invalid size"); + +struct NFCReadBlock { +    u8 start; +    u8 end; +}; +static_assert(sizeof(NFCReadBlock) == 0x2, "NFCReadBlock is an invalid size"); + +struct NFCReadBlockCommand { +    u8 block_count{}; +    std::array<NFCReadBlock, 4> blocks{}; +}; +static_assert(sizeof(NFCReadBlockCommand) == 0x9, "NFCReadBlockCommand is an invalid size"); + +struct NFCReadCommandData { +    u8 unknown; +    u8 uuid_length; +    u8 unknown_2; +    std::array<u8, 6> uid; +    NFCTagType tag_type; +    NFCReadBlockCommand read_block; +}; +static_assert(sizeof(NFCReadCommandData) == 0x13, "NFCReadCommandData is an invalid size"); + +struct NFCPollingCommandData { +    u8 enable_mifare; +    u8 unknown_1; +    u8 unknown_2; +    u8 unknown_3; +    u8 unknown_4; +}; +static_assert(sizeof(NFCPollingCommandData) == 0x05, "NFCPollingCommandData is an invalid size"); + +struct NFCRequestState { +    MCUSubCommand sub_command; +    NFCReadCommand command_argument; +    u8 packet_id; +    INSERT_PADDING_BYTES(0x1); +    MCUPacketFlag packet_flag; +    u8 data_length; +    union { +        std::array<u8, 0x1F> raw_data; +        NFCReadCommandData nfc_read; +        NFCPollingCommandData nfc_polling; +    }; +    u8 crc; +}; +static_assert(sizeof(NFCRequestState) == 0x26, "NFCRequestState is an invalid size"); + +struct IrsConfigure { +    MCUCommand command; +    MCUSubCommand sub_command; +    IrsMode irs_mode; +    IrsFragments number_of_fragments; +    u16 mcu_major_version; +    u16 mcu_minor_version; +    INSERT_PADDING_BYTES(0x1D); +    u8 crc; +}; +static_assert(sizeof(IrsConfigure) == 0x26, "IrsConfigure is an invalid size"); + +#pragma pack(push, 1) +struct IrsRegister { +    IrRegistersAddress address; +    u8 value; +}; +static_assert(sizeof(IrsRegister) == 0x3, "IrsRegister is an invalid size"); + +struct IrsWriteRegisters { +    MCUCommand command; +    MCUSubCommand sub_command; +    u8 number_of_registers; +    std::array<IrsRegister, 9> registers; +    INSERT_PADDING_BYTES(0x7); +    u8 crc; +}; +static_assert(sizeof(IrsWriteRegisters) == 0x26, "IrsWriteRegisters is an invalid size"); +#pragma pack(pop) + +struct FirmwareVersion { +    u8 major; +    u8 minor; +}; +static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size"); + +struct DeviceInfo { +    FirmwareVersion firmware; +    MacAddress mac_address; +}; +static_assert(sizeof(DeviceInfo) == 0x8, "DeviceInfo is an invalid size"); + +struct MotionStatus { +    bool is_enabled; +    u64 delta_time; +    GyroSensitivity gyro_sensitivity; +    AccelerometerSensitivity accelerometer_sensitivity; +}; + +struct RingStatus { +    bool is_enabled; +    s16 default_value; +    s16 max_value; +    s16 min_value; +}; + +struct JoyconCallbacks { +    std::function<void(Battery)> on_battery_data; +    std::function<void(Color)> on_color_data; +    std::function<void(int, bool)> on_button_data; +    std::function<void(int, f32)> on_stick_data; +    std::function<void(int, const MotionData&)> on_motion_data; +    std::function<void(f32)> on_ring_data; +    std::function<void(const std::vector<u8>&)> on_amiibo_data; +    std::function<void(const std::vector<u8>&, IrsResolution)> on_camera_data; +}; + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/nfc.cpp b/src/input_common/helpers/joycon_protocol/nfc.cpp new file mode 100644 index 000000000..5c0f71722 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/nfc.cpp @@ -0,0 +1,400 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <thread> +#include "common/logging/log.h" +#include "input_common/helpers/joycon_protocol/nfc.h" + +namespace InputCommon::Joycon { + +NfcProtocol::NfcProtocol(std::shared_ptr<JoyconHandle> handle) +    : JoyconCommonProtocol(std::move(handle)) {} + +DriverResult NfcProtocol::EnableNfc() { +    LOG_INFO(Input, "Enable NFC"); +    ScopedSetBlocking sb(this); +    DriverResult result{DriverResult::Success}; + +    if (result == DriverResult::Success) { +        result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ); +    } +    if (result == DriverResult::Success) { +        result = EnableMCU(true); +    } +    if (result == DriverResult::Success) { +        result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby); +    } +    if (result == DriverResult::Success) { +        const MCUConfig config{ +            .command = MCUCommand::ConfigureMCU, +            .sub_command = MCUSubCommand::SetMCUMode, +            .mode = MCUMode::NFC, +            .crc = {}, +        }; + +        result = ConfigureMCU(config); +    } + +    return result; +} + +DriverResult NfcProtocol::DisableNfc() { +    LOG_DEBUG(Input, "Disable NFC"); +    ScopedSetBlocking sb(this); +    DriverResult result{DriverResult::Success}; + +    if (result == DriverResult::Success) { +        result = EnableMCU(false); +    } + +    is_enabled = false; + +    return result; +} + +DriverResult NfcProtocol::StartNFCPollingMode() { +    LOG_DEBUG(Input, "Start NFC pooling Mode"); +    ScopedSetBlocking sb(this); +    DriverResult result{DriverResult::Success}; +    TagFoundData tag_data{}; + +    if (result == DriverResult::Success) { +        result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::NFC); +    } +    if (result == DriverResult::Success) { +        result = WaitUntilNfcIsReady(); +    } +    if (result == DriverResult::Success) { +        is_enabled = true; +    } + +    return result; +} + +DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) { +    LOG_DEBUG(Input, "Start NFC pooling Mode"); +    ScopedSetBlocking sb(this); +    DriverResult result{DriverResult::Success}; +    TagFoundData tag_data{}; + +    if (result == DriverResult::Success) { +        result = StartPolling(tag_data); +    } +    if (result == DriverResult::Success) { +        result = ReadTag(tag_data); +    } +    if (result == DriverResult::Success) { +        result = WaitUntilNfcIsReady(); +    } +    if (result == DriverResult::Success) { +        result = StartPolling(tag_data); +    } +    if (result == DriverResult::Success) { +        result = GetAmiiboData(data); +    } + +    return result; +} + +bool NfcProtocol::HasAmiibo() { +    ScopedSetBlocking sb(this); +    DriverResult result{DriverResult::Success}; +    TagFoundData tag_data{}; + +    if (result == DriverResult::Success) { +        result = StartPolling(tag_data); +    } + +    return result == DriverResult::Success; +} + +DriverResult NfcProtocol::WaitUntilNfcIsReady() { +    constexpr std::size_t timeout_limit = 10; +    std::vector<u8> output; +    std::size_t tries = 0; + +    do { +        auto result = SendStartWaitingRecieveRequest(output); + +        if (result != DriverResult::Success) { +            return result; +        } +        if (tries++ > timeout_limit) { +            return DriverResult::Timeout; +        } +    } while (output[49] != 0x2a || (output[51] << 8) + output[50] != 0x0500 || output[55] != 0x31 || +             output[56] != 0x00); + +    return DriverResult::Success; +} + +DriverResult NfcProtocol::StartPolling(TagFoundData& data) { +    LOG_DEBUG(Input, "Start Polling for tag"); +    constexpr std::size_t timeout_limit = 7; +    std::vector<u8> output; +    std::size_t tries = 0; + +    do { +        const auto result = SendStartPollingRequest(output); +        if (result != DriverResult::Success) { +            return result; +        } +        if (tries++ > timeout_limit) { +            return DriverResult::Timeout; +        } +    } while (output[49] != 0x2a || (output[51] << 8) + output[50] != 0x0500 || output[56] != 0x09); + +    data.type = output[62]; +    data.uuid.resize(output[64]); +    memcpy(data.uuid.data(), output.data() + 65, data.uuid.size()); + +    return DriverResult::Success; +} + +DriverResult NfcProtocol::ReadTag(const TagFoundData& data) { +    constexpr std::size_t timeout_limit = 10; +    std::vector<u8> output; +    std::size_t tries = 0; + +    std::string uuid_string; +    for (auto& content : data.uuid) { +        uuid_string += fmt::format(" {:02x}", content); +    } + +    LOG_INFO(Input, "Tag detected, type={}, uuid={}", data.type, uuid_string); + +    tries = 0; +    NFCPages ntag_pages = NFCPages::Block0; +    // Read Tag data +    while (true) { +        auto result = SendReadAmiiboRequest(output, ntag_pages); +        const auto mcu_report = static_cast<MCUReport>(output[49]); +        const auto nfc_status = static_cast<NFCStatus>(output[56]); + +        if (result != DriverResult::Success) { +            return result; +        } + +        if ((mcu_report == MCUReport::NFCReadData || mcu_report == MCUReport::NFCState) && +            nfc_status == NFCStatus::TagLost) { +            return DriverResult::ErrorReadingData; +        } + +        if (mcu_report == MCUReport::NFCReadData && output[51] == 0x07 && output[52] == 0x01) { +            if (data.type != 2) { +                continue; +            } +            switch (output[74]) { +            case 0: +                ntag_pages = NFCPages::Block135; +                break; +            case 3: +                ntag_pages = NFCPages::Block45; +                break; +            case 4: +                ntag_pages = NFCPages::Block231; +                break; +            default: +                return DriverResult::ErrorReadingData; +            } +            continue; +        } + +        if (mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) { +            // finished +            SendStopPollingRequest(output); +            return DriverResult::Success; +        } + +        // Ignore other state reports +        if (mcu_report == MCUReport::NFCState) { +            continue; +        } + +        if (tries++ > timeout_limit) { +            return DriverResult::Timeout; +        } +    } + +    return DriverResult::Success; +} + +DriverResult NfcProtocol::GetAmiiboData(std::vector<u8>& ntag_data) { +    constexpr std::size_t timeout_limit = 10; +    std::vector<u8> output; +    std::size_t tries = 0; + +    NFCPages ntag_pages = NFCPages::Block135; +    std::size_t ntag_buffer_pos = 0; +    // Read Tag data +    while (true) { +        auto result = SendReadAmiiboRequest(output, ntag_pages); +        const auto mcu_report = static_cast<MCUReport>(output[49]); +        const auto nfc_status = static_cast<NFCStatus>(output[56]); + +        if (result != DriverResult::Success) { +            return result; +        } + +        if ((mcu_report == MCUReport::NFCReadData || mcu_report == MCUReport::NFCState) && +            nfc_status == NFCStatus::TagLost) { +            return DriverResult::ErrorReadingData; +        } + +        if (mcu_report == MCUReport::NFCReadData && output[51] == 0x07) { +            std::size_t payload_size = (output[54] << 8 | output[55]) & 0x7FF; +            if (output[52] == 0x01) { +                memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 116, payload_size - 60); +                ntag_buffer_pos += payload_size - 60; +            } else { +                memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 56, payload_size); +            } +            continue; +        } + +        if (mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) { +            LOG_INFO(Input, "Finished reading amiibo"); +            return DriverResult::Success; +        } + +        // Ignore other state reports +        if (mcu_report == MCUReport::NFCState) { +            continue; +        } + +        if (tries++ > timeout_limit) { +            return DriverResult::Timeout; +        } +    } + +    return DriverResult::Success; +} + +DriverResult NfcProtocol::SendStartPollingRequest(std::vector<u8>& output) { +    NFCRequestState request{ +        .sub_command = MCUSubCommand::ReadDeviceMode, +        .command_argument = NFCReadCommand::StartPolling, +        .packet_id = 0x0, +        .packet_flag = MCUPacketFlag::LastCommandPacket, +        .data_length = sizeof(NFCPollingCommandData), +        .nfc_polling = +            { +                .enable_mifare = 0x01, +                .unknown_1 = 0x00, +                .unknown_2 = 0x00, +                .unknown_3 = 0x2c, +                .unknown_4 = 0x01, +            }, +        .crc = {}, +    }; + +    std::array<u8, sizeof(NFCRequestState)> request_data{}; +    memcpy(request_data.data(), &request, sizeof(NFCRequestState)); +    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); +    return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output); +} + +DriverResult NfcProtocol::SendStopPollingRequest(std::vector<u8>& output) { +    NFCRequestState request{ +        .sub_command = MCUSubCommand::ReadDeviceMode, +        .command_argument = NFCReadCommand::StopPolling, +        .packet_id = 0x0, +        .packet_flag = MCUPacketFlag::LastCommandPacket, +        .data_length = 0, +        .raw_data = {}, +        .crc = {}, +    }; + +    std::array<u8, sizeof(NFCRequestState)> request_data{}; +    memcpy(request_data.data(), &request, sizeof(NFCRequestState)); +    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); +    return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output); +} + +DriverResult NfcProtocol::SendStartWaitingRecieveRequest(std::vector<u8>& output) { +    NFCRequestState request{ +        .sub_command = MCUSubCommand::ReadDeviceMode, +        .command_argument = NFCReadCommand::StartWaitingRecieve, +        .packet_id = 0x0, +        .packet_flag = MCUPacketFlag::LastCommandPacket, +        .data_length = 0, +        .raw_data = {}, +        .crc = {}, +    }; + +    std::vector<u8> request_data(sizeof(NFCRequestState)); +    memcpy(request_data.data(), &request, sizeof(NFCRequestState)); +    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); +    return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output); +} + +DriverResult NfcProtocol::SendReadAmiiboRequest(std::vector<u8>& output, NFCPages ntag_pages) { +    NFCRequestState request{ +        .sub_command = MCUSubCommand::ReadDeviceMode, +        .command_argument = NFCReadCommand::Ntag, +        .packet_id = 0x0, +        .packet_flag = MCUPacketFlag::LastCommandPacket, +        .data_length = sizeof(NFCReadCommandData), +        .nfc_read = +            { +                .unknown = 0xd0, +                .uuid_length = 0x07, +                .unknown_2 = 0x00, +                .uid = {}, +                .tag_type = NFCTagType::AllTags, +                .read_block = GetReadBlockCommand(ntag_pages), +            }, +        .crc = {}, +    }; + +    std::array<u8, sizeof(NFCRequestState)> request_data{}; +    memcpy(request_data.data(), &request, sizeof(NFCRequestState)); +    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); +    return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output); +} + +NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(NFCPages pages) const { +    switch (pages) { +    case NFCPages::Block0: +        return { +            .block_count = 1, +        }; +    case NFCPages::Block45: +        return { +            .block_count = 1, +            .blocks = +                { +                    NFCReadBlock{0x00, 0x2C}, +                }, +        }; +    case NFCPages::Block135: +        return { +            .block_count = 3, +            .blocks = +                { +                    NFCReadBlock{0x00, 0x3b}, +                    {0x3c, 0x77}, +                    {0x78, 0x86}, +                }, +        }; +    case NFCPages::Block231: +        return { +            .block_count = 4, +            .blocks = +                { +                    NFCReadBlock{0x00, 0x3b}, +                    {0x3c, 0x77}, +                    {0x78, 0x83}, +                    {0xb4, 0xe6}, +                }, +        }; +    default: +        return {}; +    }; +} + +bool NfcProtocol::IsEnabled() const { +    return is_enabled; +} + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/nfc.h b/src/input_common/helpers/joycon_protocol/nfc.h new file mode 100644 index 000000000..e63665aa9 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/nfc.h @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse +// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c +// https://github.com/CTCaer/jc_toolkit +// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering + +#pragma once + +#include <vector> + +#include "input_common/helpers/joycon_protocol/common_protocol.h" +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon::Joycon { + +class NfcProtocol final : private JoyconCommonProtocol { +public: +    explicit NfcProtocol(std::shared_ptr<JoyconHandle> handle); + +    DriverResult EnableNfc(); + +    DriverResult DisableNfc(); + +    DriverResult StartNFCPollingMode(); + +    DriverResult ScanAmiibo(std::vector<u8>& data); + +    bool HasAmiibo(); + +    bool IsEnabled() const; + +private: +    struct TagFoundData { +        u8 type; +        std::vector<u8> uuid; +    }; + +    DriverResult WaitUntilNfcIsReady(); + +    DriverResult StartPolling(TagFoundData& data); + +    DriverResult ReadTag(const TagFoundData& data); + +    DriverResult GetAmiiboData(std::vector<u8>& data); + +    DriverResult SendStartPollingRequest(std::vector<u8>& output); + +    DriverResult SendStopPollingRequest(std::vector<u8>& output); + +    DriverResult SendStartWaitingRecieveRequest(std::vector<u8>& output); + +    DriverResult SendReadAmiiboRequest(std::vector<u8>& output, NFCPages ntag_pages); + +    NFCReadBlockCommand GetReadBlockCommand(NFCPages pages) const; + +    bool is_enabled{}; +}; + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/poller.cpp b/src/input_common/helpers/joycon_protocol/poller.cpp new file mode 100644 index 000000000..7f8e093fa --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/poller.cpp @@ -0,0 +1,341 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "input_common/helpers/joycon_protocol/poller.h" + +namespace InputCommon::Joycon { + +JoyconPoller::JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_, +                           JoyStickCalibration right_stick_calibration_, +                           MotionCalibration motion_calibration_) +    : device_type{device_type_}, left_stick_calibration{left_stick_calibration_}, +      right_stick_calibration{right_stick_calibration_}, motion_calibration{motion_calibration_} {} + +void JoyconPoller::SetCallbacks(const Joycon::JoyconCallbacks& callbacks_) { +    callbacks = std::move(callbacks_); +} + +void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status, +                                  const RingStatus& ring_status) { +    InputReportActive data{}; +    memcpy(&data, buffer.data(), sizeof(InputReportActive)); + +    switch (device_type) { +    case Joycon::ControllerType::Left: +        UpdateActiveLeftPadInput(data, motion_status); +        break; +    case Joycon::ControllerType::Right: +        UpdateActiveRightPadInput(data, motion_status); +        break; +    case Joycon::ControllerType::Pro: +        UpdateActiveProPadInput(data, motion_status); +        break; +    case Joycon::ControllerType::Grip: +    case Joycon::ControllerType::Dual: +    case Joycon::ControllerType::None: +        break; +    } + +    if (ring_status.is_enabled) { +        UpdateRing(data.ring_input, ring_status); +    } + +    callbacks.on_battery_data(data.battery_status); +} + +void JoyconPoller::ReadPassiveMode(std::span<u8> buffer) { +    InputReportPassive data{}; +    memcpy(&data, buffer.data(), sizeof(InputReportPassive)); + +    switch (device_type) { +    case Joycon::ControllerType::Left: +        UpdatePasiveLeftPadInput(data); +        break; +    case Joycon::ControllerType::Right: +        UpdatePasiveRightPadInput(data); +        break; +    case Joycon::ControllerType::Pro: +        UpdatePasiveProPadInput(data); +        break; +    case Joycon::ControllerType::Grip: +    case Joycon::ControllerType::Dual: +    case Joycon::ControllerType::None: +        break; +    } +} + +void JoyconPoller::ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status) { +    // This mode is compatible with the active mode +    ReadActiveMode(buffer, motion_status, {}); +} + +void JoyconPoller::UpdateColor(const Color& color) { +    callbacks.on_color_data(color); +} + +void JoyconPoller::UpdateAmiibo(const std::vector<u8>& amiibo_data) { +    callbacks.on_amiibo_data(amiibo_data); +} + +void JoyconPoller::UpdateCamera(const std::vector<u8>& camera_data, IrsResolution format) { +    callbacks.on_camera_data(camera_data, format); +} + +void JoyconPoller::UpdateRing(s16 value, const RingStatus& ring_status) { +    float normalized_value = static_cast<float>(value - ring_status.default_value); +    if (normalized_value > 0) { +        normalized_value = normalized_value / +                           static_cast<float>(ring_status.max_value - ring_status.default_value); +    } +    if (normalized_value < 0) { +        normalized_value = normalized_value / +                           static_cast<float>(ring_status.default_value - ring_status.min_value); +    } +    callbacks.on_ring_data(normalized_value); +} + +void JoyconPoller::UpdateActiveLeftPadInput(const InputReportActive& input, +                                            const MotionStatus& motion_status) { +    static constexpr std::array<Joycon::PadButton, 11> left_buttons{ +        Joycon::PadButton::Down,    Joycon::PadButton::Up,     Joycon::PadButton::Right, +        Joycon::PadButton::Left,    Joycon::PadButton::LeftSL, Joycon::PadButton::LeftSR, +        Joycon::PadButton::L,       Joycon::PadButton::ZL,     Joycon::PadButton::Minus, +        Joycon::PadButton::Capture, Joycon::PadButton::StickL, +    }; + +    const u32 raw_button = +        static_cast<u32>(input.button_input[2] | ((input.button_input[1] & 0b00101001) << 16)); +    for (std::size_t i = 0; i < left_buttons.size(); ++i) { +        const bool button_status = (raw_button & static_cast<u32>(left_buttons[i])) != 0; +        const int button = static_cast<int>(left_buttons[i]); +        callbacks.on_button_data(button, button_status); +    } + +    const u16 raw_left_axis_x = +        static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8)); +    const u16 raw_left_axis_y = +        static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4)); +    const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x); +    const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y); +    callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x); +    callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y); + +    if (motion_status.is_enabled) { +        auto left_motion = GetMotionInput(input, motion_status); +        // Rotate motion axis to the correct direction +        left_motion.accel_y = -left_motion.accel_y; +        left_motion.accel_z = -left_motion.accel_z; +        left_motion.gyro_x = -left_motion.gyro_x; +        callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), left_motion); +    } +} + +void JoyconPoller::UpdateActiveRightPadInput(const InputReportActive& input, +                                             const MotionStatus& motion_status) { +    static constexpr std::array<Joycon::PadButton, 11> right_buttons{ +        Joycon::PadButton::Y,    Joycon::PadButton::X,       Joycon::PadButton::B, +        Joycon::PadButton::A,    Joycon::PadButton::RightSL, Joycon::PadButton::RightSR, +        Joycon::PadButton::R,    Joycon::PadButton::ZR,      Joycon::PadButton::Plus, +        Joycon::PadButton::Home, Joycon::PadButton::StickR, +    }; + +    const u32 raw_button = +        static_cast<u32>((input.button_input[0] << 8) | (input.button_input[1] << 16)); +    for (std::size_t i = 0; i < right_buttons.size(); ++i) { +        const bool button_status = (raw_button & static_cast<u32>(right_buttons[i])) != 0; +        const int button = static_cast<int>(right_buttons[i]); +        callbacks.on_button_data(button, button_status); +    } + +    const u16 raw_right_axis_x = +        static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8)); +    const u16 raw_right_axis_y = +        static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4)); +    const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x); +    const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y); +    callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x); +    callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y); + +    if (motion_status.is_enabled) { +        auto right_motion = GetMotionInput(input, motion_status); +        // Rotate motion axis to the correct direction +        right_motion.accel_x = -right_motion.accel_x; +        right_motion.accel_y = -right_motion.accel_y; +        right_motion.gyro_z = -right_motion.gyro_z; +        callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), right_motion); +    } +} + +void JoyconPoller::UpdateActiveProPadInput(const InputReportActive& input, +                                           const MotionStatus& motion_status) { +    static constexpr std::array<Joycon::PadButton, 18> pro_buttons{ +        Joycon::PadButton::Down,  Joycon::PadButton::Up,      Joycon::PadButton::Right, +        Joycon::PadButton::Left,  Joycon::PadButton::L,       Joycon::PadButton::ZL, +        Joycon::PadButton::Minus, Joycon::PadButton::Capture, Joycon::PadButton::Y, +        Joycon::PadButton::X,     Joycon::PadButton::B,       Joycon::PadButton::A, +        Joycon::PadButton::R,     Joycon::PadButton::ZR,      Joycon::PadButton::Plus, +        Joycon::PadButton::Home,  Joycon::PadButton::StickL,  Joycon::PadButton::StickR, +    }; + +    const u32 raw_button = static_cast<u32>(input.button_input[2] | (input.button_input[0] << 8) | +                                            (input.button_input[1] << 16)); +    for (std::size_t i = 0; i < pro_buttons.size(); ++i) { +        const bool button_status = (raw_button & static_cast<u32>(pro_buttons[i])) != 0; +        const int button = static_cast<int>(pro_buttons[i]); +        callbacks.on_button_data(button, button_status); +    } + +    const u16 raw_left_axis_x = +        static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8)); +    const u16 raw_left_axis_y = +        static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4)); +    const u16 raw_right_axis_x = +        static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8)); +    const u16 raw_right_axis_y = +        static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4)); + +    const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x); +    const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y); +    const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x); +    const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y); +    callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x); +    callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y); +    callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x); +    callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y); + +    if (motion_status.is_enabled) { +        auto pro_motion = GetMotionInput(input, motion_status); +        pro_motion.gyro_x = -pro_motion.gyro_x; +        pro_motion.accel_y = -pro_motion.accel_y; +        pro_motion.accel_z = -pro_motion.accel_z; +        callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), pro_motion); +        callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), pro_motion); +    } +} + +void JoyconPoller::UpdatePasiveLeftPadInput(const InputReportPassive& input) { +    static constexpr std::array<Joycon::PasivePadButton, 11> left_buttons{ +        Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X, +        Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y, +        Joycon::PasivePadButton::SL,     Joycon::PasivePadButton::SR, +        Joycon::PasivePadButton::L_R,    Joycon::PasivePadButton::ZL_ZR, +        Joycon::PasivePadButton::Minus,  Joycon::PasivePadButton::Capture, +        Joycon::PasivePadButton::StickL, +    }; + +    for (auto left_button : left_buttons) { +        const bool button_status = (input.button_input & static_cast<u32>(left_button)) != 0; +        const int button = static_cast<int>(left_button); +        callbacks.on_button_data(button, button_status); +    } +} + +void JoyconPoller::UpdatePasiveRightPadInput(const InputReportPassive& input) { +    static constexpr std::array<Joycon::PasivePadButton, 11> right_buttons{ +        Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X, +        Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y, +        Joycon::PasivePadButton::SL,     Joycon::PasivePadButton::SR, +        Joycon::PasivePadButton::L_R,    Joycon::PasivePadButton::ZL_ZR, +        Joycon::PasivePadButton::Plus,   Joycon::PasivePadButton::Home, +        Joycon::PasivePadButton::StickR, +    }; + +    for (auto right_button : right_buttons) { +        const bool button_status = (input.button_input & static_cast<u32>(right_button)) != 0; +        const int button = static_cast<int>(right_button); +        callbacks.on_button_data(button, button_status); +    } +} + +void JoyconPoller::UpdatePasiveProPadInput(const InputReportPassive& input) { +    static constexpr std::array<Joycon::PasivePadButton, 14> pro_buttons{ +        Joycon::PasivePadButton::Down_A,  Joycon::PasivePadButton::Right_X, +        Joycon::PasivePadButton::Left_B,  Joycon::PasivePadButton::Up_Y, +        Joycon::PasivePadButton::SL,      Joycon::PasivePadButton::SR, +        Joycon::PasivePadButton::L_R,     Joycon::PasivePadButton::ZL_ZR, +        Joycon::PasivePadButton::Minus,   Joycon::PasivePadButton::Plus, +        Joycon::PasivePadButton::Capture, Joycon::PasivePadButton::Home, +        Joycon::PasivePadButton::StickL,  Joycon::PasivePadButton::StickR, +    }; + +    for (auto pro_button : pro_buttons) { +        const bool button_status = (input.button_input & static_cast<u32>(pro_button)) != 0; +        const int button = static_cast<int>(pro_button); +        callbacks.on_button_data(button, button_status); +    } +} + +f32 JoyconPoller::GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const { +    const f32 value = static_cast<f32>(raw_value - calibration.center); +    if (value > 0.0f) { +        return value / calibration.max; +    } +    return value / calibration.min; +} + +f32 JoyconPoller::GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal, +                                        AccelerometerSensitivity sensitivity) const { +    const f32 value = raw * (1.0f / (cal.scale - cal.offset)) * 4; +    switch (sensitivity) { +    case Joycon::AccelerometerSensitivity::G2: +        return value / 4.0f; +    case Joycon::AccelerometerSensitivity::G4: +        return value / 2.0f; +    case Joycon::AccelerometerSensitivity::G8: +        return value; +    case Joycon::AccelerometerSensitivity::G16: +        return value * 2.0f; +    } +    return value; +} + +f32 JoyconPoller::GetGyroValue(s16 raw, const MotionSensorCalibration& cal, +                               GyroSensitivity sensitivity) const { +    const f32 value = (raw - cal.offset) * (936.0f / (cal.scale - cal.offset)) / 360.0f; +    switch (sensitivity) { +    case Joycon::GyroSensitivity::DPS250: +        return value / 8.0f; +    case Joycon::GyroSensitivity::DPS500: +        return value / 4.0f; +    case Joycon::GyroSensitivity::DPS1000: +        return value / 2.0f; +    case Joycon::GyroSensitivity::DPS2000: +        return value; +    } +    return value; +} + +s16 JoyconPoller::GetRawIMUValues(std::size_t sensor, size_t axis, +                                  const InputReportActive& input) const { +    return input.motion_input[(sensor * 3) + axis]; +} + +MotionData JoyconPoller::GetMotionInput(const InputReportActive& input, +                                        const MotionStatus& motion_status) const { +    MotionData motion{}; +    const auto& accel_cal = motion_calibration.accelerometer; +    const auto& gyro_cal = motion_calibration.gyro; +    const s16 raw_accel_x = input.motion_input[1]; +    const s16 raw_accel_y = input.motion_input[0]; +    const s16 raw_accel_z = input.motion_input[2]; +    const s16 raw_gyro_x = input.motion_input[4]; +    const s16 raw_gyro_y = input.motion_input[3]; +    const s16 raw_gyro_z = input.motion_input[5]; + +    motion.delta_timestamp = motion_status.delta_time; +    motion.accel_x = +        GetAccelerometerValue(raw_accel_x, accel_cal[1], motion_status.accelerometer_sensitivity); +    motion.accel_y = +        GetAccelerometerValue(raw_accel_y, accel_cal[0], motion_status.accelerometer_sensitivity); +    motion.accel_z = +        GetAccelerometerValue(raw_accel_z, accel_cal[2], motion_status.accelerometer_sensitivity); +    motion.gyro_x = GetGyroValue(raw_gyro_x, gyro_cal[1], motion_status.gyro_sensitivity); +    motion.gyro_y = GetGyroValue(raw_gyro_y, gyro_cal[0], motion_status.gyro_sensitivity); +    motion.gyro_z = GetGyroValue(raw_gyro_z, gyro_cal[2], motion_status.gyro_sensitivity); + +    // TODO(German77): Return all three samples data +    return motion; +} + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/poller.h b/src/input_common/helpers/joycon_protocol/poller.h new file mode 100644 index 000000000..354d41dad --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/poller.h @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse +// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c +// https://github.com/CTCaer/jc_toolkit +// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering + +#pragma once + +#include <functional> +#include <span> + +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon::Joycon { + +// Handles input packages and triggers the corresponding input events +class JoyconPoller { +public: +    JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_, +                 JoyStickCalibration right_stick_calibration_, +                 MotionCalibration motion_calibration_); + +    void SetCallbacks(const Joycon::JoyconCallbacks& callbacks_); + +    /// Handles data from passive packages +    void ReadPassiveMode(std::span<u8> buffer); + +    /// Handles data from active packages +    void ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status, +                        const RingStatus& ring_status); + +    /// Handles data from nfc or ir packages +    void ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status); + +    void UpdateColor(const Color& color); +    void UpdateRing(s16 value, const RingStatus& ring_status); +    void UpdateAmiibo(const std::vector<u8>& amiibo_data); +    void UpdateCamera(const std::vector<u8>& amiibo_data, IrsResolution format); + +private: +    void UpdateActiveLeftPadInput(const InputReportActive& input, +                                  const MotionStatus& motion_status); +    void UpdateActiveRightPadInput(const InputReportActive& input, +                                   const MotionStatus& motion_status); +    void UpdateActiveProPadInput(const InputReportActive& input, const MotionStatus& motion_status); + +    void UpdatePasiveLeftPadInput(const InputReportPassive& buffer); +    void UpdatePasiveRightPadInput(const InputReportPassive& buffer); +    void UpdatePasiveProPadInput(const InputReportPassive& buffer); + +    /// Returns a calibrated joystick axis from raw axis data +    f32 GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const; + +    /// Returns a calibrated accelerometer axis from raw motion data +    f32 GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal, +                              AccelerometerSensitivity sensitivity) const; + +    /// Returns a calibrated gyro axis from raw motion data +    f32 GetGyroValue(s16 raw_value, const MotionSensorCalibration& cal, +                     GyroSensitivity sensitivity) const; + +    /// Returns a raw motion value from a buffer +    s16 GetRawIMUValues(size_t sensor, size_t axis, const InputReportActive& input) const; + +    /// Returns motion data from a buffer +    MotionData GetMotionInput(const InputReportActive& input, +                              const MotionStatus& motion_status) const; + +    ControllerType device_type{}; + +    // Device calibration +    JoyStickCalibration left_stick_calibration{}; +    JoyStickCalibration right_stick_calibration{}; +    MotionCalibration motion_calibration{}; + +    Joycon::JoyconCallbacks callbacks{}; +}; + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/ringcon.cpp b/src/input_common/helpers/joycon_protocol/ringcon.cpp new file mode 100644 index 000000000..12f81309e --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/ringcon.cpp @@ -0,0 +1,117 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "input_common/helpers/joycon_protocol/ringcon.h" + +namespace InputCommon::Joycon { + +RingConProtocol::RingConProtocol(std::shared_ptr<JoyconHandle> handle) +    : JoyconCommonProtocol(std::move(handle)) {} + +DriverResult RingConProtocol::EnableRingCon() { +    LOG_DEBUG(Input, "Enable Ringcon"); +    ScopedSetBlocking sb(this); +    DriverResult result{DriverResult::Success}; + +    if (result == DriverResult::Success) { +        result = SetReportMode(ReportMode::STANDARD_FULL_60HZ); +    } +    if (result == DriverResult::Success) { +        result = EnableMCU(true); +    } +    if (result == DriverResult::Success) { +        const MCUConfig config{ +            .command = MCUCommand::ConfigureMCU, +            .sub_command = MCUSubCommand::SetDeviceMode, +            .mode = MCUMode::Standby, +            .crc = {}, +        }; +        result = ConfigureMCU(config); +    } + +    return result; +} + +DriverResult RingConProtocol::DisableRingCon() { +    LOG_DEBUG(Input, "Disable RingCon"); +    ScopedSetBlocking sb(this); +    DriverResult result{DriverResult::Success}; + +    if (result == DriverResult::Success) { +        result = EnableMCU(false); +    } + +    is_enabled = false; + +    return result; +} + +DriverResult RingConProtocol::StartRingconPolling() { +    LOG_DEBUG(Input, "Enable Ringcon"); +    ScopedSetBlocking sb(this); +    DriverResult result{DriverResult::Success}; +    bool is_connected = false; + +    if (result == DriverResult::Success) { +        result = IsRingConnected(is_connected); +    } +    if (result == DriverResult::Success && is_connected) { +        LOG_INFO(Input, "Ringcon detected"); +        result = ConfigureRing(); +    } +    if (result == DriverResult::Success) { +        is_enabled = true; +    } + +    return result; +} + +DriverResult RingConProtocol::IsRingConnected(bool& is_connected) { +    LOG_DEBUG(Input, "IsRingConnected"); +    constexpr std::size_t max_tries = 28; +    constexpr u8 ring_controller_id = 0x20; +    std::vector<u8> output; +    std::size_t tries = 0; +    is_connected = false; + +    do { +        std::array<u8, 1> empty_data{}; +        const auto result = SendSubCommand(SubCommand::UNKNOWN_RINGCON, empty_data, output); + +        if (result != DriverResult::Success) { +            return result; +        } + +        if (tries++ >= max_tries) { +            return DriverResult::NoDeviceDetected; +        } +    } while (output[16] != ring_controller_id); + +    is_connected = true; +    return DriverResult::Success; +} + +DriverResult RingConProtocol::ConfigureRing() { +    LOG_DEBUG(Input, "ConfigureRing"); + +    static constexpr std::array<u8, 37> ring_config{ +        0x06, 0x03, 0x25, 0x06, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x16, 0xED, 0x34, 0x36, +        0x00, 0x00, 0x00, 0x0A, 0x64, 0x0B, 0xE6, 0xA9, 0x22, 0x00, 0x00, 0x04, 0x00, +        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xA8, 0xE1, 0x34, 0x36}; + +    const DriverResult result = SendSubCommand(SubCommand::UNKNOWN_RINGCON3, ring_config); + +    if (result != DriverResult::Success) { +        return result; +    } + +    static constexpr std::array<u8, 4> ringcon_data{0x04, 0x01, 0x01, 0x02}; +    return SendSubCommand(SubCommand::UNKNOWN_RINGCON2, ringcon_data); +} + +bool RingConProtocol::IsEnabled() const { +    return is_enabled; +} + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/ringcon.h b/src/input_common/helpers/joycon_protocol/ringcon.h new file mode 100644 index 000000000..6e858f3fc --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/ringcon.h @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse +// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c +// https://github.com/CTCaer/jc_toolkit +// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering + +#pragma once + +#include <vector> + +#include "input_common/helpers/joycon_protocol/common_protocol.h" +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon::Joycon { + +class RingConProtocol final : private JoyconCommonProtocol { +public: +    explicit RingConProtocol(std::shared_ptr<JoyconHandle> handle); + +    DriverResult EnableRingCon(); + +    DriverResult DisableRingCon(); + +    DriverResult StartRingconPolling(); + +    bool IsEnabled() const; + +private: +    DriverResult IsRingConnected(bool& is_connected); + +    DriverResult ConfigureRing(); + +    bool is_enabled{}; +}; + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/rumble.cpp b/src/input_common/helpers/joycon_protocol/rumble.cpp new file mode 100644 index 000000000..63b60c946 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/rumble.cpp @@ -0,0 +1,299 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <algorithm> +#include <cmath> + +#include "common/logging/log.h" +#include "input_common/helpers/joycon_protocol/rumble.h" + +namespace InputCommon::Joycon { + +RumbleProtocol::RumbleProtocol(std::shared_ptr<JoyconHandle> handle) +    : JoyconCommonProtocol(std::move(handle)) {} + +DriverResult RumbleProtocol::EnableRumble(bool is_enabled) { +    LOG_DEBUG(Input, "Enable Rumble"); +    ScopedSetBlocking sb(this); +    const std::array<u8, 1> buffer{static_cast<u8>(is_enabled ? 1 : 0)}; +    return SendSubCommand(SubCommand::ENABLE_VIBRATION, buffer); +} + +DriverResult RumbleProtocol::SendVibration(const VibrationValue& vibration) { +    std::array<u8, sizeof(DefaultVibrationBuffer)> buffer{}; + +    if (vibration.high_amplitude <= 0.0f && vibration.low_amplitude <= 0.0f) { +        return SendVibrationReport(DefaultVibrationBuffer); +    } + +    // Protect joycons from damage from strong vibrations +    const f32 clamp_amplitude = +        1.0f / std::max(1.0f, vibration.high_amplitude + vibration.low_amplitude); + +    const u16 encoded_high_frequency = EncodeHighFrequency(vibration.high_frequency); +    const u8 encoded_high_amplitude = +        EncodeHighAmplitude(vibration.high_amplitude * clamp_amplitude); +    const u8 encoded_low_frequency = EncodeLowFrequency(vibration.low_frequency); +    const u16 encoded_low_amplitude = EncodeLowAmplitude(vibration.low_amplitude * clamp_amplitude); + +    buffer[0] = static_cast<u8>(encoded_high_frequency & 0xFF); +    buffer[1] = static_cast<u8>(encoded_high_amplitude | ((encoded_high_frequency >> 8) & 0x01)); +    buffer[2] = static_cast<u8>(encoded_low_frequency | ((encoded_low_amplitude >> 8) & 0x80)); +    buffer[3] = static_cast<u8>(encoded_low_amplitude & 0xFF); + +    // Duplicate rumble for now +    buffer[4] = buffer[0]; +    buffer[5] = buffer[1]; +    buffer[6] = buffer[2]; +    buffer[7] = buffer[3]; + +    return SendVibrationReport(buffer); +} + +u16 RumbleProtocol::EncodeHighFrequency(f32 frequency) const { +    const u8 new_frequency = +        static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f)); +    return static_cast<u16>((new_frequency - 0x60) * 4); +} + +u8 RumbleProtocol::EncodeLowFrequency(f32 frequency) const { +    const u8 new_frequency = +        static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f)); +    return static_cast<u8>(new_frequency - 0x40); +} + +u8 RumbleProtocol::EncodeHighAmplitude(f32 amplitude) const { +    // More information about these values can be found here: +    // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md + +    static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{ +        std::pair<f32, int>{0.0f, 0x0}, +        {0.01f, 0x2}, +        {0.012f, 0x4}, +        {0.014f, 0x6}, +        {0.017f, 0x8}, +        {0.02f, 0x0a}, +        {0.024f, 0x0c}, +        {0.028f, 0x0e}, +        {0.033f, 0x10}, +        {0.04f, 0x12}, +        {0.047f, 0x14}, +        {0.056f, 0x16}, +        {0.067f, 0x18}, +        {0.08f, 0x1a}, +        {0.095f, 0x1c}, +        {0.112f, 0x1e}, +        {0.117f, 0x20}, +        {0.123f, 0x22}, +        {0.128f, 0x24}, +        {0.134f, 0x26}, +        {0.14f, 0x28}, +        {0.146f, 0x2a}, +        {0.152f, 0x2c}, +        {0.159f, 0x2e}, +        {0.166f, 0x30}, +        {0.173f, 0x32}, +        {0.181f, 0x34}, +        {0.189f, 0x36}, +        {0.198f, 0x38}, +        {0.206f, 0x3a}, +        {0.215f, 0x3c}, +        {0.225f, 0x3e}, +        {0.23f, 0x40}, +        {0.235f, 0x42}, +        {0.24f, 0x44}, +        {0.245f, 0x46}, +        {0.251f, 0x48}, +        {0.256f, 0x4a}, +        {0.262f, 0x4c}, +        {0.268f, 0x4e}, +        {0.273f, 0x50}, +        {0.279f, 0x52}, +        {0.286f, 0x54}, +        {0.292f, 0x56}, +        {0.298f, 0x58}, +        {0.305f, 0x5a}, +        {0.311f, 0x5c}, +        {0.318f, 0x5e}, +        {0.325f, 0x60}, +        {0.332f, 0x62}, +        {0.34f, 0x64}, +        {0.347f, 0x66}, +        {0.355f, 0x68}, +        {0.362f, 0x6a}, +        {0.37f, 0x6c}, +        {0.378f, 0x6e}, +        {0.387f, 0x70}, +        {0.395f, 0x72}, +        {0.404f, 0x74}, +        {0.413f, 0x76}, +        {0.422f, 0x78}, +        {0.431f, 0x7a}, +        {0.44f, 0x7c}, +        {0.45f, 0x7e}, +        {0.46f, 0x80}, +        {0.47f, 0x82}, +        {0.48f, 0x84}, +        {0.491f, 0x86}, +        {0.501f, 0x88}, +        {0.512f, 0x8a}, +        {0.524f, 0x8c}, +        {0.535f, 0x8e}, +        {0.547f, 0x90}, +        {0.559f, 0x92}, +        {0.571f, 0x94}, +        {0.584f, 0x96}, +        {0.596f, 0x98}, +        {0.609f, 0x9a}, +        {0.623f, 0x9c}, +        {0.636f, 0x9e}, +        {0.65f, 0xa0}, +        {0.665f, 0xa2}, +        {0.679f, 0xa4}, +        {0.694f, 0xa6}, +        {0.709f, 0xa8}, +        {0.725f, 0xaa}, +        {0.741f, 0xac}, +        {0.757f, 0xae}, +        {0.773f, 0xb0}, +        {0.79f, 0xb2}, +        {0.808f, 0xb4}, +        {0.825f, 0xb6}, +        {0.843f, 0xb8}, +        {0.862f, 0xba}, +        {0.881f, 0xbc}, +        {0.9f, 0xbe}, +        {0.92f, 0xc0}, +        {0.94f, 0xc2}, +        {0.96f, 0xc4}, +        {0.981f, 0xc6}, +        {1.003f, 0xc8}, +    }; + +    for (const auto& [amplitude_value, code] : high_fequency_amplitude) { +        if (amplitude <= amplitude_value) { +            return static_cast<u8>(code); +        } +    } + +    return static_cast<u8>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second); +} + +u16 RumbleProtocol::EncodeLowAmplitude(f32 amplitude) const { +    // More information about these values can be found here: +    // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md + +    static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{ +        std::pair<f32, int>{0.0f, 0x0040}, +        {0.01f, 0x8040}, +        {0.012f, 0x0041}, +        {0.014f, 0x8041}, +        {0.017f, 0x0042}, +        {0.02f, 0x8042}, +        {0.024f, 0x0043}, +        {0.028f, 0x8043}, +        {0.033f, 0x0044}, +        {0.04f, 0x8044}, +        {0.047f, 0x0045}, +        {0.056f, 0x8045}, +        {0.067f, 0x0046}, +        {0.08f, 0x8046}, +        {0.095f, 0x0047}, +        {0.112f, 0x8047}, +        {0.117f, 0x0048}, +        {0.123f, 0x8048}, +        {0.128f, 0x0049}, +        {0.134f, 0x8049}, +        {0.14f, 0x004a}, +        {0.146f, 0x804a}, +        {0.152f, 0x004b}, +        {0.159f, 0x804b}, +        {0.166f, 0x004c}, +        {0.173f, 0x804c}, +        {0.181f, 0x004d}, +        {0.189f, 0x804d}, +        {0.198f, 0x004e}, +        {0.206f, 0x804e}, +        {0.215f, 0x004f}, +        {0.225f, 0x804f}, +        {0.23f, 0x0050}, +        {0.235f, 0x8050}, +        {0.24f, 0x0051}, +        {0.245f, 0x8051}, +        {0.251f, 0x0052}, +        {0.256f, 0x8052}, +        {0.262f, 0x0053}, +        {0.268f, 0x8053}, +        {0.273f, 0x0054}, +        {0.279f, 0x8054}, +        {0.286f, 0x0055}, +        {0.292f, 0x8055}, +        {0.298f, 0x0056}, +        {0.305f, 0x8056}, +        {0.311f, 0x0057}, +        {0.318f, 0x8057}, +        {0.325f, 0x0058}, +        {0.332f, 0x8058}, +        {0.34f, 0x0059}, +        {0.347f, 0x8059}, +        {0.355f, 0x005a}, +        {0.362f, 0x805a}, +        {0.37f, 0x005b}, +        {0.378f, 0x805b}, +        {0.387f, 0x005c}, +        {0.395f, 0x805c}, +        {0.404f, 0x005d}, +        {0.413f, 0x805d}, +        {0.422f, 0x005e}, +        {0.431f, 0x805e}, +        {0.44f, 0x005f}, +        {0.45f, 0x805f}, +        {0.46f, 0x0060}, +        {0.47f, 0x8060}, +        {0.48f, 0x0061}, +        {0.491f, 0x8061}, +        {0.501f, 0x0062}, +        {0.512f, 0x8062}, +        {0.524f, 0x0063}, +        {0.535f, 0x8063}, +        {0.547f, 0x0064}, +        {0.559f, 0x8064}, +        {0.571f, 0x0065}, +        {0.584f, 0x8065}, +        {0.596f, 0x0066}, +        {0.609f, 0x8066}, +        {0.623f, 0x0067}, +        {0.636f, 0x8067}, +        {0.65f, 0x0068}, +        {0.665f, 0x8068}, +        {0.679f, 0x0069}, +        {0.694f, 0x8069}, +        {0.709f, 0x006a}, +        {0.725f, 0x806a}, +        {0.741f, 0x006b}, +        {0.757f, 0x806b}, +        {0.773f, 0x006c}, +        {0.79f, 0x806c}, +        {0.808f, 0x006d}, +        {0.825f, 0x806d}, +        {0.843f, 0x006e}, +        {0.862f, 0x806e}, +        {0.881f, 0x006f}, +        {0.9f, 0x806f}, +        {0.92f, 0x0070}, +        {0.94f, 0x8070}, +        {0.96f, 0x0071}, +        {0.981f, 0x8071}, +        {1.003f, 0x0072}, +    }; + +    for (const auto& [amplitude_value, code] : high_fequency_amplitude) { +        if (amplitude <= amplitude_value) { +            return static_cast<u16>(code); +        } +    } + +    return static_cast<u16>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second); +} + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/rumble.h b/src/input_common/helpers/joycon_protocol/rumble.h new file mode 100644 index 000000000..6c12b7925 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/rumble.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse +// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c +// https://github.com/CTCaer/jc_toolkit +// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering + +#pragma once + +#include <vector> + +#include "input_common/helpers/joycon_protocol/common_protocol.h" +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon::Joycon { + +class RumbleProtocol final : private JoyconCommonProtocol { +public: +    explicit RumbleProtocol(std::shared_ptr<JoyconHandle> handle); + +    DriverResult EnableRumble(bool is_enabled); + +    DriverResult SendVibration(const VibrationValue& vibration); + +private: +    u16 EncodeHighFrequency(f32 frequency) const; +    u8 EncodeLowFrequency(f32 frequency) const; +    u8 EncodeHighAmplitude(f32 amplitude) const; +    u16 EncodeLowAmplitude(f32 amplitude) const; +}; + +} // namespace InputCommon::Joycon diff --git a/src/input_common/input_engine.cpp b/src/input_common/input_engine.cpp index 61cfd0911..91aa96aa7 100644 --- a/src/input_common/input_engine.cpp +++ b/src/input_common/input_engine.cpp @@ -79,6 +79,17 @@ void InputEngine::SetBattery(const PadIdentifier& identifier, Common::Input::Bat      TriggerOnBatteryChange(identifier, value);  } +void InputEngine::SetColor(const PadIdentifier& identifier, Common::Input::BodyColorStatus value) { +    { +        std::scoped_lock lock{mutex}; +        ControllerData& controller = controller_list.at(identifier); +        if (!configuring) { +            controller.color = value; +        } +    } +    TriggerOnColorChange(identifier, value); +} +  void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value) {      {          std::scoped_lock lock{mutex}; @@ -176,6 +187,18 @@ Common::Input::BatteryLevel InputEngine::GetBattery(const PadIdentifier& identif      return controller.battery;  } +Common::Input::BodyColorStatus InputEngine::GetColor(const PadIdentifier& identifier) const { +    std::scoped_lock lock{mutex}; +    const auto controller_iter = controller_list.find(identifier); +    if (controller_iter == controller_list.cend()) { +        LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(), +                  identifier.pad, identifier.port); +        return {}; +    } +    const ControllerData& controller = controller_iter->second; +    return controller.color; +} +  BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) const {      std::scoped_lock lock{mutex};      const auto controller_iter = controller_list.find(identifier); @@ -328,6 +351,20 @@ void InputEngine::TriggerOnBatteryChange(const PadIdentifier& identifier,      }  } +void InputEngine::TriggerOnColorChange(const PadIdentifier& identifier, +                                       [[maybe_unused]] Common::Input::BodyColorStatus value) { +    std::scoped_lock lock{mutex_callback}; +    for (const auto& poller_pair : callback_list) { +        const InputIdentifier& poller = poller_pair.second; +        if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Color, 0)) { +            continue; +        } +        if (poller.callback.on_change) { +            poller.callback.on_change(); +        } +    } +} +  void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int motion,                                          const BasicMotion& value) {      std::scoped_lock lock{mutex_callback}; diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h index 6cbcf5207..50b5a3dc8 100644 --- a/src/input_common/input_engine.h +++ b/src/input_common/input_engine.h @@ -40,6 +40,7 @@ enum class EngineInputType {      Battery,      Button,      Camera, +    Color,      HatButton,      Motion,      Nfc, @@ -104,14 +105,17 @@ public:      void EndConfiguration();      // Sets a led pattern for a controller -    virtual void SetLeds([[maybe_unused]] const PadIdentifier& identifier, -                         [[maybe_unused]] const Common::Input::LedStatus& led_status) {} +    virtual Common::Input::DriverResult SetLeds( +        [[maybe_unused]] const PadIdentifier& identifier, +        [[maybe_unused]] const Common::Input::LedStatus& led_status) { +        return Common::Input::DriverResult::NotSupported; +    }      // Sets rumble to a controller -    virtual Common::Input::VibrationError SetVibration( +    virtual Common::Input::DriverResult SetVibration(          [[maybe_unused]] const PadIdentifier& identifier,          [[maybe_unused]] const Common::Input::VibrationStatus& vibration) { -        return Common::Input::VibrationError::NotSupported; +        return Common::Input::DriverResult::NotSupported;      }      // Returns true if device supports vibrations @@ -120,17 +124,17 @@ public:      }      // Sets polling mode to a controller -    virtual Common::Input::PollingError SetPollingMode( +    virtual Common::Input::DriverResult SetPollingMode(          [[maybe_unused]] const PadIdentifier& identifier,          [[maybe_unused]] const Common::Input::PollingMode polling_mode) { -        return Common::Input::PollingError::NotSupported; +        return Common::Input::DriverResult::NotSupported;      }      // Sets camera format to a controller -    virtual Common::Input::CameraError SetCameraFormat( +    virtual Common::Input::DriverResult SetCameraFormat(          [[maybe_unused]] const PadIdentifier& identifier,          [[maybe_unused]] Common::Input::CameraFormat camera_format) { -        return Common::Input::CameraError::NotSupported; +        return Common::Input::DriverResult::NotSupported;      }      // Returns success if nfc is supported @@ -199,6 +203,7 @@ public:      bool GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const;      f32 GetAxis(const PadIdentifier& identifier, int axis) const;      Common::Input::BatteryLevel GetBattery(const PadIdentifier& identifier) const; +    Common::Input::BodyColorStatus GetColor(const PadIdentifier& identifier) const;      BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const;      Common::Input::CameraStatus GetCamera(const PadIdentifier& identifier) const;      Common::Input::NfcStatus GetNfc(const PadIdentifier& identifier) const; @@ -212,6 +217,7 @@ protected:      void SetHatButton(const PadIdentifier& identifier, int button, u8 value);      void SetAxis(const PadIdentifier& identifier, int axis, f32 value);      void SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value); +    void SetColor(const PadIdentifier& identifier, Common::Input::BodyColorStatus value);      void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value);      void SetCamera(const PadIdentifier& identifier, const Common::Input::CameraStatus& value);      void SetNfc(const PadIdentifier& identifier, const Common::Input::NfcStatus& value); @@ -227,6 +233,7 @@ private:          std::unordered_map<int, float> axes;          std::unordered_map<int, BasicMotion> motions;          Common::Input::BatteryLevel battery{}; +        Common::Input::BodyColorStatus color{};          Common::Input::CameraStatus camera{};          Common::Input::NfcStatus nfc{};      }; @@ -235,6 +242,8 @@ private:      void TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value);      void TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value);      void TriggerOnBatteryChange(const PadIdentifier& identifier, Common::Input::BatteryLevel value); +    void TriggerOnColorChange(const PadIdentifier& identifier, +                              Common::Input::BodyColorStatus value);      void TriggerOnMotionChange(const PadIdentifier& identifier, int motion,                                 const BasicMotion& value);      void TriggerOnCameraChange(const PadIdentifier& identifier, diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp index fb8be42e2..15cbf7e5f 100644 --- a/src/input_common/input_poller.cpp +++ b/src/input_common/input_poller.cpp @@ -498,6 +498,58 @@ private:      InputEngine* input_engine;  }; +class InputFromColor final : public Common::Input::InputDevice { +public: +    explicit InputFromColor(PadIdentifier identifier_, InputEngine* input_engine_) +        : identifier(identifier_), input_engine(input_engine_) { +        UpdateCallback engine_callback{[this]() { OnChange(); }}; +        const InputIdentifier input_identifier{ +            .identifier = identifier, +            .type = EngineInputType::Color, +            .index = 0, +            .callback = engine_callback, +        }; +        last_color_value = {}; +        callback_key = input_engine->SetCallback(input_identifier); +    } + +    ~InputFromColor() override { +        input_engine->DeleteCallback(callback_key); +    } + +    Common::Input::BodyColorStatus GetStatus() const { +        return input_engine->GetColor(identifier); +    } + +    void ForceUpdate() override { +        const Common::Input::CallbackStatus status{ +            .type = Common::Input::InputType::Color, +            .color_status = GetStatus(), +        }; + +        last_color_value = status.color_status; +        TriggerOnChange(status); +    } + +    void OnChange() { +        const Common::Input::CallbackStatus status{ +            .type = Common::Input::InputType::Color, +            .color_status = GetStatus(), +        }; + +        if (status.color_status.body != last_color_value.body) { +            last_color_value = status.color_status; +            TriggerOnChange(status); +        } +    } + +private: +    const PadIdentifier identifier; +    int callback_key; +    Common::Input::BodyColorStatus last_color_value; +    InputEngine* input_engine; +}; +  class InputFromMotion final : public Common::Input::InputDevice {  public:      explicit InputFromMotion(PadIdentifier identifier_, int motion_sensor_, float gyro_threshold_, @@ -754,11 +806,11 @@ public:      explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_)          : identifier(identifier_), input_engine(input_engine_) {} -    void SetLED(const Common::Input::LedStatus& led_status) override { -        input_engine->SetLeds(identifier, led_status); +    Common::Input::DriverResult SetLED(const Common::Input::LedStatus& led_status) override { +        return input_engine->SetLeds(identifier, led_status);      } -    Common::Input::VibrationError SetVibration( +    Common::Input::DriverResult SetVibration(          const Common::Input::VibrationStatus& vibration_status) override {          return input_engine->SetVibration(identifier, vibration_status);      } @@ -767,11 +819,12 @@ public:          return input_engine->IsVibrationEnabled(identifier);      } -    Common::Input::PollingError SetPollingMode(Common::Input::PollingMode polling_mode) override { +    Common::Input::DriverResult SetPollingMode(Common::Input::PollingMode polling_mode) override {          return input_engine->SetPollingMode(identifier, polling_mode);      } -    Common::Input::CameraError SetCameraFormat(Common::Input::CameraFormat camera_format) override { +    Common::Input::DriverResult SetCameraFormat( +        Common::Input::CameraFormat camera_format) override {          return input_engine->SetCameraFormat(identifier, camera_format);      } @@ -966,6 +1019,18 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateBatteryDevice(      return std::make_unique<InputFromBattery>(identifier, input_engine.get());  } +std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateColorDevice( +    const Common::ParamPackage& params) { +    const PadIdentifier identifier = { +        .guid = Common::UUID{params.Get("guid", "")}, +        .port = static_cast<std::size_t>(params.Get("port", 0)), +        .pad = static_cast<std::size_t>(params.Get("pad", 0)), +    }; + +    input_engine->PreSetController(identifier); +    return std::make_unique<InputFromColor>(identifier, input_engine.get()); +} +  std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice(      Common::ParamPackage params) {      const PadIdentifier identifier = { @@ -1053,6 +1118,9 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::Create(      if (params.Has("battery")) {          return CreateBatteryDevice(params);      } +    if (params.Has("color")) { +        return CreateColorDevice(params); +    }      if (params.Has("camera")) {          return CreateCameraDevice(params);      } diff --git a/src/input_common/input_poller.h b/src/input_common/input_poller.h index d7db13ce4..e097e254c 100644 --- a/src/input_common/input_poller.h +++ b/src/input_common/input_poller.h @@ -191,6 +191,17 @@ private:          const Common::ParamPackage& params);      /** +     * Creates a color device from the parameters given. +     * @param params contains parameters for creating the device: +     *               - "guid": text string for identifying controllers +     *               - "port": port of the connected device +     *               - "pad": slot of the connected controller +     * @returns a unique input device with the parameters specified +     */ +    std::unique_ptr<Common::Input::InputDevice> CreateColorDevice( +        const Common::ParamPackage& params); + +    /**       * Creates a motion device from the parameters given.       * @param params contains parameters for creating the device:       *               - "axis_x": the controller horizontal axis id to bind with the input diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index e0b2131ed..c77fc04ee 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -23,6 +23,7 @@  #include "input_common/drivers/gc_adapter.h"  #endif  #ifdef HAVE_SDL2 +#include "input_common/drivers/joycon.h"  #include "input_common/drivers/sdl_driver.h"  #endif @@ -81,6 +82,7 @@ struct InputSubsystem::Impl {          RegisterEngine("virtual_gamepad", virtual_gamepad);  #ifdef HAVE_SDL2          RegisterEngine("sdl", sdl); +        RegisterEngine("joycon", joycon);  #endif          Common::Input::RegisterInputFactory("touch_from_button", @@ -111,6 +113,7 @@ struct InputSubsystem::Impl {          UnregisterEngine(virtual_gamepad);  #ifdef HAVE_SDL2          UnregisterEngine(sdl); +        UnregisterEngine(joycon);  #endif          Common::Input::UnregisterInputFactory("touch_from_button"); @@ -133,6 +136,8 @@ struct InputSubsystem::Impl {          auto udp_devices = udp_client->GetInputDevices();          devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());  #ifdef HAVE_SDL2 +        auto joycon_devices = joycon->GetInputDevices(); +        devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end());          auto sdl_devices = sdl->GetInputDevices();          devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());  #endif @@ -164,6 +169,9 @@ struct InputSubsystem::Impl {          if (engine == sdl->GetEngineName()) {              return sdl;          } +        if (engine == joycon->GetEngineName()) { +            return joycon; +        }  #endif          return nullptr;      } @@ -247,6 +255,9 @@ struct InputSubsystem::Impl {          if (engine == sdl->GetEngineName()) {              return true;          } +        if (engine == joycon->GetEngineName()) { +            return true; +        }  #endif          return false;      } @@ -260,6 +271,7 @@ struct InputSubsystem::Impl {          udp_client->BeginConfiguration();  #ifdef HAVE_SDL2          sdl->BeginConfiguration(); +        joycon->BeginConfiguration();  #endif      } @@ -272,6 +284,7 @@ struct InputSubsystem::Impl {          udp_client->EndConfiguration();  #ifdef HAVE_SDL2          sdl->EndConfiguration(); +        joycon->EndConfiguration();  #endif      } @@ -304,6 +317,7 @@ struct InputSubsystem::Impl {  #ifdef HAVE_SDL2      std::shared_ptr<SDLDriver> sdl; +    std::shared_ptr<Joycons> joycon;  #endif  }; diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index fd3bb30e1..35fef506a 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -440,6 +440,7 @@ void Config::ReadControlValues() {      ReadBasicSetting(Settings::values.emulate_analog_keyboard);      Settings::values.mouse_panning = false;      ReadBasicSetting(Settings::values.mouse_panning_sensitivity); +    ReadBasicSetting(Settings::values.enable_joycon_driver);      ReadBasicSetting(Settings::values.tas_enable);      ReadBasicSetting(Settings::values.tas_loop); @@ -1139,6 +1140,7 @@ void Config::SaveControlValues() {      WriteGlobalSetting(Settings::values.enable_accurate_vibrations);      WriteGlobalSetting(Settings::values.motion_enabled);      WriteBasicSetting(Settings::values.enable_raw_input); +    WriteBasicSetting(Settings::values.enable_joycon_driver);      WriteBasicSetting(Settings::values.keyboard_enabled);      WriteBasicSetting(Settings::values.emulate_analog_keyboard);      WriteBasicSetting(Settings::values.mouse_panning_sensitivity); diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp index 235b813d9..77b976e74 100644 --- a/src/yuzu/configuration/configure_input_advanced.cpp +++ b/src/yuzu/configuration/configure_input_advanced.cpp @@ -138,6 +138,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() {      Settings::values.controller_navigation = ui->controller_navigation->isChecked();      Settings::values.enable_ring_controller = ui->enable_ring_controller->isChecked();      Settings::values.enable_ir_sensor = ui->enable_ir_sensor->isChecked(); +    Settings::values.enable_joycon_driver = ui->enable_joycon_driver->isChecked();  }  void ConfigureInputAdvanced::LoadConfiguration() { @@ -172,6 +173,7 @@ void ConfigureInputAdvanced::LoadConfiguration() {      ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue());      ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue());      ui->enable_ir_sensor->setChecked(Settings::values.enable_ir_sensor.GetValue()); +    ui->enable_joycon_driver->setChecked(Settings::values.enable_joycon_driver.GetValue());      UpdateUIEnabled();  } diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui index fac8cf827..75d96d3ab 100644 --- a/src/yuzu/configuration/configure_input_advanced.ui +++ b/src/yuzu/configuration/configure_input_advanced.ui @@ -2696,6 +2696,22 @@                       </widget>                     </item>                     <item row="5" column="0"> +                      <widget class="QCheckBox" name="enable_joycon_driver"> +                       <property name="toolTip"> +                         <string>Requires restarting yuzu</string> +                       </property> +                       <property name="minimumSize"> +                         <size> +                           <width>0</width> +                           <height>23</height> +                         </size> +                       </property> +                       <property name="text"> +                         <string>Enable direct JoyCon driver</string> +                       </property> +                     </widget> +                   </item> +                   <item row="6" column="0">                       <widget class="QCheckBox" name="mouse_panning">                         <property name="minimumSize">                           <size> @@ -2708,7 +2724,7 @@                         </property>                       </widget>                     </item> -                   <item row="5" column="2"> +                   <item row="6" column="2">                       <widget class="QSpinBox" name="mouse_panning_sensitivity">                         <property name="toolTip">                           <string>Mouse sensitivity</string> @@ -2730,14 +2746,14 @@                         </property>                       </widget>                     </item> -                   <item row="6" column="0"> +                   <item row="7" column="0">                       <widget class="QLabel" name="motion_touch">                         <property name="text">                           <string>Motion / Touch</string>                         </property>                       </widget>                     </item> -                   <item row="6" column="2"> +                   <item row="7" column="2">                       <widget class="QPushButton" name="buttonMotionTouch">                         <property name="text">                           <string>Configure</string> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index c40d980c9..4b7e3b01b 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -66,6 +66,18 @@ QString GetButtonName(Common::Input::ButtonNames button_name) {          return QObject::tr("R");      case Common::Input::ButtonNames::TriggerL:          return QObject::tr("L"); +    case Common::Input::ButtonNames::TriggerZR: +        return QObject::tr("ZR"); +    case Common::Input::ButtonNames::TriggerZL: +        return QObject::tr("ZL"); +    case Common::Input::ButtonNames::TriggerSR: +        return QObject::tr("SR"); +    case Common::Input::ButtonNames::TriggerSL: +        return QObject::tr("SL"); +    case Common::Input::ButtonNames::ButtonStickL: +        return QObject::tr("Stick L"); +    case Common::Input::ButtonNames::ButtonStickR: +        return QObject::tr("Stick R");      case Common::Input::ButtonNames::ButtonA:          return QObject::tr("A");      case Common::Input::ButtonNames::ButtonB: @@ -76,6 +88,14 @@ QString GetButtonName(Common::Input::ButtonNames button_name) {          return QObject::tr("Y");      case Common::Input::ButtonNames::ButtonStart:          return QObject::tr("Start"); +    case Common::Input::ButtonNames::ButtonPlus: +        return QObject::tr("Plus"); +    case Common::Input::ButtonNames::ButtonMinus: +        return QObject::tr("Minus"); +    case Common::Input::ButtonNames::ButtonHome: +        return QObject::tr("Home"); +    case Common::Input::ButtonNames::ButtonCapture: +        return QObject::tr("Capture");      case Common::Input::ButtonNames::L1:          return QObject::tr("L1");      case Common::Input::ButtonNames::L2: diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp index 11390fec0..68af6c20c 100644 --- a/src/yuzu/configuration/configure_input_player_widget.cpp +++ b/src/yuzu/configuration/configure_input_player_widget.cpp @@ -103,9 +103,13 @@ void PlayerControlPreview::UpdateColors() {      colors.left = colors.primary;      colors.right = colors.primary; -    // Possible alternative to set colors from settings -    // colors.left = QColor(controller->GetColors().left.body); -    // colors.right = QColor(controller->GetColors().right.body); + +    const auto color_left = controller->GetColorsValues()[0].body; +    const auto color_right = controller->GetColorsValues()[1].body; +    if (color_left != 0 && color_right != 0) { +        colors.left = QColor(color_left); +        colors.right = QColor(color_right); +    }  }  void PlayerControlPreview::ResetInputs() { diff --git a/src/yuzu/configuration/configure_ringcon.cpp b/src/yuzu/configuration/configure_ringcon.cpp index 688c2dd38..1275f10c8 100644 --- a/src/yuzu/configuration/configure_ringcon.cpp +++ b/src/yuzu/configuration/configure_ringcon.cpp @@ -4,9 +4,11 @@  #include <memory>  #include <QKeyEvent>  #include <QMenu> +#include <QMessageBox>  #include <QTimer> +#include <fmt/format.h> -#include "core/hid/emulated_devices.h" +#include "core/hid/emulated_controller.h"  #include "core/hid/hid_core.h"  #include "input_common/drivers/keyboard.h"  #include "input_common/drivers/mouse.h" @@ -126,9 +128,16 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,          ui->buttonRingAnalogPush,      }; -    emulated_device = hid_core_.GetEmulatedDevices(); -    emulated_device->SaveCurrentConfig(); -    emulated_device->EnableConfiguration(); +    emulated_controller = hid_core_.GetEmulatedController(Core::HID::NpadIdType::Player1); +    emulated_controller->SaveCurrentConfig(); +    emulated_controller->EnableConfiguration(); + +    Core::HID::ControllerUpdateCallback engine_callback{ +        .on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdate(type); }, +        .is_npad_service = false, +    }; +    callback_key = emulated_controller->SetCallback(engine_callback); +    is_controller_set = true;      LoadConfiguration(); @@ -143,9 +152,9 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,              HandleClick(                  analog_map_buttons[sub_button_id],                  [=, this](const Common::ParamPackage& params) { -                    Common::ParamPackage param = emulated_device->GetRingParam(); +                    Common::ParamPackage param = emulated_controller->GetRingParam();                      SetAnalogParam(params, param, analog_sub_buttons[sub_button_id]); -                    emulated_device->SetRingParam(param); +                    emulated_controller->SetRingParam(param);                  },                  InputCommon::Polling::InputType::Stick);          }); @@ -155,16 +164,16 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,          connect(analog_button, &QPushButton::customContextMenuRequested,                  [=, this](const QPoint& menu_location) {                      QMenu context_menu; -                    Common::ParamPackage param = emulated_device->GetRingParam(); +                    Common::ParamPackage param = emulated_controller->GetRingParam();                      context_menu.addAction(tr("Clear"), [&] { -                        emulated_device->SetRingParam({}); +                        emulated_controller->SetRingParam(param);                          analog_map_buttons[sub_button_id]->setText(tr("[not set]"));                      });                      context_menu.addAction(tr("Invert axis"), [&] {                          const bool invert_value = param.Get("invert_x", "+") == "-";                          const std::string invert_str = invert_value ? "+" : "-";                          param.Set("invert_x", invert_str); -                        emulated_device->SetRingParam(param); +                        emulated_controller->SetRingParam(param);                          for (int sub_button_id2 = 0; sub_button_id2 < ANALOG_SUB_BUTTONS_NUM;                               ++sub_button_id2) {                              analog_map_buttons[sub_button_id2]->setText( @@ -177,16 +186,19 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,      }      connect(ui->sliderRingAnalogDeadzone, &QSlider::valueChanged, [=, this] { -        Common::ParamPackage param = emulated_device->GetRingParam(); +        Common::ParamPackage param = emulated_controller->GetRingParam();          const auto slider_value = ui->sliderRingAnalogDeadzone->value();          ui->labelRingAnalogDeadzone->setText(tr("Deadzone: %1%").arg(slider_value));          param.Set("deadzone", slider_value / 100.0f); -        emulated_device->SetRingParam(param); +        emulated_controller->SetRingParam(param);      });      connect(ui->restore_defaults_button, &QPushButton::clicked, this,              &ConfigureRingController::RestoreDefaults); +    connect(ui->enable_ring_controller_button, &QPushButton::clicked, this, +            &ConfigureRingController::EnableRingController); +      timeout_timer->setSingleShot(true);      connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); }); @@ -202,7 +214,14 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,  }  ConfigureRingController::~ConfigureRingController() { -    emulated_device->DisableConfiguration(); +    emulated_controller->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, +                                        Common::Input::PollingMode::Active); +    emulated_controller->DisableConfiguration(); + +    if (is_controller_set) { +        emulated_controller->DeleteCallback(callback_key); +        is_controller_set = false; +    }  };  void ConfigureRingController::changeEvent(QEvent* event) { @@ -219,7 +238,7 @@ void ConfigureRingController::RetranslateUI() {  void ConfigureRingController::UpdateUI() {      RetranslateUI(); -    const Common::ParamPackage param = emulated_device->GetRingParam(); +    const Common::ParamPackage param = emulated_controller->GetRingParam();      for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {          auto* const analog_button = analog_map_buttons[sub_button_id]; @@ -240,9 +259,9 @@ void ConfigureRingController::UpdateUI() {  }  void ConfigureRingController::ApplyConfiguration() { -    emulated_device->DisableConfiguration(); -    emulated_device->SaveCurrentConfig(); -    emulated_device->EnableConfiguration(); +    emulated_controller->DisableConfiguration(); +    emulated_controller->SaveCurrentConfig(); +    emulated_controller->EnableConfiguration();  }  void ConfigureRingController::LoadConfiguration() { @@ -252,10 +271,62 @@ void ConfigureRingController::LoadConfiguration() {  void ConfigureRingController::RestoreDefaults() {      const std::string default_ring_string = InputCommon::GenerateAnalogParamFromKeys(          0, 0, Config::default_ringcon_analogs[0], Config::default_ringcon_analogs[1], 0, 0.05f); -    emulated_device->SetRingParam(Common::ParamPackage(default_ring_string)); +    emulated_controller->SetRingParam(Common::ParamPackage(default_ring_string));      UpdateUI();  } +void ConfigureRingController::EnableRingController() { +    const auto dialog_title = tr("Error enabling ring input"); + +    is_ring_enabled = false; +    ui->ring_controller_sensor_value->setText(tr("Not connected")); + +    if (!Settings::values.enable_joycon_driver) { +        QMessageBox::warning(this, dialog_title, tr("Direct Joycon driver is not enabled")); +        return; +    } + +    ui->enable_ring_controller_button->setEnabled(false); +    ui->enable_ring_controller_button->setText(tr("Configuring")); +    // SetPollingMode is blocking. Allow to update the button status before calling the command +    repaint(); + +    const auto result = emulated_controller->SetPollingMode( +        Core::HID::EmulatedDeviceIndex::RightIndex, Common::Input::PollingMode::Ring); +    switch (result) { +    case Common::Input::DriverResult::Success: +        is_ring_enabled = true; +        break; +    case Common::Input::DriverResult::NotSupported: +        QMessageBox::warning(this, dialog_title, +                             tr("The current mapped device doesn't support the ring controller")); +        break; +    case Common::Input::DriverResult::NoDeviceDetected: +        QMessageBox::warning(this, dialog_title, +                             tr("The current mapped device doesn't have a ring attached")); +        break; +    default: +        QMessageBox::warning(this, dialog_title, +                             tr("Unexpected driver result %1").arg(static_cast<int>(result))); +        break; +    } +    ui->enable_ring_controller_button->setEnabled(true); +    ui->enable_ring_controller_button->setText(tr("Enable")); +} + +void ConfigureRingController::ControllerUpdate(Core::HID::ControllerTriggerType type) { +    if (!is_ring_enabled) { +        return; +    } +    if (type != Core::HID::ControllerTriggerType::RingController) { +        return; +    } + +    const auto value = emulated_controller->GetRingSensorValues(); +    const auto tex_value = QString::fromStdString(fmt::format("{:.3f}", value.raw_value)); +    ui->ring_controller_sensor_value->setText(tex_value); +} +  void ConfigureRingController::HandleClick(      QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,      InputCommon::Polling::InputType type) { diff --git a/src/yuzu/configuration/configure_ringcon.h b/src/yuzu/configuration/configure_ringcon.h index 38a9cb716..b23c27906 100644 --- a/src/yuzu/configuration/configure_ringcon.h +++ b/src/yuzu/configuration/configure_ringcon.h @@ -13,7 +13,7 @@ class InputSubsystem;  namespace Core::HID {  class HIDCore; -class EmulatedDevices; +class EmulatedController;  } // namespace Core::HID  namespace Ui { @@ -42,6 +42,12 @@ private:      /// Restore all buttons to their default values.      void RestoreDefaults(); +    /// Sets current polling mode to ring input +    void EnableRingController(); + +    // Handles emulated controller events +    void ControllerUpdate(Core::HID::ControllerTriggerType type); +      /// Called when the button was pressed.      void HandleClick(QPushButton* button,                       std::function<void(const Common::ParamPackage&)> new_input_setter, @@ -78,7 +84,11 @@ private:      std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;      InputCommon::InputSubsystem* input_subsystem; -    Core::HID::EmulatedDevices* emulated_device; +    Core::HID::EmulatedController* emulated_controller; + +    bool is_ring_enabled{}; +    bool is_controller_set{}; +    int callback_key;      std::unique_ptr<Ui::ConfigureRingController> ui;  }; diff --git a/src/yuzu/configuration/configure_ringcon.ui b/src/yuzu/configuration/configure_ringcon.ui index 9ec634dd4..514dff372 100644 --- a/src/yuzu/configuration/configure_ringcon.ui +++ b/src/yuzu/configuration/configure_ringcon.ui @@ -6,8 +6,8 @@     <rect>      <x>0</x>      <y>0</y> -    <width>298</width> -    <height>339</height> +    <width>315</width> +    <height>400</height>     </rect>    </property>    <property name="windowTitle"> @@ -46,187 +46,283 @@       </property>      </spacer>     </item> -  <item> -  <widget class="QGroupBox" name="RingAnalog"> -    <property name="title"> -    <string>Ring Sensor Parameters</string> -    </property> -    <property name="alignment"> -    <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> -    </property> -    <layout class="QVBoxLayout" name="verticalLayout_3"> -    <property name="spacing"> -      <number>0</number> -    </property> -    <property name="sizeConstraint"> -      <enum>QLayout::SetDefaultConstraint</enum> -    </property> -    <property name="leftMargin"> -      <number>3</number> -    </property> -    <property name="topMargin"> -      <number>6</number> -    </property> -    <property name="rightMargin"> -      <number>3</number> -    </property> -    <property name="bottomMargin"> -      <number>0</number> -    </property> -    <item> -      <layout class="QHBoxLayout" name="buttonRingAnalogPullHorizontaLayout"> +   <item> +    <widget class="QGroupBox" name="RingAnalog"> +     <property name="title"> +      <string>Virtual Ring Sensor Parameters</string> +     </property> +     <property name="alignment"> +      <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> +     </property> +     <layout class="QVBoxLayout" name="verticalLayout_1">        <property name="spacing"> -        <number>3</number> +       <number>0</number>        </property> -      <item alignment="Qt::AlignHCenter"> -        <widget class="QGroupBox" name="buttonRingAnalogPullGroup"> -        <property name="title"> -          <string>Pull</string> -        </property> -        <property name="alignment"> -          <set>Qt::AlignCenter</set> +      <property name="sizeConstraint"> +       <enum>QLayout::SetDefaultConstraint</enum> +      </property> +      <property name="leftMargin"> +       <number>3</number> +      </property> +      <property name="topMargin"> +       <number>6</number> +      </property> +      <property name="rightMargin"> +       <number>3</number> +      </property> +      <property name="bottomMargin"> +       <number>0</number> +      </property> +      <item> +       <layout class="QHBoxLayout" name="buttonRingAnalogPullHorizontaLayout"> +        <property name="spacing"> +         <number>3</number>          </property> -        <layout class="QVBoxLayout" name="buttonRingAnalogPullVerticalLayout"> -          <property name="spacing"> -          <number>3</number> +        <item alignment="Qt::AlignHCenter"> +         <widget class="QGroupBox" name="buttonRingAnalogPullGroup"> +          <property name="title"> +           <string>Pull</string>            </property> -          <property name="leftMargin"> -          <number>3</number> -          </property> -          <property name="topMargin"> -          <number>3</number> +          <property name="alignment"> +           <set>Qt::AlignCenter</set>            </property> -          <property name="rightMargin"> -          <number>3</number> +          <layout class="QVBoxLayout" name="buttonRingAnalogPullVerticalLayout"> +           <property name="spacing"> +            <number>3</number> +           </property> +           <property name="leftMargin"> +            <number>3</number> +           </property> +           <property name="topMargin"> +            <number>3</number> +           </property> +           <property name="rightMargin"> +            <number>3</number> +           </property> +           <property name="bottomMargin"> +            <number>3</number> +           </property> +           <item> +            <widget class="QPushButton" name="buttonRingAnalogPull"> +             <property name="minimumSize"> +              <size> +               <width>70</width> +               <height>0</height> +              </size> +             </property> +             <property name="maximumSize"> +              <size> +               <width>68</width> +               <height>16777215</height> +              </size> +             </property> +             <property name="styleSheet"> +              <string notr="true">min-width: 68px;</string> +             </property> +             <property name="text"> +              <string>Pull</string> +             </property> +            </widget> +           </item> +          </layout> +         </widget> +        </item> +        <item alignment="Qt::AlignHCenter"> +         <widget class="QGroupBox" name="buttonRingAnalogPushGroup"> +          <property name="title"> +           <string>Push</string>            </property> -          <property name="bottomMargin"> -          <number>3</number> +          <property name="alignment"> +           <set>Qt::AlignCenter</set>            </property> -          <item> -          <widget class="QPushButton" name="buttonRingAnalogPull"> -            <property name="minimumSize"> -            <size> -              <width>68</width> -              <height>0</height> -            </size> -            </property> -            <property name="maximumSize"> -            <size> -              <width>68</width> -              <height>16777215</height> -            </size> -            </property> -            <property name="styleSheet"> -            <string notr="true">min-width: 68px;</string> -            </property> -            <property name="text"> -            <string>Pull</string> -            </property> -          </widget> -          </item> -        </layout> -        </widget> +          <layout class="QVBoxLayout" name="buttonRingAnalogPushVerticalLayout"> +           <property name="spacing"> +            <number>3</number> +           </property> +           <property name="leftMargin"> +            <number>3</number> +           </property> +           <property name="topMargin"> +            <number>3</number> +           </property> +           <property name="rightMargin"> +            <number>3</number> +           </property> +           <property name="bottomMargin"> +            <number>3</number> +           </property> +           <item> +            <widget class="QPushButton" name="buttonRingAnalogPush"> +             <property name="minimumSize"> +              <size> +               <width>70</width> +               <height>0</height> +              </size> +             </property> +             <property name="maximumSize"> +              <size> +               <width>68</width> +               <height>16777215</height> +              </size> +             </property> +             <property name="styleSheet"> +              <string notr="true">min-width: 68px;</string> +             </property> +             <property name="text"> +              <string>Push</string> +             </property> +            </widget> +           </item> +          </layout> +         </widget> +        </item> +       </layout>        </item> -      <item alignment="Qt::AlignHCenter"> -        <widget class="QGroupBox" name="buttonRingAnalogPushGroup"> -        <property name="title"> -          <string>Push</string> +      <item> +       <layout class="QVBoxLayout" name="sliderRingAnalogDeadzoneVerticalLayout"> +        <property name="spacing"> +         <number>3</number>          </property> -        <property name="alignment"> -          <set>Qt::AlignCenter</set> +        <property name="sizeConstraint"> +         <enum>QLayout::SetDefaultConstraint</enum>          </property> -        <layout class="QVBoxLayout" name="buttonRingAnalogPushVerticalLayout"> -          <property name="spacing"> -          <number>3</number> -          </property> -          <property name="leftMargin"> -          <number>3</number> -          </property> -          <property name="topMargin"> -          <number>3</number> -          </property> -          <property name="rightMargin"> -          <number>3</number> -          </property> -          <property name="bottomMargin"> -          <number>3</number> -          </property> +        <property name="leftMargin"> +         <number>0</number> +        </property> +        <property name="topMargin"> +         <number>10</number> +        </property> +        <property name="rightMargin"> +         <number>0</number> +        </property> +        <property name="bottomMargin"> +         <number>3</number> +        </property> +        <item> +         <layout class="QHBoxLayout" name="sliderRingAnalogDeadzoneHorizontalLayout">            <item> -          <widget class="QPushButton" name="buttonRingAnalogPush"> -            <property name="minimumSize"> -            <size> -              <width>68</width> -              <height>0</height> -            </size> -            </property> -            <property name="maximumSize"> -            <size> -              <width>68</width> -              <height>16777215</height> -            </size> -            </property> -            <property name="styleSheet"> -            <string notr="true">min-width: 68px;</string> -            </property> +           <widget class="QLabel" name="labelRingAnalogDeadzone">              <property name="text"> -            <string>Push</string> +             <string>Deadzone: 0%</string> +            </property> +            <property name="alignment"> +             <set>Qt::AlignHCenter</set>              </property> -          </widget> +           </widget>            </item> -        </layout> -        </widget> +         </layout> +        </item> +        <item> +         <widget class="QSlider" name="sliderRingAnalogDeadzone"> +          <property name="maximum"> +           <number>100</number> +          </property> +          <property name="orientation"> +           <enum>Qt::Horizontal</enum> +          </property> +         </widget> +        </item> +       </layout>        </item> -      </layout> -    </item> -    <item> -      <layout class="QVBoxLayout" name="sliderRingAnalogDeadzoneVerticalLayout"> +     </layout> +    </widget> +   </item> +   <item> +    <widget class="QGroupBox" name="RingDriver"> +     <property name="title"> +      <string>Direct Joycon Driver</string> +     </property> +     <property name="alignment"> +      <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> +     </property> +     <layout class="QVBoxLayout" name="verticalLayout_2">        <property name="spacing"> -        <number>3</number> +       <number>0</number>        </property>        <property name="sizeConstraint"> -        <enum>QLayout::SetDefaultConstraint</enum> +       <enum>QLayout::SetDefaultConstraint</enum>        </property>        <property name="leftMargin"> -        <number>0</number> +       <number>3</number>        </property>        <property name="topMargin"> -        <number>10</number> +       <number>6</number>        </property>        <property name="rightMargin"> -        <number>0</number> +       <number>3</number>        </property>        <property name="bottomMargin"> -        <number>3</number> +       <number>10</number>        </property>        <item> -        <layout class="QHBoxLayout" name="sliderRingAnalogDeadzoneHorizontalLayout"> -        <item> -          <widget class="QLabel" name="labelRingAnalogDeadzone"> +       <layout class="QGridLayout" name="gridLayout"> +        <property name="leftMargin"> +         <number>10</number> +        </property> +        <property name="topMargin"> +         <number>6</number> +        </property> +        <property name="rightMargin"> +         <number>10</number> +        </property> +        <property name="bottomMargin"> +         <number>10</number> +        </property> +        <property name="verticalSpacing"> +         <number>10</number> +        </property> +        <item row="0" column="1"> +         <spacer name="horizontalSpacer"> +          <property name="orientation"> +           <enum>Qt::Horizontal</enum> +          </property> +          <property name="sizeType"> +           <enum>QSizePolicy::Fixed</enum> +          </property> +          <property name="sizeHint" stdset="0"> +           <size> +            <width>76</width> +            <height>20</height> +           </size> +          </property> +         </spacer> +        </item> +        <item row="0" column="0"> +         <widget class="QLabel" name="enable_ring_controller_label"> +          <property name="text"> +           <string>Enable Ring Input</string> +          </property> +         </widget> +        </item> +        <item row="0" column="2"> +         <widget class="QPushButton" name="enable_ring_controller_button">            <property name="text"> -            <string>Deadzone: 0%</string> +           <string>Enable</string> +          </property> +         </widget> +        </item> +        <item row="1" column="0"> +         <widget class="QLabel" name="ring_controller_sensor_label"> +          <property name="text"> +           <string>Ring Sensor Value</string> +          </property> +         </widget> +        </item> +        <item row="1" column="2"> +         <widget class="QLabel" name="ring_controller_sensor_value"> +          <property name="text"> +           <string>Not connected</string>            </property>            <property name="alignment"> -            <set>Qt::AlignHCenter</set> +           <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>            </property> -          </widget> +         </widget>          </item> -        </layout> -      </item> -      <item> -        <widget class="QSlider" name="sliderRingAnalogDeadzone"> -        <property name="maximum"> -          <number>100</number> -        </property> -        <property name="orientation"> -          <enum>Qt::Horizontal</enum> -        </property> -        </widget> +       </layout>        </item> -      </layout> -    </item> -    </layout> -  </widget> -  </item> +     </layout> +    </widget> +   </item>     <item>      <spacer name="verticalSpacer">       <property name="orientation"> @@ -273,6 +369,6 @@     <signal>rejected()</signal>     <receiver>ConfigureRingController</receiver>     <slot>reject()</slot> -   </connection> +  </connection>   </connections>  </ui> | 
