diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 2b670ddfd0..56af075072 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -11,7 +11,6 @@
 #include "core/hle/service/acc/profile_manager.h"
 #include "core/hle/service/hid/controllers/npad.h"
 #include "input_common/main.h"
-#include "input_common/udp/client.h"
 #include "yuzu/configuration/config.h"
 
 namespace FS = Common::FS;
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 1599299db2..61513865f4 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -73,7 +73,7 @@ ConfigureInput::ConfigureInput(Core::System& system_, QWidget* parent)
 
 ConfigureInput::~ConfigureInput() = default;
 
-void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
+void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, Core::System& system,
                                 std::size_t max_players) {
     player_controllers = {
         new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem, profiles.get(),
@@ -184,22 +184,8 @@ QList<QWidget*> ConfigureInput::GetSubTabs() const {
 void ConfigureInput::ApplyConfiguration() {
     for (auto* controller : player_controllers) {
         controller->ApplyConfiguration();
-        controller->TryDisconnectSelectedController();
     }
 
-    // This emulates a delay between disconnecting and reconnecting controllers as some games
-    // do not respond to a change in controller type if it was instantaneous.
-    using namespace std::chrono_literals;
-    std::this_thread::sleep_for(150ms);
-
-    for (auto* controller : player_controllers) {
-        controller->TryConnectSelectedController();
-    }
-
-    // This emulates a delay between disconnecting and reconnecting controllers as some games
-    // do not respond to a change in controller type if it was instantaneous.
-    std::this_thread::sleep_for(150ms);
-
     advanced->ApplyConfiguration();
 
     const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue();
diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h
index 4cafa3dab7..6e5edad589 100644
--- a/src/yuzu/configuration/configure_input.h
+++ b/src/yuzu/configuration/configure_input.h
@@ -42,7 +42,8 @@ public:
     ~ConfigureInput() override;
 
     /// Initializes the input dialog with the given input subsystem.
-    void Initialize(InputCommon::InputSubsystem* input_subsystem_, std::size_t max_players = 8);
+    void Initialize(InputCommon::InputSubsystem* input_subsystem_, Core::System& system,
+                    std::size_t max_players = 8);
 
     /// Save all button configurations to settings file.
     void ApplyConfiguration();
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 09f0b219bf..adc9706f14 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -13,9 +13,11 @@
 #include <QTimer>
 #include "common/param_package.h"
 #include "core/core.h"
-#include "core/hle/service/hid/controllers/npad.h"
-#include "core/hle/service/hid/hid.h"
-#include "core/hle/service/sm/sm.h"
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
+#include "core/hid/hid_types.h"
+#include "input_common/drivers/keyboard.h"
+#include "input_common/drivers/mouse.h"
 #include "input_common/main.h"
 #include "ui_configure_input_player.h"
 #include "yuzu/bootmanager.h"
@@ -26,8 +28,6 @@
 #include "yuzu/configuration/input_profiles.h"
 #include "yuzu/util/limitable_input_dialog.h"
 
-using namespace Service::HID;
-
 const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM>
     ConfigureInputPlayer::analog_sub_buttons{{
         "up",
@@ -40,31 +40,8 @@ namespace {
 
 constexpr std::size_t HANDHELD_INDEX = 8;
 
-void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index,
-                      bool connected, Core::System& system) {
-    if (!system.IsPoweredOn()) {
-        return;
-    }
-    Service::SM::ServiceManager& sm = system.ServiceManager();
-
-    auto& npad = sm.GetService<Hid>("hid")->GetAppletResource()->GetController<Controller_NPad>(
-        HidController::NPad);
-
-    npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected);
-}
-
 QString GetKeyName(int key_code) {
     switch (key_code) {
-    case Qt::LeftButton:
-        return QObject::tr("Click 0");
-    case Qt::RightButton:
-        return QObject::tr("Click 1");
-    case Qt::MiddleButton:
-        return QObject::tr("Click 2");
-    case Qt::BackButton:
-        return QObject::tr("Click 3");
-    case Qt::ForwardButton:
-        return QObject::tr("Click 4");
     case Qt::Key_Shift:
         return QObject::tr("Shift");
     case Qt::Key_Control:
@@ -94,95 +71,26 @@ void SetAnalogParam(const Common::ParamPackage& input_param, Common::ParamPackag
     }
     analog_param.Set(button_name, input_param.Serialize());
 }
+} // namespace
 
-QString ButtonToText(const Common::ParamPackage& param) {
+QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) {
     if (!param.Has("engine")) {
         return QObject::tr("[not set]");
     }
 
+    // Retrieve the names from Qt
     if (param.Get("engine", "") == "keyboard") {
         const QString button_str = GetKeyName(param.Get("code", 0));
         const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : "");
         return QObject::tr("%1%2").arg(toggle, button_str);
     }
 
-    if (param.Get("engine", "") == "gcpad") {
-        if (param.Has("axis")) {
-            const QString axis_str = QString::fromStdString(param.Get("axis", ""));
-            const QString direction_str = QString::fromStdString(param.Get("direction", ""));
-
-            return QObject::tr("GC Axis %1%2").arg(axis_str, direction_str);
-        }
-        if (param.Has("button")) {
-            const QString button_str = QString::number(int(std::log2(param.Get("button", 0))));
-            return QObject::tr("GC Button %1").arg(button_str);
-        }
-        return GetKeyName(param.Get("code", 0));
-    }
-
-    if (param.Get("engine", "") == "tas") {
-        if (param.Has("axis")) {
-            const QString axis_str = QString::fromStdString(param.Get("axis", ""));
-
-            return QObject::tr("TAS Axis %1").arg(axis_str);
-        }
-        if (param.Has("button")) {
-            const QString button_str = QString::number(int(std::log2(param.Get("button", 0))));
-            return QObject::tr("TAS Btn %1").arg(button_str);
-        }
-        return GetKeyName(param.Get("code", 0));
-    }
-
-    if (param.Get("engine", "") == "cemuhookudp") {
-        if (param.Has("pad_index")) {
-            const QString motion_str = QString::fromStdString(param.Get("pad_index", ""));
-            return QObject::tr("Motion %1").arg(motion_str);
-        }
-        return GetKeyName(param.Get("code", 0));
-    }
-
-    if (param.Get("engine", "") == "sdl") {
-        if (param.Has("hat")) {
-            const QString hat_str = QString::fromStdString(param.Get("hat", ""));
-            const QString direction_str = QString::fromStdString(param.Get("direction", ""));
-
-            return QObject::tr("Hat %1 %2").arg(hat_str, direction_str);
-        }
-
-        if (param.Has("axis")) {
-            const QString axis_str = QString::fromStdString(param.Get("axis", ""));
-            const QString direction_str = QString::fromStdString(param.Get("direction", ""));
-
-            return QObject::tr("Axis %1%2").arg(axis_str, direction_str);
-        }
-
-        if (param.Has("button")) {
-            const QString button_str = QString::fromStdString(param.Get("button", ""));
-            const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : "");
-
-            return QObject::tr("%1Button %2").arg(toggle, button_str);
-        }
-
-        if (param.Has("motion")) {
-            return QObject::tr("SDL Motion");
-        }
-
-        return {};
-    }
-
-    if (param.Get("engine", "") == "mouse") {
-        if (param.Has("button")) {
-            const QString button_str = QString::number(int(param.Get("button", 0)));
-            const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : "");
-            return QObject::tr("%1Click %2").arg(toggle, button_str);
-        }
-        return GetKeyName(param.Get("code", 0));
-    }
-
-    return QObject::tr("[unknown]");
+    std::string button_name = input_subsystem->GetButtonName(param);
+    return QString::fromStdString(button_name);
 }
 
-QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) {
+QString ConfigureInputPlayer::AnalogToText(const Common::ParamPackage& param,
+                                           const std::string& dir) {
     if (!param.Has("engine")) {
         return QObject::tr("[not set]");
     }
@@ -191,39 +99,39 @@ QString AnalogToText(const Common::ParamPackage& param, const std::string& dir)
         return ButtonToText(Common::ParamPackage{param.Get(dir, "")});
     }
 
+    if (!param.Has("axis_x") || !param.Has("axis_y")) {
+        return QObject::tr("[unknown]");
+    }
+
     const auto engine_str = param.Get("engine", "");
     const QString axis_x_str = QString::fromStdString(param.Get("axis_x", ""));
     const QString axis_y_str = QString::fromStdString(param.Get("axis_y", ""));
     const bool invert_x = param.Get("invert_x", "+") == "-";
     const bool invert_y = param.Get("invert_y", "+") == "-";
-    if (engine_str == "sdl" || engine_str == "gcpad" || engine_str == "mouse" ||
-        engine_str == "tas") {
-        if (dir == "modifier") {
-            return QObject::tr("[unused]");
-        }
 
-        if (dir == "left") {
-            const QString invert_x_str = QString::fromStdString(invert_x ? "+" : "-");
-            return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str);
-        }
-        if (dir == "right") {
-            const QString invert_x_str = QString::fromStdString(invert_x ? "-" : "+");
-            return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str);
-        }
-        if (dir == "up") {
-            const QString invert_y_str = QString::fromStdString(invert_y ? "-" : "+");
-            return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str);
-        }
-        if (dir == "down") {
-            const QString invert_y_str = QString::fromStdString(invert_y ? "+" : "-");
-            return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str);
-        }
-
-        return {};
+    if (dir == "modifier") {
+        return QObject::tr("[unused]");
     }
+
+    if (dir == "left") {
+        const QString invert_x_str = QString::fromStdString(invert_x ? "+" : "-");
+        return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str);
+    }
+    if (dir == "right") {
+        const QString invert_x_str = QString::fromStdString(invert_x ? "-" : "+");
+        return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str);
+    }
+    if (dir == "up") {
+        const QString invert_y_str = QString::fromStdString(invert_y ? "-" : "+");
+        return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str);
+    }
+    if (dir == "down") {
+        const QString invert_y_str = QString::fromStdString(invert_y ? "+" : "-");
+        return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str);
+    }
+
     return QObject::tr("[unknown]");
 }
-} // namespace
 
 ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index,
                                            QWidget* bottom_row,
@@ -234,6 +142,9 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
       debug(debug), input_subsystem{input_subsystem_}, profiles(profiles_),
       timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()),
       bottom_row(bottom_row), system{system_} {
+
+    emulated_controller = system_.HIDCore().GetEmulatedControllerByIndex(player_index);
+    emulated_controller->EnableConfiguration();
     ui->setupUi(this);
 
     setFocusPolicy(Qt::ClickFocus);
@@ -275,31 +186,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
     analog_map_range_groupbox = {ui->buttonLStickRangeGroup, ui->buttonRStickRangeGroup};
     analog_map_range_spinbox = {ui->spinboxLStickRange, ui->spinboxRStickRange};
 
-    const auto ConfigureButtonClick = [&](QPushButton* button, std::size_t button_id,
-                                          Common::ParamPackage* param, int default_val,
-                                          InputCommon::Polling::DeviceType type) {
-        connect(button, &QPushButton::clicked, [=, this] {
-            HandleClick(
-                button, button_id,
-                [=, this](Common::ParamPackage params) {
-                    // Workaround for ZL & ZR for analog triggers like on XBOX
-                    // controllers. Analog triggers (from controllers like the XBOX
-                    // controller) would not work due to a different range of their
-                    // signals (from 0 to 255 on analog triggers instead of -32768 to
-                    // 32768 on analog joysticks). The SDL driver misinterprets analog
-                    // triggers as analog joysticks.
-                    // TODO: reinterpret the signal range for analog triggers to map the
-                    // values correctly. This is required for the correct emulation of
-                    // the analog triggers of the GameCube controller.
-                    if (button == ui->buttonZL || button == ui->buttonZR) {
-                        params.Set("direction", "+");
-                        params.Set("threshold", "0.5");
-                    }
-                    *param = std::move(params);
-                },
-                type);
-        });
-    };
+    ui->controllerFrame->SetController(emulated_controller);
 
     for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
         auto* const button = button_map[button_id];
@@ -308,34 +195,52 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
             continue;
         }
 
-        ConfigureButtonClick(button_map[button_id], button_id, &buttons_param[button_id],
-                             Config::default_buttons[button_id],
-                             InputCommon::Polling::DeviceType::Button);
+        connect(button, &QPushButton::clicked, [=, this] {
+            HandleClick(
+                button, button_id,
+                [=, this](Common::ParamPackage params) {
+                    emulated_controller->SetButtonParam(button_id, params);
+                },
+                InputCommon::Polling::InputType::Button);
+        });
 
         button->setContextMenuPolicy(Qt::CustomContextMenu);
         connect(button, &QPushButton::customContextMenuRequested,
                 [=, this](const QPoint& menu_location) {
                     QMenu context_menu;
+                    Common::ParamPackage param = emulated_controller->GetButtonParam(button_id);
                     context_menu.addAction(tr("Clear"), [&] {
-                        buttons_param[button_id].Clear();
+                        emulated_controller->SetButtonParam(button_id, {});
                         button_map[button_id]->setText(tr("[not set]"));
                     });
-                    if (buttons_param[button_id].Has("toggle")) {
+                    if (param.Has("button") || param.Has("hat")) {
                         context_menu.addAction(tr("Toggle button"), [&] {
-                            const bool toggle_value =
-                                !buttons_param[button_id].Get("toggle", false);
-                            buttons_param[button_id].Set("toggle", toggle_value);
-                            button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
+                            const bool toggle_value = !param.Get("toggle", false);
+                            param.Set("toggle", toggle_value);
+                            button_map[button_id]->setText(ButtonToText(param));
+                            emulated_controller->SetButtonParam(button_id, param);
+                        });
+                        context_menu.addAction(tr("Invert button"), [&] {
+                            const bool toggle_value = !param.Get("inverted", false);
+                            param.Set("inverted", toggle_value);
+                            button_map[button_id]->setText(ButtonToText(param));
+                            emulated_controller->SetButtonParam(button_id, param);
                         });
                     }
-                    if (buttons_param[button_id].Has("threshold")) {
+                    if (param.Has("axis")) {
+                        context_menu.addAction(tr("Invert axis"), [&] {
+                            const bool toggle_value = !(param.Get("invert", "+") == "-");
+                            param.Set("invert", toggle_value ? "-" : "+");
+                            button_map[button_id]->setText(ButtonToText(param));
+                            emulated_controller->SetButtonParam(button_id, param);
+                        });
                         context_menu.addAction(tr("Set threshold"), [&] {
-                            const int button_threshold = static_cast<int>(
-                                buttons_param[button_id].Get("threshold", 0.5f) * 100.0f);
+                            const int button_threshold =
+                                static_cast<int>(param.Get("threshold", 0.5f) * 100.0f);
                             const int new_threshold = QInputDialog::getInt(
                                 this, tr("Set threshold"), tr("Choose a value between 0% and 100%"),
                                 button_threshold, 0, 100);
-                            buttons_param[button_id].Set("threshold", new_threshold / 100.0f);
+                            param.Set("threshold", new_threshold / 100.0f);
 
                             if (button_id == Settings::NativeButton::ZL) {
                                 ui->sliderZLThreshold->setValue(new_threshold);
@@ -343,11 +248,10 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
                             if (button_id == Settings::NativeButton::ZR) {
                                 ui->sliderZRThreshold->setValue(new_threshold);
                             }
+                            emulated_controller->SetButtonParam(button_id, param);
                         });
                     }
-
                     context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
-                    ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param);
                 });
     }
 
@@ -357,9 +261,14 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
             continue;
         }
 
-        ConfigureButtonClick(motion_map[motion_id], motion_id, &motions_param[motion_id],
-                             Config::default_motions[motion_id],
-                             InputCommon::Polling::DeviceType::Motion);
+        connect(button, &QPushButton::clicked, [=, this] {
+            HandleClick(
+                button, motion_id,
+                [=, this](Common::ParamPackage params) {
+                    emulated_controller->SetMotionParam(motion_id, params);
+                },
+                InputCommon::Polling::InputType::Motion);
+        });
 
         button->setContextMenuPolicy(Qt::CustomContextMenu);
 
@@ -367,7 +276,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
                 [=, this](const QPoint& menu_location) {
                     QMenu context_menu;
                     context_menu.addAction(tr("Clear"), [&] {
-                        motions_param[motion_id].Clear();
+                        emulated_controller->SetMotionParam(motion_id, {});
                         motion_map[motion_id]->setText(tr("[not set]"));
                     });
                     context_menu.exec(motion_map[motion_id]->mapToGlobal(menu_location));
@@ -375,16 +284,22 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
     }
 
     connect(ui->sliderZLThreshold, &QSlider::valueChanged, [=, this] {
-        if (buttons_param[Settings::NativeButton::ZL].Has("threshold")) {
+        Common::ParamPackage param =
+            emulated_controller->GetButtonParam(Settings::NativeButton::ZL);
+        if (param.Has("threshold")) {
             const auto slider_value = ui->sliderZLThreshold->value();
-            buttons_param[Settings::NativeButton::ZL].Set("threshold", slider_value / 100.0f);
+            param.Set("threshold", slider_value / 100.0f);
+            emulated_controller->SetButtonParam(Settings::NativeButton::ZL, param);
         }
     });
 
     connect(ui->sliderZRThreshold, &QSlider::valueChanged, [=, this] {
-        if (buttons_param[Settings::NativeButton::ZR].Has("threshold")) {
+        Common::ParamPackage param =
+            emulated_controller->GetButtonParam(Settings::NativeButton::ZR);
+        if (param.Has("threshold")) {
             const auto slider_value = ui->sliderZRThreshold->value();
-            buttons_param[Settings::NativeButton::ZR].Set("threshold", slider_value / 100.0f);
+            param.Set("threshold", slider_value / 100.0f);
+            emulated_controller->SetButtonParam(Settings::NativeButton::ZR, param);
         }
     });
 
@@ -412,45 +327,45 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
                 HandleClick(
                     analog_map_buttons[analog_id][sub_button_id], analog_id,
                     [=, this](const Common::ParamPackage& params) {
-                        SetAnalogParam(params, analogs_param[analog_id],
-                                       analog_sub_buttons[sub_button_id]);
+                        Common::ParamPackage param = emulated_controller->GetStickParam(analog_id);
+                        SetAnalogParam(params, param, analog_sub_buttons[sub_button_id]);
+                        emulated_controller->SetStickParam(analog_id, param);
                     },
-                    InputCommon::Polling::DeviceType::AnalogPreferred);
+                    InputCommon::Polling::InputType::Stick);
             });
 
             analog_button->setContextMenuPolicy(Qt::CustomContextMenu);
 
