From 66cf94709ae0d28d0c62d2745b3365fc18658cd5 Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Mon, 11 Oct 2021 00:43:11 -0500
Subject: [PATCH] core/hid: Add output devices

---
 src/common/input.h                            |  39 ++++++
 src/core/hid/emulated_controller.cpp          | 119 ++++++++++++++----
 src/core/hid/emulated_controller.h            |  13 +-
 src/core/hid/hid_types.h                      |  18 +++
 src/core/hle/service/hid/controllers/npad.cpp |  27 +---
 src/core/hle/service/hid/controllers/npad.h   |  18 +--
 src/input_common/drivers/gc_adapter.cpp       |   8 +-
 src/input_common/drivers/gc_adapter.h         |   2 +-
 src/input_common/drivers/sdl_driver.cpp       |   8 +-
 src/input_common/drivers/sdl_driver.h         |   2 +-
 .../helpers/stick_from_buttons.cpp            |   3 +-
 src/input_common/helpers/stick_from_buttons.h |   3 +-
 .../helpers/touch_from_buttons.cpp            |   4 +-
 src/input_common/input_engine.h               |  18 ++-
 src/input_common/input_poller.cpp             |  40 +++++-
 src/input_common/input_poller.h               |  28 ++++-
 src/input_common/main.cpp                     |  32 +++--
 .../configuration/configure_input_player.cpp  |   7 ++
 .../configure_input_player_widget.cpp         |  59 ++++-----
 .../configure_input_player_widget.h           |  10 +-
 20 files changed, 313 insertions(+), 145 deletions(-)

diff --git a/src/common/input.h b/src/common/input.h
index 6eefc55f9c..3a28b77a7b 100644
--- a/src/common/input.h
+++ b/src/common/input.h
@@ -38,6 +38,27 @@ enum class BatteryLevel {
     Charging,
 };
 
+enum class PollingMode {
+    Active,
+    Pasive,
+    Camera,
+    NCF,
+    IR,
+};
+
+enum class VibrationError {
+    None,
+    NotSupported,
+    Disabled,
+    Unknown,
+};
+
+enum class PollingError {
+    None,
+    NotSupported,
+    Unknown,
+};
+
 struct AnalogProperties {
     float deadzone{};
     float range{1.0f};
@@ -149,6 +170,24 @@ private:
     InputCallback callback;
 };
 