-            connect(
-                analog_button, &QPushButton::customContextMenuRequested,
-                [=, this](const QPoint& menu_location) {
-                    QMenu context_menu;
-                    context_menu.addAction(tr("Clear"), [&] {
-                        analogs_param[analog_id].Clear();
-                        analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
+            connect(analog_button, &QPushButton::customContextMenuRequested,
+                    [=, this](const QPoint& menu_location) {
+                        QMenu context_menu;
+                        Common::ParamPackage param = emulated_controller->GetStickParam(analog_id);
+                        context_menu.addAction(tr("Clear"), [&] {
+                            emulated_controller->SetStickParam(analog_id, {});
+                            analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
+                        });
+                        context_menu.addAction(tr("Invert axis"), [&] {
+                            if (sub_button_id == 2 || sub_button_id == 3) {
+                                const bool invert_value = param.Get("invert_x", "+") == "-";
+                                const std::string invert_str = invert_value ? "+" : "-";
+                                param.Set("invert_x", invert_str);
+                                emulated_controller->SetStickParam(analog_id, param);
+                            }
+                            if (sub_button_id == 0 || sub_button_id == 1) {
+                                const bool invert_value = param.Get("invert_y", "+") == "-";
+                                const std::string invert_str = invert_value ? "+" : "-";
+                                param.Set("invert_y", invert_str);
+                                emulated_controller->SetStickParam(analog_id, param);
+                            }
+                            for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM;
+                                 ++sub_button_id) {
+                                analog_map_buttons[analog_id][sub_button_id]->setText(
+                                    AnalogToText(param, analog_sub_buttons[sub_button_id]));
+                            }
+                        });
+                        context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(
+                            menu_location));
                     });
-                    context_menu.addAction(tr("Invert axis"), [&] {
-                        if (sub_button_id == 2 || sub_button_id == 3) {
-                            const bool invert_value =
-                                analogs_param[analog_id].Get("invert_x", "+") == "-";
-                            const std::string invert_str = invert_value ? "+" : "-";
-                            analogs_param[analog_id].Set("invert_x", invert_str);
-                        }
-                        if (sub_button_id == 0 || sub_button_id == 1) {
-                            const bool invert_value =
-                                analogs_param[analog_id].Get("invert_y", "+") == "-";
-                            const std::string invert_str = invert_value ? "+" : "-";
-                            analogs_param[analog_id].Set("invert_y", invert_str);
-                        }
-                        for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM;
-                             ++sub_button_id) {
-                            analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText(
-                                analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
-                        }
-                    });
-                    context_menu.exec(
-                        analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(menu_location));
-                    ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param);
-                });
         }
 
         // Handle clicks for the modifier buttons as well.
@@ -458,9 +373,11 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
             HandleClick(
                 analog_map_modifier_button[analog_id], analog_id,
                 [=, this](const Common::ParamPackage& params) {
-                    analogs_param[analog_id].Set("modifier", params.Serialize());
+                    Common::ParamPackage param = emulated_controller->GetStickParam(analog_id);
+                    param.Set("modifier", params.Serialize());
+                    emulated_controller->SetStickParam(analog_id, param);
                 },
-                InputCommon::Polling::DeviceType::Button);
+                InputCommon::Polling::InputType::Button);
         });
 
         analog_map_modifier_button[analog_id]->setContextMenuPolicy(Qt::CustomContextMenu);
@@ -468,18 +385,21 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
         connect(analog_map_modifier_button[analog_id], &QPushButton::customContextMenuRequested,
                 [=, this](const QPoint& menu_location) {
                     QMenu context_menu;
+                    Common::ParamPackage param = emulated_controller->GetStickParam(analog_id);
                     context_menu.addAction(tr("Clear"), [&] {
-                        analogs_param[analog_id].Set("modifier", "");
+                        param.Set("modifier", "");
                         analog_map_modifier_button[analog_id]->setText(tr("[not set]"));
+                        emulated_controller->SetStickParam(analog_id, param);
                     });
                     context_menu.addAction(tr("Toggle button"), [&] {
                         Common::ParamPackage modifier_param =
-                            Common::ParamPackage{analogs_param[analog_id].Get("modifier", "")};
+                            Common::ParamPackage{param.Get("modifier", "")};
                         const bool toggle_value = !modifier_param.Get("toggle", false);
                         modifier_param.Set("toggle", toggle_value);
-                        analogs_param[analog_id].Set("modifier", modifier_param.Serialize());
+                        param.Set("modifier", modifier_param.Serialize());
                         analog_map_modifier_button[analog_id]->setText(
                             ButtonToText(modifier_param));
+                        emulated_controller->SetStickParam(analog_id, param);
                     });
                     context_menu.exec(
                         analog_map_modifier_button[analog_id]->mapToGlobal(menu_location));
@@ -487,37 +407,39 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
 
         connect(analog_map_range_spinbox[analog_id], qOverload<int>(&QSpinBox::valueChanged),
                 [=, this] {
+                    Common::ParamPackage param = emulated_controller->GetStickParam(analog_id);
                     const auto spinbox_value = analog_map_range_spinbox[analog_id]->value();
-                    analogs_param[analog_id].Set("range", spinbox_value / 100.0f);
-                    ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param);
+                    param.Set("range", spinbox_value / 100.0f);
+                    emulated_controller->SetStickParam(analog_id, param);
                 });
 
         connect(analog_map_deadzone_slider[analog_id], &QSlider::valueChanged, [=, this] {
+            Common::ParamPackage param = emulated_controller->GetStickParam(analog_id);
             const auto slider_value = analog_map_deadzone_slider[analog_id]->value();
             analog_map_deadzone_label[analog_id]->setText(tr("Deadzone: %1%").arg(slider_value));
-            analogs_param[analog_id].Set("deadzone", slider_value / 100.0f);
-            ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param);
+            param.Set("deadzone", slider_value / 100.0f);
+            emulated_controller->SetStickParam(analog_id, param);
         });
 
         connect(analog_map_modifier_slider[analog_id], &QSlider::valueChanged, [=, this] {
+            Common::ParamPackage param = emulated_controller->GetStickParam(analog_id);
             const auto slider_value = analog_map_modifier_slider[analog_id]->value();
             analog_map_modifier_label[analog_id]->setText(
                 tr("Modifier Range: %1%").arg(slider_value));
-            analogs_param[analog_id].Set("modifier_scale", slider_value / 100.0f);
+            param.Set("modifier_scale", slider_value / 100.0f);
+            emulated_controller->SetStickParam(analog_id, param);
         });
     }
 
     // Player Connected checkbox
-    connect(ui->groupConnectedController, &QGroupBox::toggled, [this](bool checked) {
-        emit Connected(checked);
-        ui->controllerFrame->SetConnectedStatus(checked);
-    });
+    connect(ui->groupConnectedController, &QGroupBox::toggled,
+            [this](bool checked) { emit Connected(checked); });
 
     if (player_index == 0) {
         connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged),
                 [this](int index) {
                     emit HandheldStateChanged(GetControllerTypeFromIndex(index) ==
-                                              Settings::ControllerType::Handheld);
+                                              Core::HID::NpadType::Handheld);
                 });
     }
 
@@ -534,13 +456,11 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
         SetConnectableControllers();
     }
 
-    UpdateControllerIcon();
     UpdateControllerAvailableButtons();
     UpdateControllerEnabledButtons();
     UpdateControllerButtonNames();
     UpdateMotionButtons();
     connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) {
-        UpdateControllerIcon();
         UpdateControllerAvailableButtons();
         UpdateControllerEnabledButtons();
         UpdateControllerButtonNames();
@@ -560,13 +480,10 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
     connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
 
     connect(poll_timer.get(), &QTimer::timeout, [this] {
-        Common::ParamPackage params;
-        for (auto& poller : device_pollers) {
-            params = poller->GetNextInput();
-            if (params.Has("engine") && IsInputAcceptable(params)) {
-                SetPollingResult(params, false);
-                return;
-            }
+        const auto& params = input_subsystem->GetNextInput();
+        if (params.Has("engine") && IsInputAcceptable(params)) {
+            SetPollingResult(params, false);
+            return;
         }
     });
 
@@ -582,110 +499,14 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
             &ConfigureInputPlayer::SaveProfile);
 
     LoadConfiguration();
-    ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param);
-    ui->controllerFrame->SetConnectedStatus(ui->groupConnectedController->isChecked());
 }
 
-ConfigureInputPlayer::~ConfigureInputPlayer() = default;
+ConfigureInputPlayer::~ConfigureInputPlayer() {
+    emulated_controller->DisableConfiguration();
+};
 
 void ConfigureInputPlayer::ApplyConfiguration() {
-    auto& player = Settings::values.players.GetValue()[player_index];
-    auto& buttons = debug ? Settings::values.debug_pad_buttons : player.buttons;
-    auto& analogs = debug ? Settings::values.debug_pad_analogs : player.analogs;
-
-    std::transform(buttons_param.begin(), buttons_param.end(), buttons.begin(),
-                   [](const Common::ParamPackage& param) { return param.Serialize(); });
-    std::transform(analogs_param.begin(), analogs_param.end(), analogs.begin(),
-                   [](const Common::ParamPackage& param) { return param.Serialize(); });
-
-    if (debug) {
-        return;
-    }
-
-    auto& motions = player.motions;
-
-    std::transform(motions_param.begin(), motions_param.end(), motions.begin(),
-                   [](const Common::ParamPackage& param) { return param.Serialize(); });
-
-    // Apply configuration for handheld
-    if (player_index == 0) {
-        auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX];
-        const auto handheld_connected = handheld.connected;
-        handheld = player;
-        handheld.connected = handheld_connected;
-    }
-}
-
-void ConfigureInputPlayer::TryConnectSelectedController() {
-    auto& player = Settings::values.players.GetValue()[player_index];
-
-    const auto controller_type =
-        GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
-    const auto player_connected = ui->groupConnectedController->isChecked() &&
-                                  controller_type != Settings::ControllerType::Handheld;
-
-    // Connect Handheld depending on Player 1's controller configuration.
-    if (player_index == 0) {
-        auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX];
-        const auto handheld_connected = ui->groupConnectedController->isChecked() &&
-                                        controller_type == Settings::ControllerType::Handheld;
-        // Connect only if handheld is going from disconnected to connected
-        if (!handheld.connected && handheld_connected) {
-            UpdateController(controller_type, HANDHELD_INDEX, true, system);
-        }
-        handheld.connected = handheld_connected;
-    }
-
-    if (player.controller_type == controller_type && player.connected == player_connected) {
-        // Set vibration devices in the event that the input device has changed.
-        ConfigureVibration::SetVibrationDevices(player_index);
-        return;
-    }
-
-    player.controller_type = controller_type;
-    player.connected = player_connected;
-
-    ConfigureVibration::SetVibrationDevices(player_index);
-
-    if (!player.connected) {
-        return;
-    }
-
-    UpdateController(controller_type, player_index, true, system);
-}
-
-void ConfigureInputPlayer::TryDisconnectSelectedController() {
-    const auto& player = Settings::values.players.GetValue()[player_index];
-
-    const auto controller_type =
-        GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
-    const auto player_connected = ui->groupConnectedController->isChecked() &&
-                                  controller_type != Settings::ControllerType::Handheld;
-
-    // Disconnect Handheld depending on Player 1's controller configuration.
-    if (player_index == 0 && player.controller_type == Settings::ControllerType::Handheld) {
-        const auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX];
-        const auto handheld_connected = ui->groupConnectedController->isChecked() &&
-                                        controller_type == Settings::ControllerType::Handheld;
-        // Disconnect only if handheld is going from connected to disconnected
-        if (handheld.connected && !handheld_connected) {
-            UpdateController(controller_type, HANDHELD_INDEX, false, system);
-        }
-        return;
-    }
-
-    // Do not do anything if the controller configuration has not changed.
-    if (player.controller_type == controller_type && player.connected == player_connected) {
-        return;
-    }
-
-    // Do not disconnect if the controller is already disconnected
-    if (!player.connected) {
-        return;
-    }
-
-    // Disconnect the controller first.
-    UpdateController(controller_type, player_index, false, system);
+    emulated_controller->SaveCurrentConfig();
 }
 
 void ConfigureInputPlayer::showEvent(QShowEvent* event) {
@@ -710,34 +531,11 @@ void ConfigureInputPlayer::RetranslateUI() {
 }
 
 void ConfigureInputPlayer::LoadConfiguration() {
-    auto& player = Settings::values.players.GetValue()[player_index];
-    if (debug) {
-        std::transform(Settings::values.debug_pad_buttons.begin(),
-                       Settings::values.debug_pad_buttons.end(), buttons_param.begin(),
-                       [](const std::string& str) { return Common::ParamPackage(str); });
-        std::transform(Settings::values.debug_pad_analogs.begin(),
-                       Settings::values.debug_pad_analogs.end(), analogs_param.begin(),
-                       [](const std::string& str) { return Common::ParamPackage(str); });
-    } else {
-        std::transform(player.buttons.begin(), player.buttons.end(), buttons_param.begin(),
-                       [](const std::string& str) { return Common::ParamPackage(str); });
-        std::transform(player.analogs.begin(), player.analogs.end(), analogs_param.begin(),
-                       [](const std::string& str) { return Common::ParamPackage(str); });
-        std::transform(player.motions.begin(), player.motions.end(), motions_param.begin(),
-                       [](const std::string& str) { return Common::ParamPackage(str); });
-    }
-
     UpdateUI();
     UpdateInputDeviceCombobox();
-
-    if (debug) {
-        return;
-    }
-
-    ui->comboControllerType->setCurrentIndex(GetIndexFromControllerType(player.controller_type));
-    ui->groupConnectedController->setChecked(
-        player.connected ||
-        (player_index == 0 && Settings::values.players.GetValue()[HANDHELD_INDEX].connected));
+    const int comboBoxIndex = GetIndexFromControllerType(emulated_controller->GetNpadType());
+    ui->comboControllerType->setCurrentIndex(comboBoxIndex);
+    ui->groupConnectedController->setChecked(emulated_controller->IsConnected());
 }
 
 void ConfigureInputPlayer::ConnectPlayer(bool connected) {
@@ -751,48 +549,63 @@ void ConfigureInputPlayer::UpdateInputDeviceCombobox() {
         return;
     }
 
-    // Find the first button that isn't empty.
-    const auto button_param =
-        std::find_if(buttons_param.begin(), buttons_param.end(),
-                     [](const Common::ParamPackage param) { return param.Has("engine"); });
-    const bool buttons_empty = button_param == buttons_param.end();
-
-    const auto current_engine = button_param->Get("engine", "");
-    const auto current_guid = button_param->Get("guid", "");
-    const auto current_port = button_param->Get("port", "");
-
-    const bool is_keyboard_mouse = current_engine == "keyboard" || current_engine == "mouse";
-
+    const auto devices = emulated_controller->GetMappedDevices();
     UpdateInputDevices();
 
-    if (buttons_empty) {
+    if (devices.empty()) {
         return;
     }
 
-    const bool all_one_device =
-        std::all_of(buttons_param.begin(), buttons_param.end(),
-                    [current_engine, current_guid, current_port,
-                     is_keyboard_mouse](const Common::ParamPackage param) {
-                        if (is_keyboard_mouse) {
-                            return !param.Has("engine") || param.Get("engine", "") == "keyboard" ||
-                                   param.Get("engine", "") == "mouse";
-                        }
-                        return !param.Has("engine") || (param.Get("engine", "") == current_engine &&
-                                                        param.Get("guid", "") == current_guid &&
-                                                        param.Get("port", "") == current_port);
-                    });
+    if (devices.size() > 2) {
+        ui->comboDevices->setCurrentIndex(0);
+        return;
+    }
 
-    if (all_one_device) {
-        if (is_keyboard_mouse) {
-            ui->comboDevices->setCurrentIndex(1);
-            return;
-        }
+    const auto first_engine = devices[0].Get("engine", "");
+    const auto first_guid = devices[0].Get("guid", "");
+    const auto first_port = devices[0].Get("port", "");
+
+    if (devices.size() == 1) {
+        const auto devices_it =
+            std::find_if(input_devices.begin(), input_devices.end(),
+                         [first_engine, first_guid, first_port](const Common::ParamPackage param) {
+                             return param.Get("engine", "") == first_engine &&
+                                    param.Get("guid", "") == first_guid &&
+                                    param.Get("port", "") == first_port;
+                         });
+        const int device_index =
+            devices_it != input_devices.end()
+                ? static_cast<int>(std::distance(input_devices.begin(), devices_it))
+                : 0;
+        ui->comboDevices->setCurrentIndex(device_index);
+        return;
+    }
+
+    const auto second_engine = devices[1].Get("engine", "");
+    const auto second_guid = devices[1].Get("guid", "");
+    const auto second_port = devices[1].Get("port", "");
+
+    const bool is_keyboard_mouse = (first_engine == "keyboard" || first_engine == "mouse") &&
+                                   (second_engine == "keyboard" || second_engine == "mouse");
+
+    if (is_keyboard_mouse) {
+        ui->comboDevices->setCurrentIndex(2);
+        return;
+    }
+
+    const bool is_engine_equal = first_engine == second_engine;
+    const bool is_port_equal = first_port == second_port;
+
+    if (is_engine_equal && is_port_equal) {
         const auto devices_it = std::find_if(
             input_devices.begin(), input_devices.end(),
-            [current_engine, current_guid, current_port](const Common::ParamPackage param) {
-                return param.Get("class", "") == current_engine &&
-                       param.Get("guid", "") == current_guid &&
-                       param.Get("port", "") == current_port;
+            [first_engine, first_guid, second_guid, first_port](const Common::ParamPackage param) {
+                const bool is_guid_valid =
+                    (param.Get("guid", "") == first_guid &&
+                     param.Get("guid2", "") == second_guid) ||
+                    (param.Get("guid", "") == second_guid && param.Get("guid2", "") == first_guid);
+                return param.Get("engine", "") == first_engine && is_guid_valid &&
+                       param.Get("port", "") == first_port;
             });
         const int device_index =
             devices_it != input_devices.end()
@@ -814,8 +627,7 @@ void ConfigureInputPlayer::ClearAll() {
         if (button == nullptr) {
             continue;
         }
-
-        buttons_param[button_id].Clear();
+        emulated_controller->SetButtonParam(button_id, {});
     }
 
     for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
@@ -824,8 +636,7 @@ void ConfigureInputPlayer::ClearAll() {
             if (analog_button == nullptr) {
                 continue;
             }
-
-            analogs_param[analog_id].Clear();
+            emulated_controller->SetStickParam(analog_id, {});
         }
     }
 
@@ -834,8 +645,7 @@ void ConfigureInputPlayer::ClearAll() {
         if (motion_button == nullptr) {
             continue;
         }
-
-        motions_param[motion_id].Clear();
+        emulated_controller->SetMotionParam(motion_id, {});
     }
 
     UpdateUI();
@@ -844,26 +654,31 @@ void ConfigureInputPlayer::ClearAll() {
 
 void ConfigureInputPlayer::UpdateUI() {
     for (int button = 0; button < Settings::NativeButton::NumButtons; ++button) {
-        button_map[button]->setText(ButtonToText(buttons_param[button]));
+        const Common::ParamPackage param = emulated_controller->GetButtonParam(button);
+        button_map[button]->setText(ButtonToText(param));
     }
 
-    if (buttons_param[Settings::NativeButton::ZL].Has("threshold")) {
-        const int button_threshold = static_cast<int>(
-            buttons_param[Settings::NativeButton::ZL].Get("threshold", 0.5f) * 100.0f);
+    const Common::ParamPackage ZL_param =
+        emulated_controller->GetButtonParam(Settings::NativeButton::ZL);
+    if (ZL_param.Has("threshold")) {
+        const int button_threshold = static_cast<int>(ZL_param.Get("threshold", 0.5f) * 100.0f);
         ui->sliderZLThreshold->setValue(button_threshold);
     }
 
-    if (buttons_param[Settings::NativeButton::ZR].Has("threshold")) {
-        const int button_threshold = static_cast<int>(
-            buttons_param[Settings::NativeButton::ZR].Get("threshold", 0.5f) * 100.0f);
+    const Common::ParamPackage ZR_param =
+        emulated_controller->GetButtonParam(Settings::NativeButton::ZR);
+    if (ZR_param.Has("threshold")) {
+        const int button_threshold = static_cast<int>(ZR_param.Get("threshold", 0.5f) * 100.0f);
         ui->sliderZRThreshold->setValue(button_threshold);
     }
 
     for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
-        motion_map[motion_id]->setText(ButtonToText(motions_param[motion_id]));
+        const Common::ParamPackage param = emulated_controller->GetMotionParam(motion_id);
+        motion_map[motion_id]->setText(ButtonToText(param));
     }
 
     for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
+        const Common::ParamPackage param = emulated_controller->GetStickParam(analog_id);
         for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
             auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
 
@@ -871,12 +686,11 @@ void ConfigureInputPlayer::UpdateUI() {
                 continue;
             }
 
-            analog_button->setText(
-                AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
+            analog_button->setText(AnalogToText(param, analog_sub_buttons[sub_button_id]));
         }
 
         analog_map_modifier_button[analog_id]->setText(
-            ButtonToText(Common::ParamPackage{analogs_param[analog_id].Get("modifier", "")}));
+            ButtonToText(Common::ParamPackage{param.Get("modifier", "")}));
 
         const auto deadzone_label = analog_map_deadzone_label[analog_id];
         const auto deadzone_slider = analog_map_deadzone_slider[analog_id];
@@ -887,26 +701,14 @@ void ConfigureInputPlayer::UpdateUI() {
         const auto range_spinbox = analog_map_range_spinbox[analog_id];
 
         int slider_value;
-        auto& param = analogs_param[analog_id];
-        const bool is_controller =
-            param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad" ||
-            param.Get("engine", "") == "mouse" || param.Get("engine", "") == "tas";
+        const bool is_controller = input_subsystem->IsController(param);
 
         if (is_controller) {
-            if (!param.Has("deadzone")) {
-                param.Set("deadzone", 0.1f);
-            }
-            slider_value = static_cast<int>(param.Get("deadzone", 0.1f) * 100);
+            slider_value = static_cast<int>(param.Get("deadzone", 0.15f) * 100);
             deadzone_label->setText(tr("Deadzone: %1%").arg(slider_value));
             deadzone_slider->setValue(slider_value);
-            if (!param.Has("range")) {
-                param.Set("range", 1.0f);
-            }
             range_spinbox->setValue(static_cast<int>(param.Get("range", 1.0f) * 100));
         } else {
-            if (!param.Has("modifier_scale")) {
-                param.Set("modifier_scale", 0.5f);
-            }
             slider_value = static_cast<int>(param.Get("modifier_scale", 0.5f) * 100);
             modifier_label->setText(tr("Modifier Range: %1%").arg(slider_value));
             modifier_slider->setValue(slider_value);
@@ -918,49 +720,48 @@ void ConfigureInputPlayer::UpdateUI() {
         modifier_label->setVisible(!is_controller);
         modifier_slider->setVisible(!is_controller);
         range_groupbox->setVisible(is_controller);
-        ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param);
     }
 }
 
 void ConfigureInputPlayer::SetConnectableControllers() {
     const auto add_controllers = [this](bool enable_all,
-                                        Controller_NPad::NpadStyleSet npad_style_set = {}) {
+                                        Core::HID::NpadStyleTag npad_style_set = {}) {
         index_controller_type_pairs.clear();
         ui->comboControllerType->clear();
 
         if (enable_all || npad_style_set.fullkey == 1) {
             index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
-                                                     Settings::ControllerType::ProController);
+                                                     Core::HID::NpadType::ProController);
             ui->comboControllerType->addItem(tr("Pro Controller"));
         }
 
         if (enable_all || npad_style_set.joycon_dual == 1) {
             index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
-                                                     Settings::ControllerType::DualJoyconDetached);
+                                                     Core::HID::NpadType::JoyconDual);
             ui->comboControllerType->addItem(tr("Dual Joycons"));
         }
 
         if (enable_all || npad_style_set.joycon_left == 1) {
             index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
-                                                     Settings::ControllerType::LeftJoycon);
+                                                     Core::HID::NpadType::JoyconLeft);
             ui->comboControllerType->addItem(tr("Left Joycon"));
         }
 
         if (enable_all || npad_style_set.joycon_right == 1) {
             index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
-                                                     Settings::ControllerType::RightJoycon);
+                                                     Core::HID::NpadType::JoyconRight);
             ui->comboControllerType->addItem(tr("Right Joycon"));
         }
 
         if (player_index == 0 && (enable_all || npad_style_set.handheld == 1)) {
             index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
-                                                     Settings::ControllerType::Handheld);
+                                                     Core::HID::NpadType::Handheld);
             ui->comboControllerType->addItem(tr("Handheld"));
         }
 
         if (enable_all || npad_style_set.gamecube == 1) {
             index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
-                                                     Settings::ControllerType::GameCube);
+                                                     Core::HID::NpadType::GameCube);
             ui->comboControllerType->addItem(tr("GameCube Controller"));
         }
     };
@@ -970,27 +771,22 @@ void ConfigureInputPlayer::SetConnectableControllers() {
         return;
     }
 
-    Service::SM::ServiceManager& sm = system.ServiceManager();
-
-    auto& npad = sm.GetService<Hid>("hid")->GetAppletResource()->GetController<Controller_NPad>(
-        HidController::NPad);
-
-    add_controllers(false, npad.GetSupportedStyleSet());
+    add_controllers(false, system.HIDCore().GetSupportedStyleTag());
 }
 
-Settings::ControllerType ConfigureInputPlayer::GetControllerTypeFromIndex(int index) const {
+Core::HID::NpadType ConfigureInputPlayer::GetControllerTypeFromIndex(int index) const {
     const auto it =
         std::find_if(index_controller_type_pairs.begin(), index_controller_type_pairs.end(),
                      [index](const auto& pair) { return pair.first == index; });
 
     if (it == index_controller_type_pairs.end()) {
-        return Settings::ControllerType::ProController;
+        return Core::HID::NpadType::ProController;
     }
 
     return it->second;
 }
 
-int ConfigureInputPlayer::GetIndexFromControllerType(Settings::ControllerType type) const {
+int ConfigureInputPlayer::GetIndexFromControllerType(Core::HID::NpadType type) const {
     const auto it =
         std::find_if(index_controller_type_pairs.begin(), index_controller_type_pairs.end(),
                      [type](const auto& pair) { return pair.second == type; });
@@ -1005,52 +801,15 @@ int ConfigureInputPlayer::GetIndexFromControllerType(Settings::ControllerType ty
 void ConfigureInputPlayer::UpdateInputDevices() {
     input_devices = input_subsystem->GetInputDevices();
     ui->comboDevices->clear();
-    for (auto& device : input_devices) {
-        const std::string display = device.Get("display", "Unknown");
-        ui->comboDevices->addItem(QString::fromStdString(display), {});
-        if (display == "TAS") {
-            device.Set("pad", static_cast<u8>(player_index));
-        }
+    for (auto device : input_devices) {
+        ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {});
     }
 }
 
-void ConfigureInputPlayer::UpdateControllerIcon() {
-    // We aren't using Qt's built in theme support here since we aren't drawing an icon (and its
-    // "nonstandard" to use an image through the icon support)
-    const QString stylesheet = [this] {
-        switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) {
-        case Settings::ControllerType::ProController:
-            return QStringLiteral("image: url(:/controller/pro_controller%0)");
-        case Settings::ControllerType::DualJoyconDetached:
-            return QStringLiteral("image: url(:/controller/dual_joycon%0)");
-        case Settings::ControllerType::LeftJoycon:
-            return QStringLiteral("image: url(:/controller/single_joycon_left_vertical%0)");
-        case Settings::ControllerType::RightJoycon:
-            return QStringLiteral("image: url(:/controller/single_joycon_right_vertical%0)");
-        case Settings::ControllerType::Handheld:
-            return QStringLiteral("image: url(:/controller/handheld%0)");
-        default:
-            return QString{};
-        }
-    }();
-
-    const QString theme = [] {
-        if (QIcon::themeName().contains(QStringLiteral("dark"))) {
-            return QStringLiteral("_dark");
-        } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
-            return QStringLiteral("_midnight");
-        } else {
-            return QString{};
-        }
-    }();
-    ui->controllerFrame->SetControllerType(
-        GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()));
-}
-
 void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
     auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
     if (debug) {
-        layout = Settings::ControllerType::ProController;
+        layout = Core::HID::NpadType::ProController;
     }
 
     // List of all the widgets that will be hidden by any of the following layouts that need
@@ -1075,15 +834,15 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
 
     std::vector<QWidget*> layout_hidden;
     switch (layout) {
-    case Settings::ControllerType::ProController:
-    case Settings::ControllerType::DualJoyconDetached:
-    case Settings::ControllerType::Handheld:
+    case Core::HID::NpadType::ProController:
+    case Core::HID::NpadType::JoyconDual:
+    case Core::HID::NpadType::Handheld:
         layout_hidden = {
             ui->buttonShoulderButtonsSLSR,
             ui->horizontalSpacerShoulderButtonsWidget2,
         };
         break;
-    case Settings::ControllerType::LeftJoycon:
+    case Core::HID::NpadType::JoyconLeft:
         layout_hidden = {
             ui->horizontalSpacerShoulderButtonsWidget2,
             ui->buttonShoulderButtonsRight,
@@ -1091,7 +850,7 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
             ui->bottomRight,
         };
         break;
-    case Settings::ControllerType::RightJoycon:
+    case Core::HID::NpadType::JoyconRight:
         layout_hidden = {
             ui->horizontalSpacerShoulderButtonsWidget,
             ui->buttonShoulderButtonsLeft,
@@ -1099,7 +858,7 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
             ui->bottomLeft,
         };
         break;
-    case Settings::ControllerType::GameCube:
+    case Core::HID::NpadType::GameCube:
         layout_hidden = {
             ui->buttonShoulderButtonsSLSR,
             ui->horizontalSpacerShoulderButtonsWidget2,
@@ -1107,6 +866,8 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
             ui->buttonMiscButtonsScreenshotGroup,
         };
         break;
+    default:
+        break;
     }
 
     for (auto* widget : layout_hidden) {
@@ -1117,13 +878,12 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
 void ConfigureInputPlayer::UpdateControllerEnabledButtons() {
     auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
     if (debug) {
-        layout = Settings::ControllerType::ProController;
+        layout = Core::HID::NpadType::ProController;
     }
 
     // List of all the widgets that will be disabled by any of the following layouts that need
     // "enabled" after the controller type changes
-    const std::array<QWidget*, 4> layout_enable = {
-        ui->buttonHome,
+    const std::array<QWidget*, 3> layout_enable = {
         ui->buttonLStickPressedGroup,
         ui->groupRStickPressed,
         ui->buttonShoulderButtonsButtonLGroup,
@@ -1135,17 +895,13 @@ void ConfigureInputPlayer::UpdateControllerEnabledButtons() {
 
     std::vector<QWidget*> layout_disable;
     switch (layout) {
-    case Settings::ControllerType::ProController:
-    case Settings::ControllerType::DualJoyconDetached:
-    case Settings::ControllerType::Handheld:
-    case Settings::ControllerType::LeftJoycon:
-    case Settings::ControllerType::RightJoycon:
-        // TODO(wwylele): enable this when we actually emulate it
-        layout_disable = {
-            ui->buttonHome,
-        };
+    case Core::HID::NpadType::ProController:
+    case Core::HID::NpadType::JoyconDual:
+    case Core::HID::NpadType::Handheld:
+    case Core::HID::NpadType::JoyconLeft:
+    case Core::HID::NpadType::JoyconRight:
         break;
-    case Settings::ControllerType::GameCube:
+    case Core::HID::NpadType::GameCube:
         layout_disable = {
             ui->buttonHome,
             ui->buttonLStickPressedGroup,
@@ -1153,6 +909,8 @@ void ConfigureInputPlayer::UpdateControllerEnabledButtons() {
             ui->buttonShoulderButtonsButtonLGroup,
         };
         break;
+    default:
+        break;
     }
 
     for (auto* widget : layout_disable) {
@@ -1170,24 +928,24 @@ void ConfigureInputPlayer::UpdateMotionButtons() {
 
     // Show/hide the "Motion 1/2" groupboxes depending on the currently selected controller.
     switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) {
-    case Settings::ControllerType::ProController:
-    case Settings::ControllerType::LeftJoycon:
-    case Settings::ControllerType::Handheld:
+    case Core::HID::NpadType::ProController:
+    case Core::HID::NpadType::JoyconLeft:
+    case Core::HID::NpadType::Handheld:
         // Show "Motion 1" and hide "Motion 2".
         ui->buttonMotionLeftGroup->show();
         ui->buttonMotionRightGroup->hide();
         break;
-    case Settings::ControllerType::RightJoycon:
+    case Core::HID::NpadType::JoyconRight:
         // Show "Motion 2" and hide "Motion 1".
         ui->buttonMotionLeftGroup->hide();
         ui->buttonMotionRightGroup->show();
         break;
-    case Settings::ControllerType::GameCube:
+    case Core::HID::NpadType::GameCube:
         // Hide both "Motion 1/2".
         ui->buttonMotionLeftGroup->hide();
         ui->buttonMotionRightGroup->hide();
         break;
-    case Settings::ControllerType::DualJoyconDetached:
+    case Core::HID::NpadType::JoyconDual:
     default:
         // Show both "Motion 1/2".
         ui->buttonMotionLeftGroup->show();
@@ -1199,15 +957,15 @@ void ConfigureInputPlayer::UpdateMotionButtons() {
 void ConfigureInputPlayer::UpdateControllerButtonNames() {
     auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
     if (debug) {
-        layout = Settings::ControllerType::ProController;
+        layout = Core::HID::NpadType::ProController;
     }
 
     switch (layout) {
-    case Settings::ControllerType::ProController:
-    case Settings::ControllerType::DualJoyconDetached:
-    case Settings::ControllerType::Handheld:
-    case Settings::ControllerType::LeftJoycon:
-    case Settings::ControllerType::RightJoycon:
+    case Core::HID::NpadType::ProController:
+    case Core::HID::NpadType::JoyconDual:
+    case Core::HID::NpadType::Handheld:
+    case Core::HID::NpadType::JoyconLeft:
+    case Core::HID::NpadType::JoyconRight:
         ui->buttonMiscButtonsPlusGroup->setTitle(tr("Plus"));
         ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("ZL"));
         ui->buttonShoulderButtonsZRGroup->setTitle(tr("ZR"));
@@ -1215,7 +973,7 @@ void ConfigureInputPlayer::UpdateControllerButtonNames() {
         ui->LStick->setTitle(tr("Left Stick"));
         ui->RStick->setTitle(tr("Right Stick"));
         break;
-    case Settings::ControllerType::GameCube:
+    case Core::HID::NpadType::GameCube:
         ui->buttonMiscButtonsPlusGroup->setTitle(tr("Start / Pause"));
         ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("L"));
         ui->buttonShoulderButtonsZRGroup->setTitle(tr("R"));
@@ -1223,6 +981,8 @@ void ConfigureInputPlayer::UpdateControllerButtonNames() {
         ui->LStick->setTitle(tr("Control Stick"));
         ui->RStick->setTitle(tr("C-Stick"));
         break;
+    default:
+        break;
     }
 }
 
@@ -1231,26 +991,93 @@ void ConfigureInputPlayer::UpdateMappingWithDefaults() {
         return;
     }
 
+    for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
+        const auto* const button = button_map[button_id];
+        if (button == nullptr) {
+            continue;
+        }
+        emulated_controller->SetButtonParam(button_id, {});
+    }
+
+    for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
+        for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
+            const auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
+            if (analog_button == nullptr) {
+                continue;
+            }
+            emulated_controller->SetStickParam(analog_id, {});
+        }
+    }
+
+    for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
+        const auto* const motion_button = motion_map[motion_id];
+        if (motion_button == nullptr) {
+            continue;
+        }
+        emulated_controller->SetMotionParam(motion_id, {});
+    }
+
+    // Reset keyboard bindings
     if (ui->comboDevices->currentIndex() == 1) {
-        // Reset keyboard bindings
         for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
-            buttons_param[button_id] = Common::ParamPackage{
-                InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
+            emulated_controller->SetButtonParam(
+                button_id, Common::ParamPackage{InputCommon::GenerateKeyboardParam(
+                               Config::default_buttons[button_id])});
         }
         for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
+            Common::ParamPackage analog_param{};
             for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
                 Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
                     Config::default_analogs[analog_id][sub_button_id])};
-                SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
+                SetAnalogParam(params, analog_param, analog_sub_buttons[sub_button_id]);
             }
 
-            analogs_param[analog_id].Set("modifier", InputCommon::GenerateKeyboardParam(
-                                                         Config::default_stick_mod[analog_id]));
+            analog_param.Set("modifier", InputCommon::GenerateKeyboardParam(
+                                             Config::default_stick_mod[analog_id]));
+            emulated_controller->SetStickParam(analog_id, analog_param);
         }
 
         for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
-            motions_param[motion_id] = Common::ParamPackage{
-                InputCommon::GenerateKeyboardParam(Config::default_motions[motion_id])};
+            emulated_controller->SetMotionParam(
+                motion_id, Common::ParamPackage{InputCommon::GenerateKeyboardParam(
+                               Config::default_motions[motion_id])});
+        }
+
+        UpdateUI();
+        return;
+    }
+
+    // Reset keyboard with mouse bindings
+    if (ui->comboDevices->currentIndex() == 2) {
+        for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
+            emulated_controller->SetButtonParam(
+                button_id, Common::ParamPackage{InputCommon::GenerateKeyboardParam(
+                               Config::default_buttons[button_id])});
+        }
+
+        Common::ParamPackage left_analog_param{};
+        for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
+            Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
+                Config::default_analogs[Settings::NativeAnalog::LStick][sub_button_id])};
+            SetAnalogParam(params, left_analog_param, analog_sub_buttons[sub_button_id]);
+        }
+        left_analog_param.Set("modifier",
+                              InputCommon::GenerateKeyboardParam(
+                                  Config::default_stick_mod[Settings::NativeAnalog::LStick]));
+        emulated_controller->SetStickParam(Settings::NativeAnalog::LStick, left_analog_param);
+
+        Common::ParamPackage right_analog_param{};
+        right_analog_param.Set("engine", "mouse");
+        right_analog_param.Set("port", 0);
+        right_analog_param.Set("axis_x", 0);
+        right_analog_param.Set("axis_y", 1);
+        emulated_controller->SetStickParam(Settings::NativeAnalog::RStick,
+                                           std::move(right_analog_param));
+
+        for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
+            emulated_controller->SetMotionParam(
+                motion_id, Common::ParamPackage{InputCommon::GenerateKeyboardParam(
+                               Config::default_motions[motion_id])});
         }
 
         UpdateUI();