+/// An abstract class template for an output device (rumble, LED pattern, polling mode).
+class OutputDevice {
+public:
+    virtual ~OutputDevice() = default;
+
+    virtual void SetLED([[maybe_unused]] LedStatus led_status) {
+        return;
+    }
+
+    virtual VibrationError SetVibration([[maybe_unused]] VibrationStatus vibration_status) {
+        return VibrationError::NotSupported;
+    }
+
+    virtual PollingError SetPollingMode([[maybe_unused]] PollingMode polling_mode) {
+        return PollingError::NotSupported;
+    }
+};
+
 /// An abstract class template for a factory that can create input devices.
 template <typename InputDeviceType>
 class Factory {
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 4eb5d99bc3..b9d16657a5 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -66,12 +66,32 @@ void EmulatedController::ReloadFromSettings() {
     for (std::size_t index = 0; index < player.motions.size(); ++index) {
         motion_params[index] = Common::ParamPackage(player.motions[index]);
     }
+
+    controller.colors_state.left = {
+        .body = player.body_color_left,
+        .button = player.button_color_left,
+    };
+
+    controller.colors_state.right = {
+        .body = player.body_color_right,
+        .button = player.button_color_right,
+    };
+
+    controller.colors_state.fullkey = controller.colors_state.left;
+
+    SetNpadType(MapSettingsTypeToNPad(player.controller_type));
+
+    if (player.connected) {
+        Connect();
+    } else {
+        Disconnect();
+    }
+
     ReloadInput();
 }
 
 void EmulatedController::ReloadInput() {
     const auto player_index = NpadIdTypeToIndex(npad_id_type);
-    const auto& player = Settings::values.players.GetValue()[player_index];
     const auto left_side = button_params[Settings::NativeButton::ZL];
     const auto right_side = button_params[Settings::NativeButton::ZR];
 
@@ -90,21 +110,13 @@ void EmulatedController::ReloadInput() {
     trigger_devices[1] =
         Input::CreateDevice<Input::InputDevice>(button_params[Settings::NativeButton::ZR]);
 
-    controller.colors_state.left = {
-        .body = player.body_color_left,
-        .button = player.button_color_left,
-    };
-
-    controller.colors_state.right = {
-        .body = player.body_color_right,
-        .button = player.button_color_right,
-    };
-
-    controller.colors_state.fullkey = controller.colors_state.left;
-
     battery_devices[0] = Input::CreateDevice<Input::InputDevice>(left_side);
     battery_devices[1] = Input::CreateDevice<Input::InputDevice>(right_side);
 
+    button_params[Settings::NativeButton::ZL].Set("output",true);
+    output_devices[0] =
+        Input::CreateDevice<Input::OutputDevice>(button_params[Settings::NativeButton::ZL]);
+
     for (std::size_t index = 0; index < button_devices.size(); ++index) {
         if (!button_devices[index]) {
             continue;
@@ -149,14 +161,6 @@ void EmulatedController::ReloadInput() {
             [this, index](Input::CallbackStatus callback) { SetMotion(callback, index); }};
         motion_devices[index]->SetCallback(motion_callback);
     }
-
-    SetNpadType(MapSettingsTypeToNPad(player.controller_type));
-
-    if (player.connected) {
-        Connect();
-    } else {
-        Disconnect();
-    }
 }
 
 void EmulatedController::UnloadInput() {
@@ -197,7 +201,8 @@ void EmulatedController::SaveCurrentConfig() {
 
     const auto player_index = NpadIdTypeToIndex(npad_id_type);
     auto& player = Settings::values.players.GetValue()[player_index];
-
+    player.connected = is_connected;
+    player.controller_type = MapNPadToSettingsType(npad_type);
     for (std::size_t index = 0; index < player.buttons.size(); ++index) {
         player.buttons[index] = button_params[index].Serialize();
     }
@@ -601,13 +606,50 @@ void EmulatedController::SetBattery(Input::CallbackStatus callback, std::size_t
     TriggerOnChange(ControllerTriggerType::Battery);
 }
 
-bool EmulatedController::SetVibration([[maybe_unused]] std::size_t device_index,
-                                      [[maybe_unused]] VibrationValue vibration) {
-    return false;
+bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
+    if (!output_devices[device_index]) {
+        return false;
+    }
+
+    const Input::VibrationStatus status = {
+        .low_amplitude = vibration.high_amplitude,
+        .low_frequency = vibration.high_amplitude,
+        .high_amplitude = vibration.high_amplitude,
+        .high_frequency = vibration.high_amplitude,
+    };
+    return output_devices[device_index]->SetVibration(status) == Input::VibrationError::None;
 }
 
-int EmulatedController::TestVibration(std::size_t device_index) {
-    return 1;
+bool EmulatedController::TestVibration(std::size_t device_index) {
+    if (!output_devices[device_index]) {
+        return false;
+    }
+
+    // Send a slight vibration to test for rumble support
+    constexpr Input::VibrationStatus status = {
+        .low_amplitude = 0.001f,
+        .low_frequency = 160.0f,
+        .high_amplitude = 0.001f,
+        .high_frequency = 320.0f,
+    };
+    return output_devices[device_index]->SetVibration(status) == Input::VibrationError::None;
+}
+
+void EmulatedController::SetLedPattern() {
+    for (auto& device : output_devices) {
+        if (!device) {
+            continue;
+        }
+
+        const LedPattern pattern = GetLedPattern();
+        const Input::LedStatus status = {
+            .led_1 = pattern.position1 != 0,
+            .led_2 = pattern.position2 != 0,
+            .led_3 = pattern.position3 != 0,
+            .led_4 = pattern.position4 != 0,
+        };
+        device->SetLED(status);
+    }
 }
 
 void EmulatedController::Connect() {
@@ -655,6 +697,29 @@ void EmulatedController::SetNpadType(NpadType npad_type_) {
     TriggerOnChange(ControllerTriggerType::Type);
 }
 
+LedPattern EmulatedController::GetLedPattern() const {
+    switch (npad_id_type) {
+    case NpadIdType::Player1:
+        return LedPattern{1, 0, 0, 0};
+    case NpadIdType::Player2:
+        return LedPattern{1, 1, 0, 0};
+    case NpadIdType::Player3:
+        return LedPattern{1, 1, 1, 0};
+    case NpadIdType::Player4:
+        return LedPattern{1, 1, 1, 1};
+    case NpadIdType::Player5:
+        return LedPattern{1, 0, 0, 1};
+    case NpadIdType::Player6:
+        return LedPattern{1, 0, 1, 0};
+    case NpadIdType::Player7:
+        return LedPattern{1, 0, 1, 1};
+    case NpadIdType::Player8:
+        return LedPattern{0, 1, 1, 0};
+    default:
+        return LedPattern{0, 0, 0, 0};
+    }
+}
+
 ButtonValues EmulatedController::GetButtonsValues() const {
     return controller.button_values;
 }
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index 94db9b00be..322d2cab04 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -33,12 +33,14 @@ using ControllerMotionDevices =
 using TriggerDevices =
     std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeTrigger::NumTriggers>;
 using BatteryDevices = std::array<std::unique_ptr<Input::InputDevice>, 2>;
+using OutputDevices = std::array<std::unique_ptr<Input::OutputDevice>, 2>;
 
 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 BatteryParams = std::array<Common::ParamPackage, 2>;
+using OutputParams = std::array<Common::ParamPackage, 2>;
 
 using ButtonValues = std::array<Input::ButtonStatus, Settings::NativeButton::NumButtons>;
 using SticksValues = std::array<Input::StickStatus, Settings::NativeAnalog::NumAnalogs>;
@@ -94,6 +96,7 @@ struct ControllerStatus {
     ControllerColors colors_state{};
     BatteryLevelState battery_state{};
 };
+
 enum class ControllerTriggerType {
     Button,
     Stick,
@@ -137,6 +140,9 @@ public:
     /// Gets the NpadType for this controller.
     NpadType GetNpadType() const;
 
+    /// Gets the NpadType for this controller.
+    LedPattern GetLedPattern() const;
+
     void Connect();
     void Disconnect();
 
@@ -179,7 +185,9 @@ public:
     BatteryLevelState GetBattery() const;
 
     bool SetVibration(std::size_t device_index, VibrationValue vibration);
-    int TestVibration(std::size_t device_index);
+    bool TestVibration(std::size_t device_index);
+
+    void SetLedPattern();
 
     int SetCallback(ControllerUpdateCallback update_callback);
     void DeleteCallback(int key);
@@ -215,13 +223,14 @@ private:
     ControllerMotionParams motion_params;
     TriggerParams trigger_params;
     BatteryParams battery_params;
+    OutputParams output_params;
 
     ButtonDevices button_devices;
     StickDevices stick_devices;
     ControllerMotionDevices motion_devices;
     TriggerDevices trigger_devices;
     BatteryDevices battery_devices;
-    // VibrationDevices vibration_devices;
+    OutputDevices output_devices;
 
     mutable std::mutex mutex;
     std::unordered_map<int, ControllerUpdateCallback> callback_list;
diff --git a/src/core/hid/hid_types.h b/src/core/hid/hid_types.h
index d3f7930c93..f12a14cb8e 100644
--- a/src/core/hid/hid_types.h
+++ b/src/core/hid/hid_types.h
@@ -112,6 +112,8 @@ struct NpadStyleTag {
         BitField<7, 1, u32> lark;
         BitField<8, 1, u32> handheld_lark;
         BitField<9, 1, u32> lucia;
+        BitField<10, 1, u32> lagoon;
+        BitField<11, 1, u32> lager;
         BitField<29, 1, u32> system_ext;
         BitField<30, 1, u32> system;
     };
@@ -175,6 +177,22 @@ struct NpadPowerInfo {
 };
 static_assert(sizeof(NpadPowerInfo) == 0xC, "NpadPowerInfo is an invalid size");
 
+struct LedPattern {
+    explicit LedPattern(u64 light1, u64 light2, u64 light3, u64 light4) {
+        position1.Assign(light1);
+        position2.Assign(light2);
+        position3.Assign(light3);
+        position4.Assign(light4);
+    }
+    union {
+        u64 raw{};
+        BitField<0, 1, u64> position1;
+        BitField<1, 1, u64> position2;
+        BitField<2, 1, u64> position3;
+        BitField<3, 1, u64> position4;
+    };
+};
+
 // This is nn::hid::NpadButton
 enum class NpadButton : u64 {
     None = 0,
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 03cbd42f4d..a2e9ddf4d5 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -796,7 +796,7 @@ void Controller_NPad::InitializeVibrationDeviceAtIndex(std::size_t npad_index,
     }
 
     controller.vibration[device_index].device_mounted =
-        controller.device->TestVibration(device_index) == 1;
+        controller.device->TestVibration(device_index);
 }
 
 void Controller_NPad::SetPermitVibrationSession(bool permit_vibration_session) {
@@ -954,31 +954,12 @@ bool Controller_NPad::SwapNpadAssignment(u32 npad_id_1, u32 npad_id_2) {
     return true;
 }
 
-Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) {
+Core::HID::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) {
     if (npad_id == npad_id_list.back() || npad_id == npad_id_list[npad_id_list.size() - 2]) {
         // These are controllers without led patterns
-        return LedPattern{0, 0, 0, 0};
-    }
-    switch (npad_id) {
-    case 0:
-        return LedPattern{1, 0, 0, 0};
-    case 1:
-        return LedPattern{1, 1, 0, 0};
-    case 2:
-        return LedPattern{1, 1, 1, 0};
-    case 3:
-        return LedPattern{1, 1, 1, 1};
-    case 4:
-        return LedPattern{1, 0, 0, 1};
-    case 5:
-        return LedPattern{1, 0, 1, 0};
-    case 6:
-        return LedPattern{1, 0, 1, 1};
-    case 7:
-        return LedPattern{0, 1, 1, 0};
-    default:
-        return LedPattern{0, 0, 0, 0};
+        return Core::HID::LedPattern{0, 0, 0, 0};
     }
+    return controller_data[npad_id].device->GetLedPattern();
 }
 
 bool Controller_NPad::IsUnintendedHomeButtonInputProtectionEnabled(u32 npad_id) const {
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index 483cae5b68..b0e2f84308 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -115,22 +115,6 @@ public:
         .freq_high = 320.0f,
     };
 
-    struct LedPattern {
-        explicit LedPattern(u64 light1, u64 light2, u64 light3, u64 light4) {
-            position1.Assign(light1);
-            position2.Assign(light2);
-            position3.Assign(light3);
-            position4.Assign(light4);
-        }
-        union {
-            u64 raw{};
-            BitField<0, 1, u64> position1;
-            BitField<1, 1, u64> position2;
-            BitField<2, 1, u64> position3;
-            BitField<3, 1, u64> position4;
-        };
-    };
-
     void SetSupportedStyleSet(Core::HID::NpadStyleTag style_set);
     Core::HID::NpadStyleTag GetSupportedStyleSet() const;
 
@@ -186,7 +170,7 @@ public:
     void SetSixAxisFusionParameters(f32 parameter1, f32 parameter2);
     std::pair<f32, f32> GetSixAxisFusionParameters();
     void ResetSixAxisFusionParameters();
-    LedPattern GetLedPattern(u32 npad_id);
+    Core::HID::LedPattern GetLedPattern(u32 npad_id);
     bool IsUnintendedHomeButtonInputProtectionEnabled(u32 npad_id) const;
     void SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled, u32 npad_id);
     void SetAnalogStickUseCenterClamp(bool use_center_clamp);
diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp
index 6721ba4f7b..2aa5a16a6e 100644
--- a/src/input_common/drivers/gc_adapter.cpp
+++ b/src/input_common/drivers/gc_adapter.cpp
@@ -322,13 +322,17 @@ bool GCAdapter::GetGCEndpoint(libusb_device* device) {
     return true;
 }
 
-bool GCAdapter::SetRumble(const PadIdentifier& identifier, const Input::VibrationStatus vibration) {
+Input::VibrationError GCAdapter::SetRumble(const PadIdentifier& identifier, const Input::VibrationStatus vibration) {
     const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f;
     const auto processed_amplitude =
         static_cast<u8>((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8);
 
     pads[identifier.port].rumble_amplitude = processed_amplitude;
-    return rumble_enabled;
+
+    if (!rumble_enabled) {
+        return Input::VibrationError::Disabled;
+    }
+    return Input::VibrationError::None;
 }
 
 void GCAdapter::UpdateVibrations() {
diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h
index c0bf1ed7ae..dd23dd9f3b 100644
--- a/src/input_common/drivers/gc_adapter.h
+++ b/src/input_common/drivers/gc_adapter.h
@@ -24,7 +24,7 @@ public:
     explicit GCAdapter(const std::string input_engine_);
     ~GCAdapter();
 
-    bool SetRumble(const PadIdentifier& identifier,
+    Input::VibrationError SetRumble(const PadIdentifier& identifier,
                    const Input::VibrationStatus vibration) override;
 
     /// Used for automapping features
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp
index efb4a21068..f7f03c5f23 100644
--- a/src/input_common/drivers/sdl_driver.cpp
+++ b/src/input_common/drivers/sdl_driver.cpp
@@ -506,7 +506,8 @@ std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const {
     }
     return devices;
 }
-bool SDLDriver::SetRumble(const PadIdentifier& identifier, const Input::VibrationStatus vibration) {
+Input::VibrationError SDLDriver::SetRumble(const PadIdentifier& identifier,
+                                           const Input::VibrationStatus vibration) {
     const auto joystick =
         GetSDLJoystickByGUID(identifier.guid.Format(), static_cast<int>(identifier.port));
     const auto process_amplitude = [](f32 amplitude) {
@@ -519,7 +520,10 @@ bool SDLDriver::SetRumble(const PadIdentifier& identifier, const Input::Vibratio
         .high_frequency = vibration.high_frequency,
     };
 
-    return joystick->RumblePlay(new_vibration);
+    if (!joystick->RumblePlay(new_vibration)) {
+        return Input::VibrationError::Unknown;
+    }
+    return Input::VibrationError::None;
 }
 Common::ParamPackage SDLDriver::BuildAnalogParamPackageForButton(int port, std::string guid,
                                                                  s32 axis, float value) const {
diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h
index d8d3501842..f66b33c775 100644
--- a/src/input_common/drivers/sdl_driver.h
+++ b/src/input_common/drivers/sdl_driver.h
@@ -58,7 +58,7 @@ public:
     std::string GetHatButtonName(u8 direction_value) const override;
     u8 GetHatButtonId(const std::string direction_name) const override;
 
-    bool SetRumble(const PadIdentifier& identifier,
+    Input::VibrationError SetRumble(const PadIdentifier& identifier,
                    const Input::VibrationStatus vibration) override;
 
 private:
diff --git a/src/input_common/helpers/stick_from_buttons.cpp b/src/input_common/helpers/stick_from_buttons.cpp
index 38f1507462..89ba4aeb12 100644
--- a/src/input_common/helpers/stick_from_buttons.cpp
+++ b/src/input_common/helpers/stick_from_buttons.cpp
@@ -251,7 +251,8 @@ private:
     std::chrono::time_point<std::chrono::steady_clock> last_update;
 };
 
-std::unique_ptr<Input::InputDevice> StickFromButton::Create(const Common::ParamPackage& params) {
+std::unique_ptr<Input::InputDevice> StickFromButton::Create(
+    const Common::ParamPackage& params) {
     const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
     auto up = Input::CreateDeviceFromString<Input::InputDevice>(params.Get("up", null_engine));
     auto down = Input::CreateDeviceFromString<Input::InputDevice>(params.Get("down", null_engine));
diff --git a/src/input_common/helpers/stick_from_buttons.h b/src/input_common/helpers/stick_from_buttons.h
index 1d6e24c98e..87165e022a 100644
--- a/src/input_common/helpers/stick_from_buttons.h
+++ b/src/input_common/helpers/stick_from_buttons.h
@@ -25,7 +25,8 @@ public:
      *     - "modifier": a serialized ParamPackage for creating a button device as the modifier
      *     - "modifier_scale": a float for the multiplier the modifier gives to the position
      */
-    std::unique_ptr<Input::InputDevice> Create(const Common::ParamPackage& params) override;
+    std::unique_ptr<Input::InputDevice> Create(
+        const Common::ParamPackage& params) override;
 };
 
 } // namespace InputCommon
diff --git a/src/input_common/helpers/touch_from_buttons.cpp b/src/input_common/helpers/touch_from_buttons.cpp
index 2abfaf841d..6c9046ffb4 100644
--- a/src/input_common/helpers/touch_from_buttons.cpp
+++ b/src/input_common/helpers/touch_from_buttons.cpp
@@ -57,7 +57,9 @@ private:
     const Input::AnalogProperties properties{0.0f, 1.0f, 0.5f, 0.0f, false};
 };
 
-std::unique_ptr<Input::InputDevice> TouchFromButton::Create(const Common::ParamPackage& params) {
+
+std::unique_ptr<Input::InputDevice> TouchFromButton::Create(
+    const Common::ParamPackage& params) {
     const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
     auto button =
         Input::CreateDeviceFromString<Input::InputDevice>(params.Get("button", null_engine));
diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h
index 86a8e00d8e..8a953c3820 100644
--- a/src/input_common/input_engine.h
+++ b/src/input_common/input_engine.h
@@ -114,18 +114,24 @@ public:
     // Disable configuring mode for mapping
     void EndConfiguration();
 
-    // Sets rumble to a controller
-    virtual bool SetRumble([[maybe_unused]] const PadIdentifier& identifier,
-                           [[maybe_unused]] const Input::VibrationStatus vibration) {
-        return false;
-    }
-
     // Sets a led pattern for a controller
     virtual void SetLeds([[maybe_unused]] const PadIdentifier& identifier,
                          [[maybe_unused]] const Input::LedStatus led_status) {
         return;
     }
 
+    // Sets rumble to a controller
+    virtual Input::VibrationError SetRumble([[maybe_unused]] const PadIdentifier& identifier,
+                           [[maybe_unused]] const Input::VibrationStatus vibration) {
+        return Input::VibrationError::NotSupported;
+    }
+
+    // Sets polling mode to a controller
+    virtual Input::PollingError SetPollingMode([[maybe_unused]] const PadIdentifier& identifier,
+                           [[maybe_unused]] const Input::PollingMode vibration) {
+        return Input::PollingError::NotSupported;
+    }
+
     // Returns the engine name
     [[nodiscard]] const std::string& GetEngineName() const;
 
diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp
index 46a7dd2761..781012886a 100644
--- a/src/input_common/input_poller.cpp
+++ b/src/input_common/input_poller.cpp
@@ -592,6 +592,28 @@ private:
     InputEngine* input_engine;
 };
 
+class OutputFromIdentifier final : public Input::OutputDevice {
+public:
+    explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_)
+        : identifier(identifier_), input_engine(input_engine_) {}
+
+    virtual void SetLED( Input::LedStatus led_status) {
+        input_engine->SetLeds(identifier, led_status);
+    }
+
+    virtual Input::VibrationError SetVibration(Input::VibrationStatus vibration_status) {
+        return input_engine->SetRumble(identifier, vibration_status);
+    }
+
+    virtual Input::PollingError SetPollingMode(Input::PollingMode polling_mode) {
+        return input_engine->SetPollingMode(identifier, polling_mode);
+    }
+
+private:
+    const PadIdentifier identifier;
+    InputEngine* input_engine;
+};
+
 std::unique_ptr<Input::InputDevice> InputFactory::CreateButtonDevice(
     const Common::ParamPackage& params) {
     const PadIdentifier identifier = {
@@ -825,7 +847,8 @@ std::unique_ptr<Input::InputDevice> InputFactory::CreateMotionDevice(Common::Par
 InputFactory::InputFactory(std::shared_ptr<InputEngine> input_engine_)
     : input_engine(std::move(input_engine_)) {}
 
-std::unique_ptr<Input::InputDevice> InputFactory::Create(const Common::ParamPackage& params) {
+std::unique_ptr<Input::InputDevice> InputFactory::Create(
+    const Common::ParamPackage& params) {
     if (params.Has("button") && params.Has("axis")) {
         return CreateTriggerDevice(params);
     }
@@ -857,4 +880,19 @@ std::unique_ptr<Input::InputDevice> InputFactory::Create(const Common::ParamPack
     return std::make_unique<DummyInput>();
 }
 
+OutputFactory::OutputFactory(std::shared_ptr<InputEngine> input_engine_)
+    : input_engine(std::move(input_engine_)) {}
+
+std::unique_ptr<Input::OutputDevice> OutputFactory::Create(
+    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<OutputFromIdentifier>(identifier, input_engine.get());
+}
+
 } // namespace InputCommon
diff --git a/src/input_common/input_poller.h b/src/input_common/input_poller.h
index 3c1e5b541f..16cade5faf 100644
--- a/src/input_common/input_poller.h
+++ b/src/input_common/input_poller.h
@@ -16,12 +16,32 @@ class InputEngine;
 /**
  * An Input factory. It receives input events and forward them to all input devices it created.
  */
+
+class OutputFactory final : public Input::Factory<Input::OutputDevice> {
+public:
+    explicit OutputFactory(std::shared_ptr<InputEngine> input_engine_);
+
+    /**
+     * Creates an output device from the parameters given.
+     * @param params contains parameters for creating the device:
+     * @param    - "guid": text string for identifing controllers
+     * @param    - "port": port of the connected device
+     * @param    - "pad": slot of the connected controller
+     * @return an unique ouput device with the parameters specified
+     */
+    std::unique_ptr<Input::OutputDevice> Create(
+        const Common::ParamPackage& params) override;
+
+private:
+    std::shared_ptr<InputEngine> input_engine;
+};
+
 class InputFactory final : public Input::Factory<Input::InputDevice> {
 public:
     explicit InputFactory(std::shared_ptr<InputEngine> input_engine_);
 
     /**
-     * Creates a input device from the parameters given. Identifies the type of input to be returned
+     * Creates an input device from the parameters given. Identifies the type of input to be returned
      * if it contains the following parameters:
      * - button: Contains "button" or "code"
      * - hat_button: Contains "hat"
@@ -32,6 +52,7 @@ public:
      * - motion: Contains "motion"
      * - touch: Contains "button", "axis_x" and "axis_y"
      * - battery: Contains "battery"
+     * - output: Contains "output"
      * @param params contains parameters for creating the device:
      * @param    - "code": the code of the keyboard key to bind with the input
      * @param    - "button": same as "code" but for controller buttons
@@ -41,10 +62,11 @@ public:
      * @param    - "axis_x": same as axis but specifing horizontal direction
      * @param    - "axis_y": same as axis but specifing vertical direction
      * @param    - "axis_z": same as axis but specifing forward direction
-     * @param   - "battery": Only used as a placeholder to set the input type
+     * @param    - "battery": Only used as a placeholder to set the input type
      * @return an unique input device with the parameters specified
      */
-    std::unique_ptr<Input::InputDevice> Create(const Common::ParamPackage& params) override;
+    std::unique_ptr<Input::InputDevice> Create(
+        const Common::ParamPackage& params) override;
 
 private:
     /**
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index 46ca6b76c8..b7fe9cb373 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -46,8 +46,10 @@ struct InputSubsystem::Impl {
 
         gcadapter = std::make_shared<GCAdapter>("gcpad");
         gcadapter->SetMappingCallback(mapping_callback);
-        gcadapter_factory = std::make_shared<InputFactory>(gcadapter);
-        Input::RegisterFactory<Input::InputDevice>(gcadapter->GetEngineName(), gcadapter_factory);
+        gcadapter_input_factory = std::make_shared<InputFactory>(gcadapter);
+        gcadapter_output_factory = std::make_shared<OutputFactory>(gcadapter);
+        Input::RegisterFactory<Input::InputDevice>(gcadapter->GetEngineName(), gcadapter_input_factory);
+        Input::RegisterFactory<Input::OutputDevice>(gcadapter->GetEngineName(), gcadapter_output_factory);
 
         udp_client = std::make_shared<CemuhookUDP::UDPClient>("cemuhookudp");
         udp_client->SetMappingCallback(mapping_callback);
@@ -62,8 +64,10 @@ struct InputSubsystem::Impl {
 #ifdef HAVE_SDL2
         sdl = std::make_shared<SDLDriver>("sdl");
         sdl->SetMappingCallback(mapping_callback);
-        sdl_factory = std::make_shared<InputFactory>(sdl);
-        Input::RegisterFactory<Input::InputDevice>(sdl->GetEngineName(), sdl_factory);
+        sdl_input_factory = std::make_shared<InputFactory>(sdl);
+        sdl_output_factory = std::make_shared<OutputFactory>(sdl);
+        Input::RegisterFactory<Input::InputDevice>(sdl->GetEngineName(), sdl_input_factory);
+        Input::RegisterFactory<Input::OutputDevice>(sdl->GetEngineName(), sdl_output_factory);
 #endif
 
         Input::RegisterFactory<Input::InputDevice>("touch_from_button",
@@ -247,21 +251,27 @@ struct InputSubsystem::Impl {
     }
 
     std::shared_ptr<MappingFactory> mapping_factory;
+
     std::shared_ptr<Keyboard> keyboard;
-    std::shared_ptr<InputFactory> keyboard_factory;
     std::shared_ptr<Mouse> mouse;
-    std::shared_ptr<InputFactory> mouse_factory;
     std::shared_ptr<GCAdapter> gcadapter;
-    std::shared_ptr<InputFactory> gcadapter_factory;
     std::shared_ptr<TouchScreen> touch_screen;
-    std::shared_ptr<InputFactory> touch_screen_factory;
-    std::shared_ptr<CemuhookUDP::UDPClient> udp_client;
-    std::shared_ptr<InputFactory> udp_client_factory;
     std::shared_ptr<TasInput::Tas> tas_input;
+    std::shared_ptr<CemuhookUDP::UDPClient> udp_client;
+
+    std::shared_ptr<InputFactory> keyboard_factory;
+    std::shared_ptr<InputFactory> mouse_factory;
+    std::shared_ptr<InputFactory> gcadapter_input_factory;
+    std::shared_ptr<InputFactory> touch_screen_factory;
+    std::shared_ptr<InputFactory> udp_client_factory;
     std::shared_ptr<InputFactory> tas_input_factory;
+
+    std::shared_ptr<OutputFactory> gcadapter_output_factory;
+
 #ifdef HAVE_SDL2
     std::shared_ptr<SDLDriver> sdl;
-    std::shared_ptr<InputFactory> sdl_factory;
+    std::shared_ptr<InputFactory> sdl_input_factory;
+    std::shared_ptr<OutputFactory> sdl_output_factory;
 #endif
 };
 
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index adc9706f14..ed9c3facf5 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -465,6 +465,8 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
         UpdateControllerEnabledButtons();
         UpdateControllerButtonNames();
         UpdateMotionButtons();
+        emulated_controller->SetNpadType(
+            GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()));
     });
 
     connect(ui->comboDevices, qOverload<int>(&QComboBox::activated), this,
@@ -540,6 +542,11 @@ void ConfigureInputPlayer::LoadConfiguration() {
 
 void ConfigureInputPlayer::ConnectPlayer(bool connected) {
     ui->groupConnectedController->setChecked(connected);
+    if (connected) {
+        emulated_controller->Connect();
+    } else {
+        emulated_controller->Disconnect();
+    }
 }
 
 void ConfigureInputPlayer::UpdateInputDeviceCombobox() {
diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp
index 03d29f194e..2ba9d7290c 100644
--- a/src/yuzu/configuration/configure_input_player_widget.cpp
+++ b/src/yuzu/configuration/configure_input_player_widget.cpp
@@ -24,34 +24,6 @@ PlayerControlPreview::~PlayerControlPreview() {
     }
 };
 
-PlayerControlPreview::LedPattern PlayerControlPreview::GetColorPattern(std::size_t index,
-                                                                       bool player_on) {
-    if (!player_on) {
-        return {0, 0, 0, 0};
-    }
-
-    switch (index) {
-    case 0:
-        return {1, 0, 0, 0};
-    case 1:
-        return {1, 1, 0, 0};
-    case 2:
-        return {1, 1, 1, 0};
-    case 3:
-        return {1, 1, 1, 1};
-    case 4:
-        return {1, 0, 0, 1};
-    case 5:
-        return {1, 0, 1, 0};
-    case 6:
-        return {1, 0, 1, 1};
-    case 7:
-        return {0, 1, 1, 0};
-    default:
-        return {0, 0, 0, 0};
-    }
-}
-
 void PlayerControlPreview::SetController(Core::HID::EmulatedController* controller_) {
     if (is_controller_set) {
         controller->DeleteCallback(callback_key);
@@ -160,8 +132,13 @@ void PlayerControlPreview::ControllerUpdate(Core::HID::ControllerTriggerType typ
 
     switch (type) {
     case Core::HID::ControllerTriggerType::Connected:
+        is_connected = true;
+        led_pattern = controller->GetLedPattern();
+        needs_redraw = true;
+        break;
     case Core::HID::ControllerTriggerType::Disconnected:
-        is_connected = controller->IsConnected();
+        is_connected = false;
+        led_pattern.raw = 0;
         needs_redraw = true;
         break;
     case Core::HID::ControllerTriggerType::Type:
@@ -1853,10 +1830,14 @@ void PlayerControlPreview::DrawLeftBody(QPainter& p, const QPointF center) {
     const float led_size = 5.0f;
     const QPointF led_position = sideview_center + QPointF(0, -36);
     int led_count = 0;
-    for (const auto& color : led_color) {
-        p.setBrush(color);
-        DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size);
-    }
+    p.setBrush(led_pattern.position1 ? colors.led_on : colors.led_off);
+    DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size);
+    p.setBrush(led_pattern.position2 ? colors.led_on : colors.led_off);
+    DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size);
+    p.setBrush(led_pattern.position3 ? colors.led_on : colors.led_off);
+    DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size);
+    p.setBrush(led_pattern.position4 ? colors.led_on : colors.led_off);
+    DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size);
 }
 
 void PlayerControlPreview::DrawRightBody(QPainter& p, const QPointF center) {
@@ -1949,10 +1930,14 @@ void PlayerControlPreview::DrawRightBody(QPainter& p, const QPointF center) {
     const float led_size = 5.0f;
     const QPointF led_position = sideview_center + QPointF(0, -36);
     int led_count = 0;
-    for (const auto& color : led_color) {
-        p.setBrush(color);
-        DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size);
-    }
+    p.setBrush(led_pattern.position1 ? colors.led_on : colors.led_off);
+    DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size);
+    p.setBrush(led_pattern.position2 ? colors.led_on : colors.led_off);
+    DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size);
+    p.setBrush(led_pattern.position3 ? colors.led_on : colors.led_off);
+    DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size);
+    p.setBrush(led_pattern.position4 ? colors.led_on : colors.led_off);
+    DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size);
 }
 
 void PlayerControlPreview::DrawProTriggers(QPainter& p, const QPointF center,
diff --git a/src/yuzu/configuration/configure_input_player_widget.h b/src/yuzu/configuration/configure_input_player_widget.h
index b44a2e347d..16f9748f54 100644
--- a/src/yuzu/configuration/configure_input_player_widget.h
+++ b/src/yuzu/configuration/configure_input_player_widget.h
@@ -59,13 +59,6 @@ private:
         SR,
     };
 
-    struct LedPattern {
-        bool position1;
-        bool position2;
-        bool position3;
-        bool position4;
-    };
-
     struct ColorMapping {
         QColor outline{};
         QColor primary{};
@@ -88,7 +81,6 @@ private:
         QColor deadzone{};
     };
 
-    static LedPattern GetColorPattern(std::size_t index, bool player_on);
     void UpdateColors();
     void ResetInputs();
 
@@ -194,7 +186,7 @@ private:
     int callback_key;
     QColor button_color{};
     ColorMapping colors{};
-    std::array<QColor, 4> led_color{};
+    Core::HID::LedPattern led_pattern{0, 0, 0, 0};
     std::size_t player_index{};
     Core::HID::EmulatedController* controller;
     std::size_t button_mapping_index{Settings::NativeButton::NumButtons};