@@ -1262,14 +1089,17 @@ void ConfigureInputPlayer::UpdateMappingWithDefaults() {
     auto button_mapping = input_subsystem->GetButtonMappingForDevice(device);
     auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device);
     auto motion_mapping = input_subsystem->GetMotionMappingForDevice(device);
-    for (std::size_t i = 0; i < buttons_param.size(); ++i) {
-        buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)];
+    for (std::size_t i = 0; i < button_mapping.size(); ++i) {
+        emulated_controller->SetButtonParam(
+            i, button_mapping[static_cast<Settings::NativeButton::Values>(i)]);
     }
-    for (std::size_t i = 0; i < analogs_param.size(); ++i) {
-        analogs_param[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)];
+    for (std::size_t i = 0; i < analog_mapping.size(); ++i) {
+        emulated_controller->SetStickParam(
+            i, analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)]);
     }
-    for (std::size_t i = 0; i < motions_param.size(); ++i) {
-        motions_param[i] = motion_mapping[static_cast<Settings::NativeMotion::Values>(i)];
+    for (std::size_t i = 0; i < motion_mapping.size(); ++i) {
+        emulated_controller->SetMotionParam(
+            i, motion_mapping[static_cast<Settings::NativeMotion::Values>(i)]);
     }
 
     UpdateUI();
@@ -1278,7 +1108,7 @@ void ConfigureInputPlayer::UpdateMappingWithDefaults() {
 void ConfigureInputPlayer::HandleClick(
     QPushButton* button, std::size_t button_id,
     std::function<void(const Common::ParamPackage&)> new_input_setter,
-    InputCommon::Polling::DeviceType type) {
+    InputCommon::Polling::InputType type) {
     if (button == ui->buttonMotionLeft || button == ui->buttonMotionRight) {
         button->setText(tr("Shake!"));
     } else {
@@ -1286,25 +1116,16 @@ void ConfigureInputPlayer::HandleClick(
     }
     button->setFocus();
 
-    // The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a
-    // controller, then they don't want keyboard/mouse input
-    want_keyboard_mouse = ui->comboDevices->currentIndex() < 2;
-
     input_setter = new_input_setter;
 
-    device_pollers = input_subsystem->GetPollers(type);
-
-    for (auto& poller : device_pollers) {
-        poller->Start();
-    }
+    input_subsystem->BeginMapping(type);
 
     QWidget::grabMouse();
     QWidget::grabKeyboard();
 
-
-    if (type == InputCommon::Polling::DeviceType::Button) {
+    if (type == InputCommon::Polling::InputType::Button) {
         ui->controllerFrame->BeginMappingButton(button_id);
-    } else if (type == InputCommon::Polling::DeviceType::AnalogPreferred) {
+    } else if (type == InputCommon::Polling::InputType::Stick) {
         ui->controllerFrame->BeginMappingAnalog(button_id);
     }
 
@@ -1315,14 +1136,11 @@ void ConfigureInputPlayer::HandleClick(
 void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, bool abort) {
     timeout_timer->stop();
     poll_timer->stop();
-    for (auto& poller : device_pollers) {
-        poller->Stop();
-    }
+    input_subsystem->StopMapping();
 
     QWidget::releaseMouse();
     QWidget::releaseKeyboard();
 
-
     if (!abort) {
         (*input_setter)(params);
     }
@@ -1340,13 +1158,14 @@ bool ConfigureInputPlayer::IsInputAcceptable(const Common::ParamPackage& params)
     }
 
     // Keyboard/Mouse
-    if (ui->comboDevices->currentIndex() == 1) {
+    if (ui->comboDevices->currentIndex() == 2) {
         return params.Get("engine", "") == "keyboard" || params.Get("engine", "") == "mouse";
     }
 
     const auto current_input_device = input_devices[ui->comboDevices->currentIndex()];
-    return params.Get("engine", "") == current_input_device.Get("class", "") &&
-           params.Get("guid", "") == current_input_device.Get("guid", "") &&
+    return params.Get("engine", "") == current_input_device.Get("engine", "") &&
+           (params.Get("guid", "") == current_input_device.Get("guid", "") ||
+            params.Get("guid", "") == current_input_device.Get("guid2", "")) &&
            params.Get("port", "") == current_input_device.Get("port", "");
 }
 
@@ -1355,25 +1174,18 @@ void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) {
         return;
     }
 
-    //const auto button = GRenderWindow::QtButtonToMouseButton(event->button());
+    const auto button = GRenderWindow::QtButtonToMouseButton(event->button());
+    input_subsystem->GetMouse()->PressButton(0, 0, 0, 0, button);
 }
 
 void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
+    event->ignore();
     if (!input_setter || !event) {
         return;
     }
-
     if (event->key() != Qt::Key_Escape) {
-        if (want_keyboard_mouse) {
-            SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
-                             false);
-        } else {
-            // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling
-            return;
-        }
+        input_subsystem->GetKeyboard()->PressKey(event->key());
     }
-
-    SetPollingResult({}, true);
 }
 
 void ConfigureInputPlayer::CreateProfile() {
diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h
index 39b44b8a56..02d6920f1e 100644
--- a/src/yuzu/configuration/configure_input_player.h
+++ b/src/yuzu/configuration/configure_input_player.h
@@ -38,14 +38,22 @@ class InputSubsystem;
 }
 
 namespace InputCommon::Polling {
-class DevicePoller;
-enum class DeviceType;
+enum class InputType;
 } // namespace InputCommon::Polling
 
 namespace Ui {
 class ConfigureInputPlayer;
 }
 
+namespace Core {
+class System;
+}
+
+namespace Core::HID {
+class EmulatedController;
+enum class NpadType : u8;
+} // namespace Core::HID
+
 class ConfigureInputPlayer : public QWidget {
     Q_OBJECT
 
@@ -59,18 +67,6 @@ public:
     /// Save all button configurations to settings file.
     void ApplyConfiguration();
 
-    /**
-     * Attempts to connect the currently selected controller in the HID backend.
-     * This function will not do anything if it is not connected in the frontend.
-     */
-    void TryConnectSelectedController();
-
-    /**
-     * Attempts to disconnect the currently selected controller in the HID backend.
-     * This function will not do anything if the configuration has not changed.
-     */
-    void TryDisconnectSelectedController();
-
     /// Set the connection state checkbox (used to sync state).
     void ConnectPlayer(bool connected);
 
@@ -104,6 +100,10 @@ protected:
     void showEvent(QShowEvent* event) override;
 
 private:
+    QString ButtonToText(const Common::ParamPackage& param);
+
+    QString AnalogToText(const Common::ParamPackage& param, const std::string& dir);
+
     void changeEvent(QEvent* event) override;
     void RetranslateUI();
 
@@ -113,7 +113,7 @@ private:
     /// Called when the button was pressed.
     void HandleClick(QPushButton* button, std::size_t button_id,
                      std::function<void(const Common::ParamPackage&)> new_input_setter,
-                     InputCommon::Polling::DeviceType type);
+                     InputCommon::Polling::InputType type);
 
     /// Finish polling and configure input using the input_setter.
     void SetPollingResult(const Common::ParamPackage& params, bool abort);
@@ -134,17 +134,14 @@ private:
     void SetConnectableControllers();
 
     /// Gets the Controller Type for a given controller combobox index.
-    Settings::ControllerType GetControllerTypeFromIndex(int index) const;
+    Core::HID::NpadType GetControllerTypeFromIndex(int index) const;
 
     /// Gets the controller combobox index for a given Controller Type.
-    int GetIndexFromControllerType(Settings::ControllerType type) const;
+    int GetIndexFromControllerType(Core::HID::NpadType type) const;
 
     /// Update the available input devices.
     void UpdateInputDevices();
 
-    /// Update the current controller icon.
-    void UpdateControllerIcon();
-
     /// Hides and disables controller settings based on the current controller type.
     void UpdateControllerAvailableButtons();
 
@@ -185,7 +182,7 @@ private:
     std::unique_ptr<QTimer> poll_timer;
 
     /// Stores a pair of "Connected Controllers" combobox index and Controller Type enum.
-    std::vector<std::pair<int, Settings::ControllerType>> index_controller_type_pairs;
+    std::vector<std::pair<int, Core::HID::NpadType>> index_controller_type_pairs;
 
     static constexpr int PLAYER_COUNT = 8;
     std::array<QCheckBox*, PLAYER_COUNT> player_connected_checkbox;
@@ -193,9 +190,7 @@ private:
     /// This will be the the setting function when an input is awaiting configuration.
     std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
 
-    std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param;
-    std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param;
-    std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions> motions_param;
+    Core::HID::EmulatedController* emulated_controller;
 
     static constexpr int ANALOG_SUB_BUTTONS_NUM = 4;
 
@@ -221,15 +216,9 @@ private:
 
     static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons;
 
-    std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers;
-
     /// A flag to indicate that the "Map Analog Stick" pop-up has been shown and accepted once.
     bool map_analog_stick_accepted{};
 
-    /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false,
-    /// keyboard events are ignored.
-    bool want_keyboard_mouse{};
-
     /// List of physical devices users can map with. If a SDL backed device is selected, then you
     /// can use this device to get a default mapping.
     std::vector<Common::ParamPackage> input_devices;
diff --git a/src/yuzu/configuration/configure_input_player.ui b/src/yuzu/configuration/configure_input_player.ui
index e7433912b8..14ca02fd84 100644
--- a/src/yuzu/configuration/configure_input_player.ui
+++ b/src/yuzu/configuration/configure_input_player.ui
@@ -148,16 +148,6 @@
               <height>21</height>
              </size>
             </property>
-            <item>
-             <property name="text">
-              <string>Any</string>
-             </property>
-            </item>
-            <item>
-             <property name="text">
-              <string>Keyboard/Mouse</string>
-             </property>
-            </item>
            </widget>
           </item>
           <item>
diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp
index f31f86339e..03d29f194e 100644
--- a/src/yuzu/configuration/configure_input_player_widget.cpp
+++ b/src/yuzu/configuration/configure_input_player_widget.cpp
@@ -6,10 +6,11 @@
 #include <QMenu>
 #include <QPainter>
 #include <QTimer>
+#include "core/core.h"
 #include "yuzu/configuration/configure_input_player_widget.h"
 
 PlayerControlPreview::PlayerControlPreview(QWidget* parent) : QFrame(parent) {
-    UpdateColors();
+    is_controller_set = false;
     QTimer* timer = new QTimer(this);
     connect(timer, &QTimer::timeout, this, QOverload<>::of(&PlayerControlPreview::UpdateInput));
 
@@ -17,38 +18,11 @@ PlayerControlPreview::PlayerControlPreview(QWidget* parent) : QFrame(parent) {
     timer->start(16);
 }
 
-PlayerControlPreview::~PlayerControlPreview() = default;
-
-void PlayerControlPreview::SetPlayerInput(std::size_t index, const ButtonParam& buttons_param,
-                                          const AnalogParam& analogs_param) {
-    player_index = index;
-    Settings::ButtonsRaw buttonss;
-    Settings::AnalogsRaw analogs;
-    std::transform(buttons_param.begin(), buttons_param.end(), buttonss.begin(),
-                   [](const Common::ParamPackage& param) { return param.Serialize(); });
-    std::transform(analogs_param.begin(), analogs_param.end(), analogs.begin(),
-                   [](const Common::ParamPackage& param) { return param.Serialize(); });
-
-    std::transform(buttonss.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
-                   buttonss.begin() + Settings::NativeButton::BUTTON_NS_END, buttons.begin(),
-                   Input::CreateDevice<Input::ButtonDevice>);
-    std::transform(analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
-                   analogs.begin() + Settings::NativeAnalog::STICK_HID_END, sticks.begin(),
-                   Input::CreateDevice<Input::AnalogDevice>);
-    UpdateColors();
-}
-void PlayerControlPreview::SetPlayerInputRaw(std::size_t index,
-                                             const Settings::ButtonsRaw& buttons_,
-                                             Settings::AnalogsRaw analogs_) {
-    player_index = index;
-    std::transform(buttons_.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
-                   buttons_.begin() + Settings::NativeButton::BUTTON_NS_END, buttons.begin(),
-                   Input::CreateDevice<Input::ButtonDevice>);
-    std::transform(analogs_.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
-                   analogs_.begin() + Settings::NativeAnalog::STICK_HID_END, sticks.begin(),
-                   Input::CreateDevice<Input::AnalogDevice>);
-    UpdateColors();
-}
+PlayerControlPreview::~PlayerControlPreview() {
+    if (is_controller_set) {
+        controller->DeleteCallback(callback_key);
+    }
+};
 
 PlayerControlPreview::LedPattern PlayerControlPreview::GetColorPattern(std::size_t index,
                                                                        bool player_on) {
@@ -78,20 +52,16 @@ PlayerControlPreview::LedPattern PlayerControlPreview::GetColorPattern(std::size
     }
 }
 
-void PlayerControlPreview::SetConnectedStatus(bool checked) {
-    LedPattern led_pattern = GetColorPattern(player_index, checked);
-
-    led_color[0] = led_pattern.position1 ? colors.led_on : colors.led_off;
-    led_color[1] = led_pattern.position2 ? colors.led_on : colors.led_off;
-    led_color[2] = led_pattern.position3 ? colors.led_on : colors.led_off;
-    led_color[3] = led_pattern.position4 ? colors.led_on : colors.led_off;
-    is_enabled = checked;
-    ResetInputs();
-}
-
-void PlayerControlPreview::SetControllerType(const Settings::ControllerType type) {
-    controller_type = type;
-    UpdateColors();
+void PlayerControlPreview::SetController(Core::HID::EmulatedController* controller_) {
+    if (is_controller_set) {
+        controller->DeleteCallback(callback_key);
+    }
+    is_controller_set = true;
+    controller = controller_;
+    Core::HID::ControllerUpdateCallback engine_callback{
+        [this](Core::HID::ControllerTriggerType type) { ControllerUpdate(type); }};
+    callback_key = controller->SetCallback(engine_callback);
+    ControllerUpdate(Core::HID::ControllerTriggerType::All);
 }
 
 void PlayerControlPreview::BeginMappingButton(std::size_t index) {
@@ -162,79 +132,99 @@ void PlayerControlPreview::UpdateColors() {
 }
 
 void PlayerControlPreview::ResetInputs() {
-    for (std::size_t index = 0; index < button_values.size(); ++index) {
-        button_values[index] = false;
-    }
-
-    for (std::size_t index = 0; index < axis_values.size(); ++index) {
-        axis_values[index].properties = {0, 1, 0};
-        axis_values[index].value = {0, 0};
-        axis_values[index].raw_value = {0, 0};
-    }
+    button_values.fill({
+        .value = false,
+    });
+    stick_values.fill({
+        .x = {.value = 0, .properties = {0, 1, 0}},
+        .y = {.value = 0, .properties = {0, 1, 0}},
+    });
+    trigger_values.fill({
+        .analog = {.value = 0, .properties = {0, 1, 0}},
+        .pressed = false,
+    });
     update();
 }
 
-void PlayerControlPreview::UpdateInput() {
-    if (!is_enabled && !mapping_active && !Settings::values.tas_enable) {
+void PlayerControlPreview::ControllerUpdate(Core::HID::ControllerTriggerType type) {
+    if (type == Core::HID::ControllerTriggerType::All) {
+        ControllerUpdate(Core::HID::ControllerTriggerType::Color);
+        ControllerUpdate(Core::HID::ControllerTriggerType::Type);
+        ControllerUpdate(Core::HID::ControllerTriggerType::Connected);
+        ControllerUpdate(Core::HID::ControllerTriggerType::Button);
+        ControllerUpdate(Core::HID::ControllerTriggerType::Stick);
+        ControllerUpdate(Core::HID::ControllerTriggerType::Trigger);
+        ControllerUpdate(Core::HID::ControllerTriggerType::Battery);
         return;
     }
-    bool input_changed = false;
-    const auto& button_state = buttons;
-    for (std::size_t index = 0; index < button_values.size(); ++index) {
-        bool value = false;
-        if (index < Settings::NativeButton::BUTTON_NS_END) {
-            value = button_state[index]->GetStatus();
-        }
-        bool blink = mapping_active && index == button_mapping_index;
-        if (analog_mapping_index == Settings::NativeAnalog::NUM_STICKS_HID) {
-            blink &= blink_counter > 25;
-        }
-        if (button_values[index] != value || blink) {
-            input_changed = true;
-        }
-        button_values[index] = value || blink;
+
+    switch (type) {
+    case Core::HID::ControllerTriggerType::Connected:
+    case Core::HID::ControllerTriggerType::Disconnected:
+        is_connected = controller->IsConnected();
+        needs_redraw = true;
+        break;
+    case Core::HID::ControllerTriggerType::Type:
+        controller_type = controller->GetNpadType();
+        needs_redraw = true;
+        break;
+    case Core::HID::ControllerTriggerType::Color:
+        UpdateColors();
+        needs_redraw = true;
+        break;
+    case Core::HID::ControllerTriggerType::Button:
+        button_values = controller->GetButtonsValues();
+        needs_redraw = true;
+        break;
+    case Core::HID::ControllerTriggerType::Stick:
+        using namespace Settings::NativeAnalog;
+        stick_values = controller->GetSticksValues();
+        // Y axis is inverted
+        stick_values[LStick].y.value = -stick_values[LStick].y.value;
+        stick_values[LStick].y.raw_value = -stick_values[LStick].y.raw_value;
+        stick_values[RStick].y.value = -stick_values[RStick].y.value;
+        stick_values[RStick].y.raw_value = -stick_values[RStick].y.raw_value;
+        needs_redraw = true;
+        break;
+    case Core::HID::ControllerTriggerType::Trigger:
+        trigger_values = controller->GetTriggersValues();
+        needs_redraw = true;
+        break;
+    case Core::HID::ControllerTriggerType::Battery:
+        battery_values = controller->GetBatteryValues();
+        needs_redraw = true;
+        break;
+    default:
+        break;
     }
+}
 
-    const auto& analog_state = sticks;
-    for (std::size_t index = 0; index < axis_values.size(); ++index) {
-        const auto [stick_x_f, stick_y_f] = analog_state[index]->GetStatus();
-        const auto [stick_x_rf, stick_y_rf] = analog_state[index]->GetRawStatus();
+void PlayerControlPreview::UpdateInput() {
+    if (mapping_active) {
 
-        if (static_cast<int>(stick_x_rf * 45) !=
-                static_cast<int>(axis_values[index].raw_value.x() * 45) ||
-            static_cast<int>(-stick_y_rf * 45) !=
-                static_cast<int>(axis_values[index].raw_value.y() * 45)) {
-            input_changed = true;
+        for (std::size_t index = 0; index < button_values.size(); ++index) {
+            bool blink = index == button_mapping_index;
+            if (analog_mapping_index == Settings::NativeAnalog::NumAnalogs) {
+                blink &= blink_counter > 25;
+            }
+            if (button_values[index].value != blink) {
+                needs_redraw = true;
+            }
+            button_values[index].value = blink;
         }
 
-        axis_values[index].properties = analog_state[index]->GetAnalogProperties();
-        axis_values[index].value = QPointF(stick_x_f, -stick_y_f);
-        axis_values[index].raw_value = QPointF(stick_x_rf, -stick_y_rf);
-
-        const bool blink_analog = mapping_active && index == analog_mapping_index;
-        if (blink_analog) {
-            input_changed = true;
-            axis_values[index].value =
-                QPointF(blink_counter < 25 ? -blink_counter / 25.0f : 0,
-                        blink_counter > 25 ? -(blink_counter - 25) / 25.0f : 0);
+        for (std::size_t index = 0; index < stick_values.size(); ++index) {
+            const bool blink_analog = index == analog_mapping_index;
+            if (blink_analog) {
+                needs_redraw = true;
+                stick_values[index].x.value = blink_counter < 25 ? -blink_counter / 25.0f : 0;
+                stick_values[index].y.value =
+                    blink_counter > 25 ? -(blink_counter - 25) / 25.0f : 0;
+            }
         }
     }
-
-    if (input_changed) {
+    if (needs_redraw) {
         update();
-        if (controller_callback.input != nullptr) {
-            ControllerInput input{
-                .axis_values = {std::pair<float, float>{
-                                    axis_values[Settings::NativeAnalog::LStick].value.x(),
-                                    axis_values[Settings::NativeAnalog::LStick].value.y()},
-                                std::pair<float, float>{
-                                    axis_values[Settings::NativeAnalog::RStick].value.x(),
-                                    axis_values[Settings::NativeAnalog::RStick].value.y()}},
-                .button_values = button_values,
-                .changed = true,
-            };
-            controller_callback.input(std::move(input));
-        }
     }
 
     if (mapping_active) {
@@ -242,10 +232,6 @@ void PlayerControlPreview::UpdateInput() {
     }
 }
 
-void PlayerControlPreview::SetCallBack(ControllerCallback callback_) {
-    controller_callback = std::move(callback_);
-}
-
 void PlayerControlPreview::paintEvent(QPaintEvent* event) {
     QFrame::paintEvent(event);
     QPainter p(this);
@@ -253,22 +239,22 @@ void PlayerControlPreview::paintEvent(QPaintEvent* event) {
     const QPointF center = rect().center();
 
     switch (controller_type) {
-    case Settings::ControllerType::Handheld:
+    case Core::HID::NpadType::Handheld:
         DrawHandheldController(p, center);
         break;
-    case Settings::ControllerType::DualJoyconDetached:
+    case Core::HID::NpadType::JoyconDual:
         DrawDualController(p, center);
         break;
-    case Settings::ControllerType::LeftJoycon:
+    case Core::HID::NpadType::JoyconLeft:
         DrawLeftController(p, center);
         break;
-    case Settings::ControllerType::RightJoycon:
+    case Core::HID::NpadType::JoyconRight:
         DrawRightController(p, center);
         break;
-    case Settings::ControllerType::GameCube:
+    case Core::HID::NpadType::GameCube:
         DrawGCController(p, center);
         break;
-    case Settings::ControllerType::ProController:
+    case Core::HID::NpadType::ProController:
     default:
         DrawProController(p, center);
         break;
@@ -281,7 +267,7 @@ void PlayerControlPreview::DrawLeftController(QPainter& p, const QPointF center)
 
         // Sideview left joystick
         DrawJoystickSideview(p, center + QPoint(142, -69),
-                             -axis_values[Settings::NativeAnalog::LStick].value.y(), 1.15f,
+                             -stick_values[Settings::NativeAnalog::LStick].y.value, 1.15f,
                              button_values[LStick]);
 
         // Topview D-pad buttons
@@ -292,7 +278,7 @@ void PlayerControlPreview::DrawLeftController(QPainter& p, const QPointF center)
 
         // Topview left joystick
         DrawJoystickSideview(p, center + QPointF(-140.5f, -28),
-                             -axis_values[Settings::NativeAnalog::LStick].value.x() + 15.0f, 1.15f,
+                             -stick_values[Settings::NativeAnalog::LStick].x.value + 15.0f, 1.15f,
                              button_values[LStick]);
 
         // Topview minus button
@@ -334,8 +320,10 @@ void PlayerControlPreview::DrawLeftController(QPainter& p, const QPointF center)
     {
         // Draw joysticks
         using namespace Settings::NativeAnalog;
-        DrawJoystick(p, center + QPointF(9, -69) + (axis_values[LStick].value * 8), 1.8f,
-                     button_values[Settings::NativeButton::LStick]);
+        DrawJoystick(p,
+                     center + QPointF(9, -69) +
+                         (QPointF(stick_values[LStick].x.value, stick_values[LStick].y.value) * 8),
+                     1.8f, button_values[Settings::NativeButton::LStick]);
         DrawRawJoystick(p, center + QPointF(-140, 90), QPointF(0, 0));
     }
 
@@ -384,6 +372,9 @@ void PlayerControlPreview::DrawLeftController(QPainter& p, const QPointF center)
     p.setPen(colors.font2);
     p.setBrush(colors.font2);
     DrawCircle(p, center + QPoint(26, 71), 5);
+
+    // Draw battery
+    DrawBattery(p, center + QPoint(-170, -140), battery_values[0]);
 }
 
 void PlayerControlPreview::DrawRightController(QPainter& p, const QPointF center) {
@@ -392,20 +383,22 @@ void PlayerControlPreview::DrawRightController(QPainter& p, const QPointF center
 
         // Sideview right joystick
         DrawJoystickSideview(p, center + QPoint(173 - 315, 11),
-                             axis_values[Settings::NativeAnalog::RStick].value.y() + 10.0f, 1.15f,
+                             stick_values[Settings::NativeAnalog::RStick].y.value + 10.0f, 1.15f,
                              button_values[Settings::NativeButton::RStick]);
 
+        // Topview right joystick
+        DrawJoystickSideview(p, center + QPointF(140, -28),
+                             -stick_values[Settings::NativeAnalog::RStick].x.value + 15.0f, 1.15f,
+                             button_values[RStick]);
+
         // Topview face buttons
         p.setPen(colors.outline);
         button_color = colors.button;
         DrawRoundButton(p, center + QPoint(163, -21), button_values[A], 11, 5, Direction::Up);
+        DrawRoundButton(p, center + QPoint(140, -21), button_values[B], 11, 5, Direction::Up);
+        DrawRoundButton(p, center + QPoint(140, -21), button_values[X], 11, 5, Direction::Up);
         DrawRoundButton(p, center + QPoint(117, -21), button_values[Y], 11, 5, Direction::Up);
 
-        // Topview right joystick
-        DrawJoystickSideview(p, center + QPointF(140, -28),
-                             -axis_values[Settings::NativeAnalog::RStick].value.x() + 15.0f, 1.15f,
-                             button_values[RStick]);
-
         // Topview plus button
         p.setPen(colors.outline);
         button_color = colors.button;
@@ -448,8 +441,10 @@ void PlayerControlPreview::DrawRightController(QPainter& p, const QPointF center
     {
         // Draw joysticks
         using namespace Settings::NativeAnalog;
-        DrawJoystick(p, center + QPointF(-9, 11) + (axis_values[RStick].value * 8), 1.8f,
-                     button_values[Settings::NativeButton::RStick]);
+        DrawJoystick(p,
+                     center + QPointF(-9, 11) +
+                         (QPointF(stick_values[RStick].x.value, stick_values[RStick].y.value) * 8),
+                     1.8f, button_values[Settings::NativeButton::RStick]);
         DrawRawJoystick(p, QPointF(0, 0), center + QPointF(140, 90));
     }
 
@@ -503,6 +498,9 @@ void PlayerControlPreview::DrawRightController(QPainter& p, const QPointF center
     p.setPen(colors.transparent);
     p.setBrush(colors.font2);
     DrawSymbol(p, center + QPoint(-26, 66), Symbol::House, 5);
+
+    // Draw battery
+    DrawBattery(p, center + QPoint(110, -140), battery_values[1]);
 }
 
 void PlayerControlPreview::DrawDualController(QPainter& p, const QPointF center) {
@@ -512,17 +510,19 @@ void PlayerControlPreview::DrawDualController(QPainter& p, const QPointF center)
         // Left/Right trigger
         DrawDualTriggers(p, center, button_values[L], button_values[R]);
 
+        // Topview right joystick
+        DrawJoystickSideview(p, center + QPointF(180, -78),
+                             -stick_values[Settings::NativeAnalog::RStick].x.value + 15.0f, 1,
+                             button_values[RStick]);
+
         // Topview face buttons
         p.setPen(colors.outline);
         button_color = colors.button;
         DrawRoundButton(p, center + QPoint(200, -71), button_values[A], 10, 5, Direction::Up);
+        DrawRoundButton(p, center + QPoint(180, -71), button_values[B], 10, 5, Direction::Up);
+        DrawRoundButton(p, center + QPoint(180, -71), button_values[X], 10, 5, Direction::Up);
         DrawRoundButton(p, center + QPoint(160, -71), button_values[Y], 10, 5, Direction::Up);
 
-        // Topview right joystick
-        DrawJoystickSideview(p, center + QPointF(180, -78),
-                             -axis_values[Settings::NativeAnalog::RStick].value.x() + 15.0f, 1,
-                             button_values[RStick]);
-
         // Topview plus button
         p.setPen(colors.outline);
         button_color = colors.button;
@@ -538,7 +538,7 @@ void PlayerControlPreview::DrawDualController(QPainter& p, const QPointF center)
 
         // Topview left joystick
         DrawJoystickSideview(p, center + QPointF(-180.5f, -78),
-                             -axis_values[Settings::NativeAnalog::LStick].value.x() + 15.0f, 1,
+                             -stick_values[Settings::NativeAnalog::LStick].x.value + 15.0f, 1,
                              button_values[LStick]);
 
         // Topview minus button
@@ -557,13 +557,13 @@ void PlayerControlPreview::DrawDualController(QPainter& p, const QPointF center)
     {
         // Draw joysticks
         using namespace Settings::NativeAnalog;
-        const auto& l_stick = axis_values[LStick];
+        const auto l_stick = QPointF(stick_values[LStick].x.value, stick_values[LStick].y.value);
         const auto l_button = button_values[Settings::NativeButton::LStick];
-        const auto& r_stick = axis_values[RStick];
+        const auto r_stick = QPointF(stick_values[RStick].x.value, stick_values[RStick].y.value);
         const auto r_button = button_values[Settings::NativeButton::RStick];
 
-        DrawJoystick(p, center + QPointF(-65, -65) + (l_stick.value * 7), 1.62f, l_button);
-        DrawJoystick(p, center + QPointF(65, 12) + (r_stick.value * 7), 1.62f, r_button);
+        DrawJoystick(p, center + QPointF(-65, -65) + (l_stick * 7), 1.62f, l_button);
+        DrawJoystick(p, center + QPointF(65, 12) + (r_stick * 7), 1.62f, r_button);
         DrawRawJoystick(p, center + QPointF(-180, 90), center + QPointF(180, 90));
     }
 
@@ -634,6 +634,10 @@ void PlayerControlPreview::DrawDualController(QPainter& p, const QPointF center)
     p.setPen(colors.transparent);
     p.setBrush(colors.font2);
     DrawSymbol(p, center + QPoint(50, 60), Symbol::House, 4.2f);
+
+    // Draw battery
+    DrawBattery(p, center + QPoint(-100, -160), battery_values[0]);
+    DrawBattery(p, center + QPoint(40, -160), battery_values[1]);
 }
 
 void PlayerControlPreview::DrawHandheldController(QPainter& p, const QPointF center) {
@@ -643,13 +647,13 @@ void PlayerControlPreview::DrawHandheldController(QPainter& p, const QPointF cen
     {
         // Draw joysticks
         using namespace Settings::NativeAnalog;
-        const auto& l_stick = axis_values[LStick];
+        const auto l_stick = QPointF(stick_values[LStick].x.value, stick_values[LStick].y.value);
         const auto l_button = button_values[Settings::NativeButton::LStick];
-        const auto& r_stick = axis_values[RStick];
+        const auto r_stick = QPointF(stick_values[RStick].x.value, stick_values[RStick].y.value);
         const auto r_button = button_values[Settings::NativeButton::RStick];
 
-        DrawJoystick(p, center + QPointF(-171, -41) + (l_stick.value * 4), 1.0f, l_button);
-        DrawJoystick(p, center + QPointF(171, 8) + (r_stick.value * 4), 1.0f, r_button);
+        DrawJoystick(p, center + QPointF(-171, -41) + (l_stick * 4), 1.0f, l_button);
+        DrawJoystick(p, center + QPointF(171, 8) + (r_stick * 4), 1.0f, r_button);
         DrawRawJoystick(p, center + QPointF(-50, 0), center + QPointF(50, 0));
     }
 
@@ -732,6 +736,11 @@ void PlayerControlPreview::DrawHandheldController(QPainter& p, const QPointF cen
     p.setPen(colors.transparent);
     p.setBrush(colors.font2);
     DrawSymbol(p, center + QPoint(161, 37), Symbol::House, 2.75f);
+
+    // Draw battery
+    DrawBattery(p, center + QPoint(-200, 110), battery_values[0]);
+    DrawBattery(p, center + QPoint(-30, 110), battery_values[1]);
+    DrawBattery(p, center + QPoint(130, 110), battery_values[2]);
 }
 
 void PlayerControlPreview::DrawProController(QPainter& p, const QPointF center) {
@@ -741,9 +750,11 @@ void PlayerControlPreview::DrawProController(QPainter& p, const QPointF center)
     {
         // Draw joysticks
         using namespace Settings::NativeAnalog;
-        DrawProJoystick(p, center + QPointF(-111, -55), axis_values[LStick].value, 11,
+        const auto l_stick = QPointF(stick_values[LStick].x.value, stick_values[LStick].y.value);
+        const auto r_stick = QPointF(stick_values[RStick].x.value, stick_values[RStick].y.value);
+        DrawProJoystick(p, center + QPointF(-111, -55), l_stick, 11,
                         button_values[Settings::NativeButton::LStick]);
-        DrawProJoystick(p, center + QPointF(51, 0), axis_values[RStick].value, 11,
+        DrawProJoystick(p, center + QPointF(51, 0), r_stick, 11,
                         button_values[Settings::NativeButton::RStick]);
         DrawRawJoystick(p, center + QPointF(-50, 105), center + QPointF(50, 105));
     }
@@ -817,24 +828,26 @@ void PlayerControlPreview::DrawProController(QPainter& p, const QPointF center)
     p.setPen(colors.transparent);
     p.setBrush(colors.font2);
     DrawSymbol(p, center + QPoint(29, -56), Symbol::House, 3.9f);
+
+    // Draw battery
+    DrawBattery(p, center + QPoint(-30, -165), battery_values[0]);
 }
 
 void PlayerControlPreview::DrawGCController(QPainter& p, const QPointF center) {
-    DrawGCTriggers(p, center, button_values[Settings::NativeButton::ZL],
-                   button_values[Settings::NativeButton::ZR]);
+    DrawGCTriggers(p, center, trigger_values[0], trigger_values[1]);
     DrawGCButtonZ(p, center, button_values[Settings::NativeButton::R]);
     DrawGCBody(p, center);
     {
         // Draw joysticks
         using namespace Settings::NativeAnalog;
-        DrawGCJoystick(p, center + QPointF(-111, -44) + (axis_values[LStick].value * 10), false);
+        const auto l_stick = QPointF(stick_values[LStick].x.value, stick_values[LStick].y.value);
+        const auto r_stick = QPointF(stick_values[RStick].x.value, stick_values[RStick].y.value);
+        DrawGCJoystick(p, center + QPointF(-111, -44) + (l_stick * 10), {});
         button_color = colors.button2;
-        DrawCircleButton(p, center + QPointF(61, 37) + (axis_values[RStick].value * 9.5f), false,
-                         15);
+        DrawCircleButton(p, center + QPointF(61, 37) + (r_stick * 9.5f), {}, 15);
         p.setPen(colors.transparent);
         p.setBrush(colors.font);
-        DrawSymbol(p, center + QPointF(61, 37) + (axis_values[RStick].value * 9.5f), Symbol::C,
-                   1.0f);
+        DrawSymbol(p, center + QPointF(61, 37) + (r_stick * 9.5f), Symbol::C, 1.0f);
         DrawRawJoystick(p, center + QPointF(-198, -125), center + QPointF(198, -125));
     }
 
@@ -871,6 +884,9 @@ void PlayerControlPreview::DrawGCController(QPainter& p, const QPointF center) {
     // Minus and Plus buttons
     p.setPen(colors.outline);
     DrawCircleButton(p, center + QPoint(0, -44), button_values[Plus], 8);
+
+    // Draw battery
+    DrawBattery(p, center + QPoint(-30, -165), battery_values[0]);
 }
 
 constexpr std::array<float, 13 * 2> symbol_a = {
@@ -1939,8 +1955,9 @@ void PlayerControlPreview::DrawRightBody(QPainter& p, const QPointF center) {
     }
 }
 
-void PlayerControlPreview::DrawProTriggers(QPainter& p, const QPointF center, bool left_pressed,
-                                           bool right_pressed) {
+void PlayerControlPreview::DrawProTriggers(QPainter& p, const QPointF center,
+                                           const Input::ButtonStatus& left_pressed,
+                                           const Input::ButtonStatus& right_pressed) {
     std::array<QPointF, pro_left_trigger.size() / 2> qleft_trigger;
     std::array<QPointF, pro_left_trigger.size() / 2> qright_trigger;
     std::array<QPointF, pro_body_top.size()> qbody_top;
@@ -1949,8 +1966,10 @@ void PlayerControlPreview::DrawProTriggers(QPainter& p, const QPointF center, bo
         const float trigger_x = pro_left_trigger[point * 2 + 0];
         const float trigger_y = pro_left_trigger[point * 2 + 1];
 
-        qleft_trigger[point] = center + QPointF(trigger_x, trigger_y + (left_pressed ? 2 : 0));
-        qright_trigger[point] = center + QPointF(-trigger_x, trigger_y + (right_pressed ? 2 : 0));
+        qleft_trigger[point] =
+            center + QPointF(trigger_x, trigger_y + (left_pressed.value ? 2 : 0));
+        qright_trigger[point] =
+            center + QPointF(-trigger_x, trigger_y + (right_pressed.value ? 2 : 0));
     }
 
     for (std::size_t point = 0; point < pro_body_top.size() / 2; ++point) {
@@ -1967,16 +1986,17 @@ void PlayerControlPreview::DrawProTriggers(QPainter& p, const QPointF center, bo
     DrawPolygon(p, qbody_top);
 
     // Left trigger
-    p.setBrush(left_pressed ? colors.highlight : colors.button);
+    p.setBrush(left_pressed.value ? colors.highlight : colors.button);
     DrawPolygon(p, qleft_trigger);
 
     // Right trigger
-    p.setBrush(right_pressed ? colors.highlight : colors.button);
+    p.setBrush(right_pressed.value ? colors.highlight : colors.button);
     DrawPolygon(p, qright_trigger);
 }
 
-void PlayerControlPreview::DrawGCTriggers(QPainter& p, const QPointF center, bool left_pressed,
-                                          bool right_pressed) {
+void PlayerControlPreview::DrawGCTriggers(QPainter& p, const QPointF center,
+                                          Input::TriggerStatus left_trigger,
+                                          Input::TriggerStatus right_trigger) {
     std::array<QPointF, left_gc_trigger.size() / 2> qleft_trigger;
     std::array<QPointF, left_gc_trigger.size() / 2> qright_trigger;
 
@@ -1984,32 +2004,37 @@ void PlayerControlPreview::DrawGCTriggers(QPainter& p, const QPointF center, boo
         const float trigger_x = left_gc_trigger[point * 2 + 0];
         const float trigger_y = left_gc_trigger[point * 2 + 1];
 
-        qleft_trigger[point] = center + QPointF(trigger_x, trigger_y + (left_pressed ? 10 : 0));
-        qright_trigger[point] = center + QPointF(-trigger_x, trigger_y + (right_pressed ? 10 : 0));
+        qleft_trigger[point] =
+            center + QPointF(trigger_x, trigger_y + (left_trigger.analog.value * 10.0f));
+        qright_trigger[point] =
+            center + QPointF(-trigger_x, trigger_y + (right_trigger.analog.value * 10.0f));
     }
 
     // Left trigger
     p.setPen(colors.outline);
-    p.setBrush(left_pressed ? colors.highlight : colors.button);
+    p.setBrush(left_trigger.pressed ? colors.highlight : colors.button);
     DrawPolygon(p, qleft_trigger);
 
     // Right trigger
-    p.setBrush(right_pressed ? colors.highlight : colors.button);
+    p.setBrush(right_trigger.pressed ? colors.highlight : colors.button);
     DrawPolygon(p, qright_trigger);
 
     // Draw L text
     p.setPen(colors.transparent);
     p.setBrush(colors.font);
-    DrawSymbol(p, center + QPointF(-132, -119 + (left_pressed ? 10 : 0)), Symbol::L, 1.7f);
+    DrawSymbol(p, center + QPointF(-132, -119 + (left_trigger.analog.value * 10.0f)), Symbol::L,
+               1.7f);
 
     // Draw R text
     p.setPen(colors.transparent);
     p.setBrush(colors.font);
-    DrawSymbol(p, center + QPointF(121.5f, -119 + (right_pressed ? 10 : 0)), Symbol::R, 1.7f);
+    DrawSymbol(p, center + QPointF(121.5f, -119 + (right_trigger.analog.value * 10.0f)), Symbol::R,
+               1.7f);
 }
 
 void PlayerControlPreview::DrawHandheldTriggers(QPainter& p, const QPointF center,
-                                                bool left_pressed, bool right_pressed) {
+                                                const Input::ButtonStatus& left_pressed,
+                                                const Input::ButtonStatus& right_pressed) {
     std::array<QPointF, left_joycon_trigger.size() / 2> qleft_trigger;
     std::array<QPointF, left_joycon_trigger.size() / 2> qright_trigger;
 
@@ -2018,23 +2043,24 @@ void PlayerControlPreview::DrawHandheldTriggers(QPainter& p, const QPointF cente
         const float left_trigger_y = left_joycon_trigger[point * 2 + 1];
 
         qleft_trigger[point] =
-            center + QPointF(left_trigger_x, left_trigger_y + (left_pressed ? 0.5f : 0));
+            center + QPointF(left_trigger_x, left_trigger_y + (left_pressed.value ? 0.5f : 0));
         qright_trigger[point] =
-            center + QPointF(-left_trigger_x, left_trigger_y + (right_pressed ? 0.5f : 0));
+            center + QPointF(-left_trigger_x, left_trigger_y + (right_pressed.value ? 0.5f : 0));
     }
 
     // Left trigger
     p.setPen(colors.outline);
-    p.setBrush(left_pressed ? colors.highlight : colors.button);
+    p.setBrush(left_pressed.value ? colors.highlight : colors.button);
     DrawPolygon(p, qleft_trigger);
 
     // Right trigger
-    p.setBrush(right_pressed ? colors.highlight : colors.button);
+    p.setBrush(right_pressed.value ? colors.highlight : colors.button);
     DrawPolygon(p, qright_trigger);
 }
 
-void PlayerControlPreview::DrawDualTriggers(QPainter& p, const QPointF center, bool left_pressed,
-                                            bool right_pressed) {
+void PlayerControlPreview::DrawDualTriggers(QPainter& p, const QPointF center,
+                                            const Input::ButtonStatus& left_pressed,
+                                            const Input::ButtonStatus& right_pressed) {
     std::array<QPointF, left_joycon_trigger.size() / 2> qleft_trigger;
     std::array<QPointF, left_joycon_trigger.size() / 2> qright_trigger;
     constexpr float size = 1.62f;
@@ -2043,25 +2069,27 @@ void PlayerControlPreview::DrawDualTriggers(QPainter& p, const QPointF center, b
         const float left_trigger_x = left_joycon_trigger[point * 2 + 0];
         const float left_trigger_y = left_joycon_trigger[point * 2 + 1];
 
-        qleft_trigger[point] = center + QPointF(left_trigger_x * size + offset,
-                                                left_trigger_y * size + (left_pressed ? 0.5f : 0));
+        qleft_trigger[point] =
+            center + QPointF(left_trigger_x * size + offset,
+                             left_trigger_y * size + (left_pressed.value ? 0.5f : 0));
         qright_trigger[point] =
             center + QPointF(-left_trigger_x * size - offset,
-                             left_trigger_y * size + (right_pressed ? 0.5f : 0));
+                             left_trigger_y * size + (right_pressed.value ? 0.5f : 0));
     }
 
     // Left trigger
     p.setPen(colors.outline);
-    p.setBrush(left_pressed ? colors.highlight : colors.button);
+    p.setBrush(left_pressed.value ? colors.highlight : colors.button);
     DrawPolygon(p, qleft_trigger);
 
     // Right trigger
-    p.setBrush(right_pressed ? colors.highlight : colors.button);
+    p.setBrush(right_pressed.value ? colors.highlight : colors.button);
     DrawPolygon(p, qright_trigger);
 }
 
 void PlayerControlPreview::DrawDualTriggersTopView(QPainter& p, const QPointF center,
-                                                   bool left_pressed, bool right_pressed) {
+                                                   const Input::ButtonStatus& left_pressed,
+                                                   const Input::ButtonStatus& right_pressed) {
     std::array<QPointF, left_joystick_L_topview.size() / 2> qleft_trigger;
     std::array<QPointF, left_joystick_L_topview.size() / 2> qright_trigger;
     constexpr float size = 0.9f;
@@ -2080,9 +2108,9 @@ void PlayerControlPreview::DrawDualTriggersTopView(QPainter& p, const QPointF ce
     }
 
     p.setPen(colors.outline);
-    p.setBrush(left_pressed ? colors.highlight : colors.button);
+    p.setBrush(left_pressed.value ? colors.highlight : colors.button);
     DrawPolygon(p, qleft_trigger);
-    p.setBrush(right_pressed ? colors.highlight : colors.button);
+    p.setBrush(right_pressed.value ? colors.highlight : colors.button);
     DrawPolygon(p, qright_trigger);
 
     // Draw L text
@@ -2097,7 +2125,8 @@ void PlayerControlPreview::DrawDualTriggersTopView(QPainter& p, const QPointF ce
 }
 
 void PlayerControlPreview::DrawDualZTriggersTopView(QPainter& p, const QPointF center,
-                                                    bool left_pressed, bool right_pressed) {
+                                                    const Input::ButtonStatus& left_pressed,
+                                                    const Input::ButtonStatus& right_pressed) {
     std::array<QPointF, left_joystick_ZL_topview.size() / 2> qleft_trigger;
     std::array<QPointF, left_joystick_ZL_topview.size() / 2> qright_trigger;
     constexpr float size = 0.9f;
@@ -2114,9 +2143,9 @@ void PlayerControlPreview::DrawDualZTriggersTopView(QPainter& p, const QPointF c
     }
 
     p.setPen(colors.outline);
-    p.setBrush(left_pressed ? colors.highlight : colors.button);
+    p.setBrush(left_pressed.value ? colors.highlight : colors.button);
     DrawPolygon(p, qleft_trigger);
-    p.setBrush(right_pressed ? colors.highlight : colors.button);
+    p.setBrush(right_pressed.value ? colors.highlight : colors.button);
     DrawPolygon(p, qright_trigger);
 
     // Draw ZL text
@@ -2130,7 +2159,8 @@ void PlayerControlPreview::DrawDualZTriggersTopView(QPainter& p, const QPointF c
     DrawSymbol(p, center + QPointF(180, -113), Symbol::ZR, 1.0f);
 }
 
-void PlayerControlPreview::DrawLeftTriggers(QPainter& p, const QPointF center, bool left_pressed) {
+void PlayerControlPreview::DrawLeftTriggers(QPainter& p, const QPointF center,
+                                            const Input::ButtonStatus& left_pressed) {
     std::array<QPointF, left_joycon_trigger.size() / 2> qleft_trigger;
     constexpr float size = 1.78f;
     constexpr float offset = 311.5f;
@@ -2138,15 +2168,16 @@ void PlayerControlPreview::DrawLeftTriggers(QPainter& p, const QPointF center, b
     for (std::size_t point = 0; point < left_joycon_trigger.size() / 2; ++point) {
         qleft_trigger[point] = center + QPointF(left_joycon_trigger[point * 2] * size + offset,
                                                 left_joycon_trigger[point * 2 + 1] * size -
-                                                    (left_pressed ? 0.5f : 1.0f));
+                                                    (left_pressed.value ? 0.5f : 1.0f));
     }
 
     p.setPen(colors.outline);
-    p.setBrush(left_pressed ? colors.highlight : colors.button);
+    p.setBrush(left_pressed.value ? colors.highlight : colors.button);
     DrawPolygon(p, qleft_trigger);
 }
 
-void PlayerControlPreview::DrawLeftZTriggers(QPainter& p, const QPointF center, bool left_pressed) {
+void PlayerControlPreview::DrawLeftZTriggers(QPainter& p, const QPointF center,
+                                             const Input::ButtonStatus& left_pressed) {
     std::array<QPointF, left_joycon_sideview_zl.size() / 2> qleft_trigger;
     constexpr float size = 1.1115f;
     constexpr float offset2 = 335;
@@ -2154,18 +2185,18 @@ void PlayerControlPreview::DrawLeftZTriggers(QPainter& p, const QPointF center,
     for (std::size_t point = 0; point < left_joycon_sideview_zl.size() / 2; ++point) {
         qleft_trigger[point] = center + QPointF(left_joycon_sideview_zl[point * 2] * size + offset2,
                                                 left_joycon_sideview_zl[point * 2 + 1] * size +
-                                                    (left_pressed ? 1.5f : 1.0f));
+                                                    (left_pressed.value ? 1.5f : 1.0f));
     }
 
     p.setPen(colors.outline);
-    p.setBrush(left_pressed ? colors.highlight : colors.button);
+    p.setBrush(left_pressed.value ? colors.highlight : colors.button);
     DrawPolygon(p, qleft_trigger);
-    p.drawArc(center.x() + 158, center.y() + (left_pressed ? -203.5f : -204.0f), 77, 77, 225 * 16,
-              44 * 16);
+    p.drawArc(center.x() + 158, center.y() + (left_pressed.value ? -203.5f : -204.0f), 77, 77,
+              225 * 16, 44 * 16);
 }
 
 void PlayerControlPreview::DrawLeftTriggersTopView(QPainter& p, const QPointF center,
-                                                   bool left_pressed) {
+                                                   const Input::ButtonStatus& left_pressed) {
     std::array<QPointF, left_joystick_L_topview.size() / 2> qleft_trigger;
 
     for (std::size_t point = 0; point < left_joystick_L_topview.size() / 2; ++point) {
@@ -2174,7 +2205,7 @@ void PlayerControlPreview::DrawLeftTriggersTopView(QPainter& p, const QPointF ce
     }
 
     p.setPen(colors.outline);
-    p.setBrush(left_pressed ? colors.highlight : colors.button);
+    p.setBrush(left_pressed.value ? colors.highlight : colors.button);
     DrawPolygon(p, qleft_trigger);
 
     // Draw L text
@@ -2184,7 +2215,7 @@ void PlayerControlPreview::DrawLeftTriggersTopView(QPainter& p, const QPointF ce
 }
 
 void PlayerControlPreview::DrawLeftZTriggersTopView(QPainter& p, const QPointF center,
-                                                    bool left_pressed) {
+                                                    const Input::ButtonStatus& left_pressed) {
     std::array<QPointF, left_joystick_ZL_topview.size() / 2> qleft_trigger;
 
     for (std::size_t point = 0; point < left_joystick_ZL_topview.size() / 2; ++point) {
@@ -2193,7 +2224,7 @@ void PlayerControlPreview::DrawLeftZTriggersTopView(QPainter& p, const QPointF c
     }
 
     p.setPen(colors.outline);
-    p.setBrush(left_pressed ? colors.highlight : colors.button);
+    p.setBrush(left_pressed.value ? colors.highlight : colors.button);
     DrawPolygon(p, qleft_trigger);
 
     // Draw ZL text
@@ -2203,7 +2234,7 @@ void PlayerControlPreview::DrawLeftZTriggersTopView(QPainter& p, const QPointF c
 }
 
 void PlayerControlPreview::DrawRightTriggers(QPainter& p, const QPointF center,
-                                             bool right_pressed) {
+                                             const Input::ButtonStatus& right_pressed) {
     std::array<QPointF, left_joycon_trigger.size() / 2> qright_trigger;
     constexpr float size = 1.78f;
     constexpr float offset = 311.5f;
@@ -2211,36 +2242,36 @@ void PlayerControlPreview::DrawRightTriggers(QPainter& p, const QPointF center,
     for (std::size_t point = 0; point < left_joycon_trigger.size() / 2; ++point) {
         qright_trigger[point] = center + QPointF(-left_joycon_trigger[point * 2] * size - offset,
                                                  left_joycon_trigger[point * 2 + 1] * size -
-                                                     (right_pressed ? 0.5f : 1.0f));
+                                                     (right_pressed.value ? 0.5f : 1.0f));
     }
 
     p.setPen(colors.outline);
-    p.setBrush(right_pressed ? colors.highlight : colors.button);
+    p.setBrush(right_pressed.value ? colors.highlight : colors.button);
     DrawPolygon(p, qright_trigger);
 }
 
 void PlayerControlPreview::DrawRightZTriggers(QPainter& p, const QPointF center,
-                                              bool right_pressed) {
+                                              const Input::ButtonStatus& right_pressed) {
     std::array<QPointF, left_joycon_sideview_zl.size() / 2> qright_trigger;
     constexpr float size = 1.1115f;
     constexpr float offset2 = 335;
 
     for (std::size_t point = 0; point < left_joycon_sideview_zl.size() / 2; ++point) {
         qright_trigger[point] =
-            center +
-            QPointF(-left_joycon_sideview_zl[point * 2] * size - offset2,
-                    left_joycon_sideview_zl[point * 2 + 1] * size + (right_pressed ? 0.5f : 0) + 1);
+            center + QPointF(-left_joycon_sideview_zl[point * 2] * size - offset2,
+                             left_joycon_sideview_zl[point * 2 + 1] * size +
+                                 (right_pressed.value ? 0.5f : 0) + 1);
     }
 
     p.setPen(colors.outline);
-    p.setBrush(right_pressed ? colors.highlight : colors.button);
+    p.setBrush(right_pressed.value ? colors.highlight : colors.button);
     DrawPolygon(p, qright_trigger);
-    p.drawArc(center.x() - 236, center.y() + (right_pressed ? -203.5f : -204.0f), 77, 77, 271 * 16,
-              44 * 16);
+    p.drawArc(center.x() - 236, center.y() + (right_pressed.value ? -203.5f : -204.0f), 77, 77,
+              271 * 16, 44 * 16);
 }
 
 void PlayerControlPreview::DrawRightTriggersTopView(QPainter& p, const QPointF center,
-                                                    bool right_pressed) {
+                                                    const Input::ButtonStatus& right_pressed) {
     std::array<QPointF, left_joystick_L_topview.size() / 2> qright_trigger;
 
     for (std::size_t point = 0; point < left_joystick_L_topview.size() / 2; ++point) {
@@ -2249,7 +2280,7 @@ void PlayerControlPreview::DrawRightTriggersTopView(QPainter& p, const QPointF c
     }
 
     p.setPen(colors.outline);
-    p.setBrush(right_pressed ? colors.highlight : colors.button);
+    p.setBrush(right_pressed.value ? colors.highlight : colors.button);
     DrawPolygon(p, qright_trigger);
 
     // Draw R text
@@ -2259,7 +2290,7 @@ void PlayerControlPreview::DrawRightTriggersTopView(QPainter& p, const QPointF c
 }
 
 void PlayerControlPreview::DrawRightZTriggersTopView(QPainter& p, const QPointF center,
-                                                     bool right_pressed) {
+                                                     const Input::ButtonStatus& right_pressed) {
     std::array<QPointF, left_joystick_ZL_topview.size() / 2> qright_trigger;
 
     for (std::size_t point = 0; point < left_joystick_ZL_topview.size() / 2; ++point) {
@@ -2268,7 +2299,7 @@ void PlayerControlPreview::DrawRightZTriggersTopView(QPainter& p, const QPointF
     }
 
     p.setPen(colors.outline);
-    p.setBrush(right_pressed ? colors.highlight : colors.button);
+    p.setBrush(right_pressed.value ? colors.highlight : colors.button);
     DrawPolygon(p, qright_trigger);
 
     // Draw ZR text
@@ -2278,13 +2309,13 @@ void PlayerControlPreview::DrawRightZTriggersTopView(QPainter& p, const QPointF
 }
 
 void PlayerControlPreview::DrawJoystick(QPainter& p, const QPointF center, float size,
-                                        bool pressed) {
+                                        const Input::ButtonStatus& pressed) {
     const float radius1 = 13.0f * size;
     const float radius2 = 9.0f * size;
 
     // Outer circle
     p.setPen(colors.outline);
-    p.setBrush(pressed ? colors.highlight : colors.button);
+    p.setBrush(pressed.value ? colors.highlight : colors.button);
     DrawCircle(p, center, radius1);
 
     // Cross
@@ -2292,17 +2323,17 @@ void PlayerControlPreview::DrawJoystick(QPainter& p, const QPointF center, float
     p.drawLine(center - QPoint(0, radius1), center + QPoint(0, radius1));
 
     // Inner circle
-    p.setBrush(pressed ? colors.highlight2 : colors.button2);
+    p.setBrush(pressed.value ? colors.highlight2 : colors.button2);
     DrawCircle(p, center, radius2);
 }
 
 void PlayerControlPreview::DrawJoystickSideview(QPainter& p, const QPointF center, float angle,
-                                                float size, bool pressed) {
+                                                float size, const Input::ButtonStatus& pressed) {
     QVector<QPointF> joystick;
     joystick.reserve(static_cast<int>(left_joystick_sideview.size() / 2));
 
     for (std::size_t point = 0; point < left_joystick_sideview.size() / 2; ++point) {
-        joystick.append(QPointF(left_joystick_sideview[point * 2] * size + (pressed ? 1 : 0),
+        joystick.append(QPointF(left_joystick_sideview[point * 2] * size + (pressed.value ? 1 : 0),
                                 left_joystick_sideview[point * 2 + 1] * size - 1));
     }
 
@@ -2314,14 +2345,15 @@ void PlayerControlPreview::DrawJoystickSideview(QPainter& p, const QPointF cente
 
     // Draw joystick
     p.setPen(colors.outline);
-    p.setBrush(pressed ? colors.highlight : colors.button);
+    p.setBrush(pressed.value ? colors.highlight : colors.button);
     p.drawPolygon(p2);
     p.drawLine(p2.at(1), p2.at(30));
     p.drawLine(p2.at(32), p2.at(71));
 }
 
 void PlayerControlPreview::DrawProJoystick(QPainter& p, const QPointF center, const QPointF offset,
-                                           float offset_scalar, bool pressed) {
+                                           float offset_scalar,
+                                           const Input::ButtonStatus& pressed) {
     const float radius1 = 24.0f;
     const float radius2 = 17.0f;
 
@@ -2339,11 +2371,11 @@ void PlayerControlPreview::DrawProJoystick(QPainter& p, const QPointF center, co
 
     // Outer circle
     p.setPen(colors.outline);
-    p.setBrush(pressed ? colors.highlight : colors.button);
+    p.setBrush(pressed.value ? colors.highlight : colors.button);
     p.drawEllipse(QPointF(0, 0), radius1 * amplitude, radius1);
 
     // Inner circle
-    p.setBrush(pressed ? colors.highlight2 : colors.button2);
+    p.setBrush(pressed.value ? colors.highlight2 : colors.button2);
 
     const float inner_offset =
         (radius1 - radius2) * 0.4f * ((offset.x() == 0 && offset.y() < 0) ? -1.0f : 1.0f);
@@ -2355,14 +2387,15 @@ void PlayerControlPreview::DrawProJoystick(QPainter& p, const QPointF center, co
     p.restore();
 }
 
-void PlayerControlPreview::DrawGCJoystick(QPainter& p, const QPointF center, bool pressed) {
+void PlayerControlPreview::DrawGCJoystick(QPainter& p, const QPointF center,
+                                          const Input::ButtonStatus& pressed) {
     // Outer circle
     p.setPen(colors.outline);
-    p.setBrush(pressed ? colors.highlight : colors.button);
+    p.setBrush(pressed.value ? colors.highlight : colors.button);
     DrawCircle(p, center, 26.0f);
 
     // Inner circle
-    p.setBrush(pressed ? colors.highlight2 : colors.button2);
+    p.setBrush(pressed.value ? colors.highlight2 : colors.button2);
     DrawCircle(p, center, 19.0f);
     p.setBrush(colors.transparent);
     DrawCircle(p, center, 13.5f);
@@ -2371,26 +2404,24 @@ void PlayerControlPreview::DrawGCJoystick(QPainter& p, const QPointF center, boo
 
 void PlayerControlPreview::DrawRawJoystick(QPainter& p, QPointF center_left, QPointF center_right) {
     using namespace Settings::NativeAnalog;
-    if (controller_type != Settings::ControllerType::LeftJoycon) {
-        DrawJoystickProperties(p, center_right, axis_values[RStick].properties);
+    if (controller_type != Core::HID::NpadType::JoyconLeft) {
+        DrawJoystickProperties(p, center_right, stick_values[RStick].x.properties);
         p.setPen(colors.indicator);
         p.setBrush(colors.indicator);
-        DrawJoystickDot(p, center_right, axis_values[RStick].raw_value,
-                        axis_values[RStick].properties);
+        DrawJoystickDot(p, center_right, stick_values[RStick], true);
         p.setPen(colors.indicator2);
         p.setBrush(colors.indicator2);
-        DrawJoystickDot(p, center_right, axis_values[RStick].value, axis_values[RStick].properties);
+        DrawJoystickDot(p, center_right, stick_values[RStick], false);
     }
 
-    if (controller_type != Settings::ControllerType::RightJoycon) {
-        DrawJoystickProperties(p, center_left, axis_values[LStick].properties);
+    if (controller_type != Core::HID::NpadType::JoyconRight) {
+        DrawJoystickProperties(p, center_left, stick_values[LStick].x.properties);
         p.setPen(colors.indicator);
         p.setBrush(colors.indicator);
-        DrawJoystickDot(p, center_left, axis_values[LStick].raw_value,
-                        axis_values[LStick].properties);
+        DrawJoystickDot(p, center_left, stick_values[LStick], true);
         p.setPen(colors.indicator2);
         p.setBrush(colors.indicator2);
-        DrawJoystickDot(p, center_left, axis_values[LStick].value, axis_values[LStick].properties);
+        DrawJoystickDot(p, center_left, stick_values[LStick], false);
     }
 }
 
@@ -2414,19 +2445,26 @@ void PlayerControlPreview::DrawJoystickProperties(QPainter& p, const QPointF cen
     DrawCircle(p, center, deadzone);
 }
 
-void PlayerControlPreview::DrawJoystickDot(QPainter& p, const QPointF center, const QPointF value,
-                                           const Input::AnalogProperties& properties) {
+void PlayerControlPreview::DrawJoystickDot(QPainter& p, const QPointF center,
+                                           const Input::StickStatus& stick, bool raw) {
     constexpr float size = 45.0f;
-    const float range = size * properties.range;
+    const float range = size * stick.x.properties.range;
 
-    // Dot pointer
-    DrawCircle(p, center + (value * range), 2);
+    if (raw) {
+        const QPointF value = QPointF(stick.x.raw_value, stick.y.raw_value) * size;
+        DrawCircle(p, center + value, 2);
+        return;
+    }
+
+    const QPointF value = QPointF(stick.x.value, stick.y.value) * range;
+    DrawCircle(p, center + value, 2);
 }
 
-void PlayerControlPreview::DrawRoundButton(QPainter& p, QPointF center, bool pressed, float width,
+void PlayerControlPreview::DrawRoundButton(QPainter& p, QPointF center,
+                                           const Input::ButtonStatus& pressed, float width,
                                            float height, Direction direction, float radius) {
     p.setBrush(button_color);
-    if (pressed) {
+    if (pressed.value) {
         switch (direction) {
         case Direction::Left:
             center.setX(center.x() - 1);
@@ -2448,17 +2486,17 @@ void PlayerControlPreview::DrawRoundButton(QPainter& p, QPointF center, bool pre
     QRectF rect = {center.x() - width, center.y() - height, width * 2.0f, height * 2.0f};
     p.drawRoundedRect(rect, radius, radius);
 }
-void PlayerControlPreview::DrawMinusButton(QPainter& p, const QPointF center, bool pressed,
-                                           int button_size) {
+void PlayerControlPreview::DrawMinusButton(QPainter& p, const QPointF center,
+                                           const Input::ButtonStatus& pressed, int button_size) {
     p.setPen(colors.outline);
-    p.setBrush(pressed ? colors.highlight : colors.button);
+    p.setBrush(pressed.value ? colors.highlight : colors.button);
     DrawRectangle(p, center, button_size, button_size / 3.0f);
 }
-void PlayerControlPreview::DrawPlusButton(QPainter& p, const QPointF center, bool pressed,
-                                          int button_size) {
+void PlayerControlPreview::DrawPlusButton(QPainter& p, const QPointF center,
+                                          const Input::ButtonStatus& pressed, int button_size) {
     // Draw outer line
     p.setPen(colors.outline);
-    p.setBrush(pressed ? colors.highlight : colors.button);
+    p.setBrush(pressed.value ? colors.highlight : colors.button);
     DrawRectangle(p, center, button_size, button_size / 3.0f);
     DrawRectangle(p, center, button_size / 3.0f, button_size);
 
@@ -2471,7 +2509,8 @@ void PlayerControlPreview::DrawPlusButton(QPainter& p, const QPointF center, boo
     DrawRectangle(p, center, button_size / 3.0f, button_size);
 }
 
-void PlayerControlPreview::DrawGCButtonX(QPainter& p, const QPointF center, bool pressed) {
+void PlayerControlPreview::DrawGCButtonX(QPainter& p, const QPointF center,
+                                         const Input::ButtonStatus& pressed) {
     std::array<QPointF, gc_button_x.size() / 2> button_x;
 
     for (std::size_t point = 0; point < gc_button_x.size() / 2; ++point) {
@@ -2479,11 +2518,12 @@ void PlayerControlPreview::DrawGCButtonX(QPainter& p, const QPointF center, bool
     }
 
     p.setPen(colors.outline);
-    p.setBrush(pressed ? colors.highlight : colors.button);
+    p.setBrush(pressed.value ? colors.highlight : colors.button);
     DrawPolygon(p, button_x);
 }
 
-void PlayerControlPreview::DrawGCButtonY(QPainter& p, const QPointF center, bool pressed) {
+void PlayerControlPreview::DrawGCButtonY(QPainter& p, const QPointF center,
+                                         const Input::ButtonStatus& pressed) {
     std::array<QPointF, gc_button_y.size() / 2> button_x;
 
     for (std::size_t point = 0; point < gc_button_y.size() / 2; ++point) {
@@ -2491,27 +2531,28 @@ void PlayerControlPreview::DrawGCButtonY(QPainter& p, const QPointF center, bool
     }
 
     p.setPen(colors.outline);
-    p.setBrush(pressed ? colors.highlight : colors.button);
+    p.setBrush(pressed.value ? colors.highlight : colors.button);
     DrawPolygon(p, button_x);
 }
 
-void PlayerControlPreview::DrawGCButtonZ(QPainter& p, const QPointF center, bool pressed) {
+void PlayerControlPreview::DrawGCButtonZ(QPainter& p, const QPointF center,
+                                         const Input::ButtonStatus& pressed) {
     std::array<QPointF, gc_button_z.size() / 2> button_x;
 
     for (std::size_t point = 0; point < gc_button_z.size() / 2; ++point) {
         button_x[point] = center + QPointF(gc_button_z[point * 2],
-                                           gc_button_z[point * 2 + 1] + (pressed ? 1 : 0));
+                                           gc_button_z[point * 2 + 1] + (pressed.value ? 1 : 0));
     }
 
     p.setPen(colors.outline);
-    p.setBrush(pressed ? colors.highlight : colors.button2);
+    p.setBrush(pressed.value ? colors.highlight : colors.button2);
     DrawPolygon(p, button_x);
 }
 
-void PlayerControlPreview::DrawCircleButton(QPainter& p, const QPointF center, bool pressed,
-                                            float button_size) {
+void PlayerControlPreview::DrawCircleButton(QPainter& p, const QPointF center,
+                                            const Input::ButtonStatus& pressed, float button_size) {
     p.setBrush(button_color);
-    if (pressed) {
+    if (pressed.value) {
         p.setBrush(colors.highlight);
     }
     p.drawEllipse(center, button_size, button_size);
@@ -2540,7 +2581,8 @@ void PlayerControlPreview::DrawArrowButtonOutline(QPainter& p, const QPointF cen
 }
 
 void PlayerControlPreview::DrawArrowButton(QPainter& p, const QPointF center,
-                                           const Direction direction, bool pressed, float size) {
+                                           const Direction direction,
+                                           const Input::ButtonStatus& pressed, float size) {
     std::array<QPointF, up_arrow_button.size() / 2> arrow_button;
     QPoint offset;
 
@@ -2567,8 +2609,8 @@ void PlayerControlPreview::DrawArrowButton(QPainter& p, const QPointF center,
     }
 
     // Draw arrow button
-    p.setPen(pressed ? colors.highlight : colors.button);
-    p.setBrush(pressed ? colors.highlight : colors.button);
+    p.setPen(pressed.value ? colors.highlight : colors.button);
+    p.setBrush(pressed.value ? colors.highlight : colors.button);
     DrawPolygon(p, arrow_button);
 
     switch (direction) {
@@ -2596,7 +2638,8 @@ void PlayerControlPreview::DrawArrowButton(QPainter& p, const QPointF center,
 }
 
 void PlayerControlPreview::DrawTriggerButton(QPainter& p, const QPointF center,
-                                             const Direction direction, bool pressed) {
+                                             const Direction direction,
+                                             const Input::ButtonStatus& pressed) {
     std::array<QPointF, trigger_button.size() / 2> qtrigger_button;
 
     for (std::size_t point = 0; point < trigger_button.size() / 2; ++point) {
@@ -2619,10 +2662,44 @@ void PlayerControlPreview::DrawTriggerButton(QPainter& p, const QPointF center,
 
     // Draw arrow button
     p.setPen(colors.outline);
-    p.setBrush(pressed ? colors.highlight : colors.button);
+    p.setBrush(pressed.value ? colors.highlight : colors.button);
     DrawPolygon(p, qtrigger_button);
 }
 
+void PlayerControlPreview::DrawBattery(QPainter& p, QPointF center, Input::BatteryLevel battery) {
+    p.setPen(colors.outline);
+    p.setBrush(colors.transparent);
+    p.drawRect(center.x(), center.y(), 56, 20);
+    p.drawRect(center.x() + 56, center.y() + 6, 3, 8);
+    p.setBrush(colors.deadzone);
+    switch (battery) {
+    case Input::BatteryLevel::Charging:
+        p.setBrush(colors.indicator2);
+        p.drawText(center + QPoint(2, 14), tr("Charging"));
+        break;
+    case Input::BatteryLevel::Full:
+        p.drawRect(center.x() + 42, center.y(), 14, 20);
+        p.drawRect(center.x() + 28, center.y(), 14, 20);
+        p.drawRect(center.x() + 14, center.y(), 14, 20);
+        break;
+    case Input::BatteryLevel::Medium:
+        p.drawRect(center.x() + 28, center.y(), 14, 20);
+        p.drawRect(center.x() + 14, center.y(), 14, 20);
+        p.drawRect(center.x(), center.y(), 14, 20);
+        break;
+    case Input::BatteryLevel::Low:
+        p.drawRect(center.x() + 14, center.y(), 14, 20);
+        p.drawRect(center.x(), center.y(), 14, 20);
+        break;
+    case Input::BatteryLevel::Critical:
+        p.drawRect(center.x(), center.y(), 14, 20);
+        break;
+    case Input::BatteryLevel::Empty:
+        p.drawRect(center.x(), center.y(), 5, 20);
+        break;
+    }
+}
+
 void PlayerControlPreview::DrawSymbol(QPainter& p, const QPointF center, Symbol symbol,
                                       float icon_size) {
     std::array<QPointF, house.size() / 2> house_icon;
diff --git a/src/yuzu/configuration/configure_input_player_widget.h b/src/yuzu/configuration/configure_input_player_widget.h
index f4bbfa5281..b44a2e347d 100644
--- a/src/yuzu/configuration/configure_input_player_widget.h
+++ b/src/yuzu/configuration/configure_input_player_widget.h
@@ -7,9 +7,10 @@
 #include <array>
 #include <QFrame>
 #include <QPointer>
+#include "common/input.h"
 #include "common/settings.h"
-#include "core/frontend/input.h"
-#include "yuzu/debugger/controller.h"
+#include "core/hid/hid_core.h"
+#include "core/hid/hid_types.h"
 
 class QLabel;
 
@@ -24,17 +25,12 @@ public:
     explicit PlayerControlPreview(QWidget* parent);
     ~PlayerControlPreview() override;
 
-    void SetPlayerInput(std::size_t index, const ButtonParam& buttons_param,
-                        const AnalogParam& analogs_param);
-    void SetPlayerInputRaw(std::size_t index, const Settings::ButtonsRaw& buttons_,
-                           Settings::AnalogsRaw analogs_);
-    void SetConnectedStatus(bool checked);
-    void SetControllerType(Settings::ControllerType type);
+    void SetController(Core::HID::EmulatedController* controller);
     void BeginMappingButton(std::size_t button_id);
     void BeginMappingAnalog(std::size_t button_id);
     void EndMapping();
+    void ControllerUpdate(Core::HID::ControllerTriggerType type);
     void UpdateInput();
-    void SetCallBack(ControllerCallback callback_);
 
 protected:
     void paintEvent(QPaintEvent* event) override;
@@ -63,15 +59,6 @@ private:
         SR,
     };
 
-    struct AxisValue {
-        QPointF value{};
-        QPointF raw_value{};
-        Input::AnalogProperties properties{};
-        int size{};
-        QPoint offset{};
-        bool active{};
-    };
-
     struct LedPattern {
         bool position1;
         bool position2;
@@ -122,47 +109,66 @@ private:
     void DrawGCBody(QPainter& p, QPointF center);
 
     // Draw triggers functions
-    void DrawProTriggers(QPainter& p, QPointF center, bool left_pressed, bool right_pressed);
-    void DrawGCTriggers(QPainter& p, QPointF center, bool left_pressed, bool right_pressed);
-    void DrawHandheldTriggers(QPainter& p, QPointF center, bool left_pressed, bool right_pressed);
-    void DrawDualTriggers(QPainter& p, QPointF center, bool left_pressed, bool right_pressed);
-    void DrawDualTriggersTopView(QPainter& p, QPointF center, bool left_pressed,
-                                 bool right_pressed);
-    void DrawDualZTriggersTopView(QPainter& p, QPointF center, bool left_pressed,
-                                  bool right_pressed);
-    void DrawLeftTriggers(QPainter& p, QPointF center, bool left_pressed);
-    void DrawLeftZTriggers(QPainter& p, QPointF center, bool left_pressed);
-    void DrawLeftTriggersTopView(QPainter& p, QPointF center, bool left_pressed);
-    void DrawLeftZTriggersTopView(QPainter& p, QPointF center, bool left_pressed);
-    void DrawRightTriggers(QPainter& p, QPointF center, bool right_pressed);
-    void DrawRightZTriggers(QPainter& p, QPointF center, bool right_pressed);
-    void DrawRightTriggersTopView(QPainter& p, QPointF center, bool right_pressed);
-    void DrawRightZTriggersTopView(QPainter& p, QPointF center, bool right_pressed);
+    void DrawProTriggers(QPainter& p, QPointF center, const Input::ButtonStatus& left_pressed,
+                         const Input::ButtonStatus& right_pressed);
+    void DrawGCTriggers(QPainter& p, QPointF center, Input::TriggerStatus left_trigger,
+                        Input::TriggerStatus right_trigger);
+    void DrawHandheldTriggers(QPainter& p, QPointF center, const Input::ButtonStatus& left_pressed,
+                              const Input::ButtonStatus& right_pressed);
+    void DrawDualTriggers(QPainter& p, QPointF center, const Input::ButtonStatus& left_pressed,
+                          const Input::ButtonStatus& right_pressed);
+    void DrawDualTriggersTopView(QPainter& p, QPointF center,
+                                 const Input::ButtonStatus& left_pressed,
+                                 const Input::ButtonStatus& right_pressed);
+    void DrawDualZTriggersTopView(QPainter& p, QPointF center,
+                                  const Input::ButtonStatus& left_pressed,
+                                  const Input::ButtonStatus& right_pressed);
+    void DrawLeftTriggers(QPainter& p, QPointF center, const Input::ButtonStatus& left_pressed);
+    void DrawLeftZTriggers(QPainter& p, QPointF center, const Input::ButtonStatus& left_pressed);
+    void DrawLeftTriggersTopView(QPainter& p, QPointF center,
+                                 const Input::ButtonStatus& left_pressed);
+    void DrawLeftZTriggersTopView(QPainter& p, QPointF center,
+                                  const Input::ButtonStatus& left_pressed);
+    void DrawRightTriggers(QPainter& p, QPointF center, const Input::ButtonStatus& right_pressed);
+    void DrawRightZTriggers(QPainter& p, QPointF center, const Input::ButtonStatus& right_pressed);
+    void DrawRightTriggersTopView(QPainter& p, QPointF center,
+                                  const Input::ButtonStatus& right_pressed);
+    void DrawRightZTriggersTopView(QPainter& p, QPointF center,
+                                   const Input::ButtonStatus& right_pressed);
 
     // Draw joystick functions
-    void DrawJoystick(QPainter& p, QPointF center, float size, bool pressed);
-    void DrawJoystickSideview(QPainter& p, QPointF center, float angle, float size, bool pressed);
+    void DrawJoystick(QPainter& p, QPointF center, float size, const Input::ButtonStatus& pressed);
+    void DrawJoystickSideview(QPainter& p, QPointF center, float angle, float size,
+                              const Input::ButtonStatus& pressed);
     void DrawRawJoystick(QPainter& p, QPointF center_left, QPointF center_right);
     void DrawJoystickProperties(QPainter& p, QPointF center,
                                 const Input::AnalogProperties& properties);
-    void DrawJoystickDot(QPainter& p, QPointF center, QPointF value,
-                         const Input::AnalogProperties& properties);
-    void DrawProJoystick(QPainter& p, QPointF center, QPointF offset, float scalar, bool pressed);
-    void DrawGCJoystick(QPainter& p, QPointF center, bool pressed);
+    void DrawJoystickDot(QPainter& p, QPointF center, const Input::StickStatus& stick, bool raw);
+    void DrawProJoystick(QPainter& p, QPointF center, QPointF offset, float scalar,
+                         const Input::ButtonStatus& pressed);
+    void DrawGCJoystick(QPainter& p, QPointF center, const Input::ButtonStatus& pressed);
 
     // Draw button functions
-    void DrawCircleButton(QPainter& p, QPointF center, bool pressed, float button_size);
-    void DrawRoundButton(QPainter& p, QPointF center, bool pressed, float width, float height,
-                         Direction direction = Direction::None, float radius = 2);
-    void DrawMinusButton(QPainter& p, QPointF center, bool pressed, int button_size);
-    void DrawPlusButton(QPainter& p, QPointF center, bool pressed, int button_size);
-    void DrawGCButtonX(QPainter& p, QPointF center, bool pressed);
-    void DrawGCButtonY(QPainter& p, QPointF center, bool pressed);
-    void DrawGCButtonZ(QPainter& p, QPointF center, bool pressed);
+    void DrawCircleButton(QPainter& p, QPointF center, const Input::ButtonStatus& pressed,
+                          float button_size);
+    void DrawRoundButton(QPainter& p, QPointF center, const Input::ButtonStatus& pressed,
+                         float width, float height, Direction direction = Direction::None,
+                         float radius = 2);
+    void DrawMinusButton(QPainter& p, QPointF center, const Input::ButtonStatus& pressed,
+                         int button_size);
+    void DrawPlusButton(QPainter& p, QPointF center, const Input::ButtonStatus& pressed,
+                        int button_size);
+    void DrawGCButtonX(QPainter& p, QPointF center, const Input::ButtonStatus& pressed);
+    void DrawGCButtonY(QPainter& p, QPointF center, const Input::ButtonStatus& pressed);
+    void DrawGCButtonZ(QPainter& p, QPointF center, const Input::ButtonStatus& pressed);
     void DrawArrowButtonOutline(QPainter& p, const QPointF center, float size = 1.0f);
-    void DrawArrowButton(QPainter& p, QPointF center, Direction direction, bool pressed,
-                         float size = 1.0f);
-    void DrawTriggerButton(QPainter& p, QPointF center, Direction direction, bool pressed);
+    void DrawArrowButton(QPainter& p, QPointF center, Direction direction,
+                         const Input::ButtonStatus& pressed, float size = 1.0f);
+    void DrawTriggerButton(QPainter& p, QPointF center, Direction direction,
+                           const Input::ButtonStatus& pressed);
+
+    // Draw battery functions
+    void DrawBattery(QPainter& p, QPointF center, Input::BatteryLevel battery);
 
     // Draw icon functions
     void DrawSymbol(QPainter& p, QPointF center, Symbol symbol, float icon_size);
@@ -178,24 +184,23 @@ private:
     void SetTextFont(QPainter& p, float text_size,
                      const QString& font_family = QStringLiteral("sans-serif"));
 
-    using ButtonArray =
-        std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::BUTTON_NS_END>;
-    using StickArray =
-        std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>;
+    bool is_controller_set{};
+    bool is_connected{};
+    bool needs_redraw{};
+    Core::HID::NpadType controller_type;
 
-    ControllerCallback controller_callback;
-    bool is_enabled{};
     bool mapping_active{};
     int blink_counter{};
+    int callback_key;
     QColor button_color{};
     ColorMapping colors{};
     std::array<QColor, 4> led_color{};
-    ButtonArray buttons{};
-    StickArray sticks{};
     std::size_t player_index{};
-    std::size_t button_mapping_index{Settings::NativeButton::BUTTON_NS_END};
-    std::size_t analog_mapping_index{Settings::NativeAnalog::NUM_STICKS_HID};
-    std::array<AxisValue, Settings::NativeAnalog::NUM_STICKS_HID> axis_values{};
-    std::array<bool, Settings::NativeButton::NumButtons> button_values{};
-    Settings::ControllerType controller_type{Settings::ControllerType::ProController};
+    Core::HID::EmulatedController* controller;
+    std::size_t button_mapping_index{Settings::NativeButton::NumButtons};
+    std::size_t analog_mapping_index{Settings::NativeAnalog::NumAnalogs};
+    Core::HID::ButtonValues button_values{};
+    Core::HID::SticksValues stick_values{};
+    Core::HID::TriggerValues trigger_values{};
+    Core::HID::BatteryValues battery_values{};
 };
diff --git a/src/yuzu/configuration/configure_motion_touch.cpp b/src/yuzu/configuration/configure_motion_touch.cpp
index f8e08c4222..9fd1a919f2 100644
--- a/src/yuzu/configuration/configure_motion_touch.cpp
+++ b/src/yuzu/configuration/configure_motion_touch.cpp
@@ -15,9 +15,9 @@
 
 #include "common/logging/log.h"
 #include "common/settings.h"
+#include "input_common/drivers/udp_client.h"
+#include "input_common/helpers/udp_protocol.h"
 #include "input_common/main.h"
-#include "input_common/udp/client.h"
-#include "input_common/udp/udp.h"
 #include "ui_configure_motion_touch.h"
 #include "yuzu/configuration/configure_motion_touch.h"
 #include "yuzu/configuration/configure_touch_from_button.h"
diff --git a/src/yuzu/configuration/configure_mouse_advanced.cpp b/src/yuzu/configuration/configure_mouse_advanced.cpp
index 2af3afda84..1e7a3751df 100644
--- a/src/yuzu/configuration/configure_mouse_advanced.cpp
+++ b/src/yuzu/configuration/configure_mouse_advanced.cpp
@@ -11,8 +11,11 @@
 
 #include "common/assert.h"
 #include "common/param_package.h"
+#include "input_common/drivers/keyboard.h"
+#include "input_common/drivers/mouse.h"
 #include "input_common/main.h"
 #include "ui_configure_mouse_advanced.h"
+#include "yuzu/bootmanager.h"
 #include "yuzu/configuration/config.h"
 #include "yuzu/configuration/configure_mouse_advanced.h"
 
@@ -101,7 +104,7 @@ ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent,
                 [=, this](const Common::ParamPackage& params) {
                     buttons_param[button_id] = params;
                 },
-                InputCommon::Polling::DeviceType::Button);
+                InputCommon::Polling::InputType::Button);
         });
         connect(button, &QPushButton::customContextMenuRequested,
                 [=, this](const QPoint& menu_location) {
@@ -127,13 +130,10 @@ ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent,
     connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
 
     connect(poll_timer.get(), &QTimer::timeout, [this] {
-        Common::ParamPackage params;
-        for (auto& poller : device_pollers) {
-            params = poller->GetNextInput();
-            if (params.Has("engine")) {
-                SetPollingResult(params, false);
-                return;
-            }
+        const auto& params = input_subsystem->GetNextInput();
+        if (params.Has("engine")) {
+            SetPollingResult(params, false);
+            return;
         }
     });
 
@@ -196,26 +196,13 @@ void ConfigureMouseAdvanced::UpdateButtonLabels() {
 
 void ConfigureMouseAdvanced::HandleClick(
     QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
-    InputCommon::Polling::DeviceType type) {
+    InputCommon::Polling::InputType type) {
     button->setText(tr("[press key]"));
     button->setFocus();
 
-    // Keyboard keys or mouse buttons can only be used as button devices
-    want_keyboard_mouse = type == InputCommon::Polling::DeviceType::Button;
-    if (want_keyboard_mouse) {
-        const auto iter = std::find(button_map.begin(), button_map.end(), button);
-        ASSERT(iter != button_map.end());
-        const auto index = std::distance(button_map.begin(), iter);
-        ASSERT(index < Settings::NativeButton::NumButtons && index >= 0);
-    }
-
     input_setter = new_input_setter;
 
-    device_pollers = input_subsystem->GetPollers(type);
-
-    for (auto& poller : device_pollers) {
-        poller->Start();
-    }
+    input_subsystem->BeginMapping(type);
 
     QWidget::grabMouse();
     QWidget::grabKeyboard();
@@ -227,9 +214,7 @@ void ConfigureMouseAdvanced::HandleClick(
 void ConfigureMouseAdvanced::SetPollingResult(const Common::ParamPackage& params, bool abort) {
     timeout_timer->stop();
     poll_timer->stop();
-    for (auto& poller : device_pollers) {
-        poller->Stop();
-    }
+    input_subsystem->StopMapping();
 
     QWidget::releaseMouse();
     QWidget::releaseKeyboard();
@@ -247,15 +232,8 @@ void ConfigureMouseAdvanced::mousePressEvent(QMouseEvent* event) {
         return;
     }
 
-    if (want_keyboard_mouse) {
-        SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->button())},
-                         false);
-    } else {
-        // We don't want any mouse buttons, so don't stop polling
-        return;
-    }
-
-    SetPollingResult({}, true);
+    const auto button = GRenderWindow::QtButtonToMouseButton(event->button());
+    input_subsystem->GetMouse()->PressButton(0, 0, 0, 0, button);
 }
 
 void ConfigureMouseAdvanced::keyPressEvent(QKeyEvent* event) {
@@ -264,13 +242,6 @@ void ConfigureMouseAdvanced::keyPressEvent(QKeyEvent* event) {
     }
 
     if (event->key() != Qt::Key_Escape) {
-        if (want_keyboard_mouse) {
-            SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
-                             false);
-        } else {
-            // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling
-            return;
-        }
+        input_subsystem->GetKeyboard()->PressKey(event->key());
     }
-    SetPollingResult({}, true);
 }
diff --git a/src/yuzu/configuration/configure_mouse_advanced.h b/src/yuzu/configuration/configure_mouse_advanced.h
index 65b6fca9aa..5fa534eaf2 100644
--- a/src/yuzu/configuration/configure_mouse_advanced.h
+++ b/src/yuzu/configuration/configure_mouse_advanced.h
@@ -46,7 +46,7 @@ private:
     /// Called when the button was pressed.
     void HandleClick(QPushButton* button,
                      std::function<void(const Common::ParamPackage&)> new_input_setter,
-                     InputCommon::Polling::DeviceType type);
+                     InputCommon::Polling::InputType type);
 
     /// Finish polling and configure input using the input_setter
     void SetPollingResult(const Common::ParamPackage& params, bool abort);
@@ -67,12 +67,6 @@ private:
     std::array<QPushButton*, Settings::NativeMouseButton::NumMouseButtons> button_map;
     std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons> buttons_param;
 
-    std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers;
-
     std::unique_ptr<QTimer> timeout_timer;
     std::unique_ptr<QTimer> poll_timer;
-
-    /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false,
-    /// keyboard events are ignored.
-    bool want_keyboard_mouse = false;
 };
diff --git a/src/yuzu/configuration/configure_touch_from_button.cpp b/src/yuzu/configuration/configure_touch_from_button.cpp
index 40129f2289..bde0a08c4b 100644
--- a/src/yuzu/configuration/configure_touch_from_button.cpp
+++ b/src/yuzu/configuration/configure_touch_from_button.cpp
@@ -163,13 +163,10 @@ void ConfigureTouchFromButton::ConnectEvents() {
     connect(timeout_timer.get(), &QTimer::timeout, [this]() { SetPollingResult({}, true); });
 
     connect(poll_timer.get(), &QTimer::timeout, [this]() {
-        Common::ParamPackage params;
-        for (auto& poller : device_pollers) {
-            params = poller->GetNextInput();
-            if (params.Has("engine")) {
-                SetPollingResult(params, false);
-                return;
-            }
+        const auto& params = input_subsystem->GetNextInput();
+        if (params.Has("engine")) {
+            SetPollingResult(params, false);
+            return;
         }
     });
 }
@@ -248,11 +245,7 @@ void ConfigureTouchFromButton::GetButtonInput(const int row_index, const bool is
         }
     };
 
-    device_pollers = input_subsystem->GetPollers(InputCommon::Polling::DeviceType::Button);
-
-    for (auto& poller : device_pollers) {
-        poller->Start();
-    }
+    input_subsystem->BeginMapping(InputCommon::Polling::InputType::Button);
 
     grabKeyboard();
     grabMouse();
@@ -365,14 +358,14 @@ void ConfigureTouchFromButton::SetCoordinates(const int dot_id, const QPoint& po
 
 void ConfigureTouchFromButton::SetPollingResult(const Common::ParamPackage& params,
                                                 const bool cancel) {
+    timeout_timer->stop();
+    poll_timer->stop();
+    input_subsystem->StopMapping();
+
     releaseKeyboard();
     releaseMouse();
     qApp->restoreOverrideCursor();
-    timeout_timer->stop();
-    poll_timer->stop();
-    for (auto& poller : device_pollers) {
-        poller->Stop();
-    }
+
     if (input_setter) {
         (*input_setter)(params, cancel);
         input_setter.reset();
diff --git a/src/yuzu/configuration/configure_touch_from_button.h b/src/yuzu/configuration/configure_touch_from_button.h
index d9513e3bc6..e1400481a2 100644
--- a/src/yuzu/configuration/configure_touch_from_button.h
+++ b/src/yuzu/configuration/configure_touch_from_button.h
@@ -24,10 +24,6 @@ namespace InputCommon {
 class InputSubsystem;
 }
 
-namespace InputCommon::Polling {
-class DevicePoller;
-}
-
 namespace Settings {
 struct TouchFromButtonMap;
 }
@@ -85,7 +81,6 @@ private:
 
     std::unique_ptr<QTimer> timeout_timer;
     std::unique_ptr<QTimer> poll_timer;
-    std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers;
     std::optional<std::function<void(const Common::ParamPackage&, bool)>> input_setter;
 
     static constexpr int DataRoleDot = Qt::ItemDataRole::UserRole + 2;