Browse Source

core/hid: Add emulated controllers

german77 4 years ago
parent
commit
c3f54ff232

+ 8 - 0
src/core/CMakeLists.txt

@@ -135,6 +135,14 @@ add_library(core STATIC
     frontend/input.h
     hardware_interrupt_manager.cpp
     hardware_interrupt_manager.h
+    hid/emulated_console.cpp
+    hid/emulated_console.h
+    hid/emulated_controller.cpp
+    hid/emulated_controller.h
+    hid/emulated_devices.cpp
+    hid/emulated_devices.h
+    hid/hid_core.cpp
+    hid/hid_core.h
     hid/hid_types.h
     hid/input_converter.cpp
     hid/input_converter.h

+ 208 - 0
src/core/hid/emulated_console.cpp

@@ -0,0 +1,208 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included
+
+#include <fmt/format.h>
+
+#include "core/hid/emulated_console.h"
+#include "core/hid/input_converter.h"
+
+namespace Core::HID {
+EmulatedConsole::EmulatedConsole() {}
+
+EmulatedConsole::~EmulatedConsole() = default;
+
+void EmulatedConsole::ReloadFromSettings() {
+    // Using first motion device from player 1. No need to assign a special config at the moment
+    const auto& player = Settings::values.players.GetValue()[0];
+    motion_params = Common::ParamPackage(player.motions[0]);
+
+    ReloadInput();
+}
+
+void EmulatedConsole::ReloadInput() {
+    motion_devices = Input::CreateDevice<Input::InputDevice>(motion_params);
+    if (motion_devices) {
+        Input::InputCallback motion_callback{
+            [this](Input::CallbackStatus callback) { SetMotion(callback); }};
+        motion_devices->SetCallback(motion_callback);
+    }
+
+    // TODO: Fix this mess
+    std::size_t index = 0;
+    const std::string mouse_device_string =
+        fmt::format("engine:mouse,axis_x:10,axis_y:11,button:{}", index);
+    touch_devices[index] = Input::CreateDeviceFromString<Input::InputDevice>(mouse_device_string);
+    Input::InputCallback trigger_callbackk{
+        [this, index](Input::CallbackStatus callback) { SetTouch(callback, index); }};
+    touch_devices[index]->SetCallback(trigger_callbackk);
+
+    index++;
+    const auto button_index =
+        static_cast<u64>(Settings::values.touch_from_button_map_index.GetValue());
+    const auto& touch_buttons = Settings::values.touch_from_button_maps[button_index].buttons;
+    for (const auto& config_entry : touch_buttons) {
+        Common::ParamPackage params{config_entry};
+        Common::ParamPackage touch_button_params;
+        const int x = params.Get("x", 0);
+        const int y = params.Get("y", 0);
+        params.Erase("x");
+        params.Erase("y");
+        touch_button_params.Set("engine", "touch_from_button");
+        touch_button_params.Set("button", params.Serialize());
+        touch_button_params.Set("x", x);
+        touch_button_params.Set("y", y);
+        touch_button_params.Set("touch_id", static_cast<int>(index));
+        LOG_ERROR(Common, "{} ", touch_button_params.Serialize());
+        touch_devices[index] =
+            Input::CreateDeviceFromString<Input::InputDevice>(touch_button_params.Serialize());
+        if (!touch_devices[index]) {
+            continue;
+        }
+
+        Input::InputCallback trigger_callback{
+            [this, index](Input::CallbackStatus callback) { SetTouch(callback, index); }};
+        touch_devices[index]->SetCallback(trigger_callback);
+        index++;
+    }
+}
+
+void EmulatedConsole::UnloadInput() {
+    motion_devices.reset();
+    for (auto& touch : touch_devices) {
+        touch.reset();
+    }
+}
+
+void EmulatedConsole::EnableConfiguration() {
+    is_configuring = true;
+    SaveCurrentConfig();
+}
+
+void EmulatedConsole::DisableConfiguration() {
+    is_configuring = false;
+}
+
+bool EmulatedConsole::IsConfiguring() const {
+    return is_configuring;
+}
+
+void EmulatedConsole::SaveCurrentConfig() {
+    if (!is_configuring) {
+        return;
+    }
+}
+
+void EmulatedConsole::RestoreConfig() {
+    if (!is_configuring) {
+        return;
+    }
+    ReloadFromSettings();
+}
+
+Common::ParamPackage EmulatedConsole::GetMotionParam() const {
+    return motion_params;
+}
+
+void EmulatedConsole::SetMotionParam(Common::ParamPackage param) {
+    motion_params = param;
+    ReloadInput();
+}
+
+void EmulatedConsole::SetMotion(Input::CallbackStatus callback) {
+    std::lock_guard lock{mutex};
+    auto& raw_status = console.motion_values.raw_status;
+    auto& emulated = console.motion_values.emulated;
+
+    raw_status = TransformToMotion(callback);
+    emulated.SetAcceleration(Common::Vec3f{
+        raw_status.accel.x.value,
+        raw_status.accel.y.value,
+        raw_status.accel.z.value,
+    });
+    emulated.SetGyroscope(Common::Vec3f{
+        raw_status.gyro.x.value,
+        raw_status.gyro.y.value,
+        raw_status.gyro.z.value,
+    });
+    emulated.UpdateRotation(raw_status.delta_timestamp);
+    emulated.UpdateOrientation(raw_status.delta_timestamp);
+
+    if (is_configuring) {
+        TriggerOnChange(ConsoleTriggerType::Motion);
+        return;
+    }
+
+    auto& motion = console.motion_state;
+    motion.accel = emulated.GetAcceleration();
+    motion.gyro = emulated.GetGyroscope();
+    motion.rotation = emulated.GetGyroscope();
+    motion.orientation = emulated.GetOrientation();
+    motion.quaternion = emulated.GetQuaternion();
+    motion.is_at_rest = emulated.IsMoving(motion_sensitivity);
+
+    TriggerOnChange(ConsoleTriggerType::Motion);
+}
+
+void EmulatedConsole::SetTouch(Input::CallbackStatus callback, [[maybe_unused]] std::size_t index) {
+    if (index >= console.touch_values.size()) {
+        return;
+    }
+    std::lock_guard lock{mutex};
+
+    console.touch_values[index] = TransformToTouch(callback);
+
+    if (is_configuring) {
+        TriggerOnChange(ConsoleTriggerType::Touch);
+        return;
+    }
+
+    console.touch_state[index] = {
+        .position = {console.touch_values[index].x.value, console.touch_values[index].y.value},
+        .id = console.touch_values[index].id,
+        .pressed = console.touch_values[index].pressed.value,
+    };
+
+    TriggerOnChange(ConsoleTriggerType::Touch);
+}
+
+ConsoleMotionValues EmulatedConsole::GetMotionValues() const {
+    return console.motion_values;
+}
+
+TouchValues EmulatedConsole::GetTouchValues() const {
+    return console.touch_values;
+}
+
+ConsoleMotion EmulatedConsole::GetMotion() const {
+    return console.motion_state;
+}
+
+TouchFingerState EmulatedConsole::GetTouch() const {
+    return console.touch_state;
+}
+
+void EmulatedConsole::TriggerOnChange(ConsoleTriggerType type) {
+    for (const std::pair<int, ConsoleUpdateCallback> poller_pair : callback_list) {
+        const ConsoleUpdateCallback& poller = poller_pair.second;
+        if (poller.on_change) {
+            poller.on_change(type);
+        }
+    }
+}
+
+int EmulatedConsole::SetCallback(ConsoleUpdateCallback update_callback) {
+    std::lock_guard lock{mutex};
+    callback_list.insert_or_assign(last_callback_key, update_callback);
+    return last_callback_key++;
+}
+
+void EmulatedConsole::DeleteCallback(int key) {
+    std::lock_guard lock{mutex};
+    if (!callback_list.contains(key)) {
+        LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
+        return;
+    }
+    callback_list.erase(key);
+}
+} // namespace Core::HID

+ 142 - 0
src/core/hid/emulated_console.h

@@ -0,0 +1,142 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <mutex>
+#include <unordered_map>
+
+#include "common/input.h"
+#include "common/param_package.h"
+#include "common/point.h"
+#include "common/quaternion.h"
+#include "common/settings.h"
+#include "common/vector_math.h"
+#include "core/hid/hid_types.h"
+#include "core/hid/motion_input.h"
+
+namespace Core::HID {
+
+struct ConsoleMotionInfo {
+    Input::MotionStatus raw_status;
+    MotionInput emulated{};
+};
+
+using ConsoleMotionDevices = std::unique_ptr<Input::InputDevice>;
+using TouchDevices = std::array<std::unique_ptr<Input::InputDevice>, 16>;
+
+using ConsoleMotionParams = Common::ParamPackage;
+using TouchParams = std::array<Common::ParamPackage, 16>;
+
+using ConsoleMotionValues = ConsoleMotionInfo;
+using TouchValues = std::array<Input::TouchStatus, 16>;
+
+struct TouchFinger {
+    u64_le last_touch{};
+    Common::Point<float> position{};
+    u32_le id{};
+    bool pressed{};
+    Core::HID::TouchAttribute attribute{};
+};
+
+struct ConsoleMotion {
+    bool is_at_rest{};
+    Common::Vec3f accel{};
+    Common::Vec3f gyro{};
+    Common::Vec3f rotation{};
+    std::array<Common::Vec3f, 3> orientation{};
+    Common::Quaternion<f32> quaternion{};
+};
+
+using TouchFingerState = std::array<TouchFinger, 16>;
+
+struct ConsoleStatus {
+    // Data from input_common
+    ConsoleMotionValues motion_values{};
+    TouchValues touch_values{};
+
+    // Data for Nintendo devices;
+    ConsoleMotion motion_state{};
+    TouchFingerState touch_state{};
+};
+
+enum class ConsoleTriggerType {
+    Motion,
+    Touch,
+    All,
+};
+
+struct ConsoleUpdateCallback {
+    std::function<void(ConsoleTriggerType)> on_change;
+};
+
+class EmulatedConsole {
+public:
+    /**
+     * TODO: Write description
+     *
+     * @param npad_id_type
+     */
+    explicit EmulatedConsole();
+    ~EmulatedConsole();
+
+    YUZU_NON_COPYABLE(EmulatedConsole);
+    YUZU_NON_MOVEABLE(EmulatedConsole);
+
+    void ReloadFromSettings();
+    void ReloadInput();
+    void UnloadInput();
+
+    void EnableConfiguration();
+    void DisableConfiguration();
+    bool IsConfiguring() const;
+    void SaveCurrentConfig();
+    void RestoreConfig();
+
+    Common::ParamPackage GetMotionParam() const;
+
+    void SetMotionParam(Common::ParamPackage param);
+
+    ConsoleMotionValues GetMotionValues() const;
+    TouchValues GetTouchValues() const;
+
+    ConsoleMotion GetMotion() const;
+    TouchFingerState GetTouch() const;
+
+    int SetCallback(ConsoleUpdateCallback update_callback);
+    void DeleteCallback(int key);
+
+private:
+    /**
+     * Sets the status of a button. Applies toggle properties to the output.
+     *
+     * @param A CallbackStatus and a button index number
+     */
+    void SetMotion(Input::CallbackStatus callback);
+    void SetTouch(Input::CallbackStatus callback, std::size_t index);
+
+    /**
+     * Triggers a callback that something has changed
+     *
+     * @param Input type of the trigger
+     */
+    void TriggerOnChange(ConsoleTriggerType type);
+
+    bool is_configuring{false};
+    f32 motion_sensitivity{0.01f};
+
+    ConsoleMotionParams motion_params;
+    TouchParams touch_params;
+
+    ConsoleMotionDevices motion_devices;
+    TouchDevices touch_devices;
+
+    mutable std::mutex mutex;
+    std::unordered_map<int, ConsoleUpdateCallback> callback_list;
+    int last_callback_key = 0;
+    ConsoleStatus console;
+};
+
+} // namespace Core::HID

+ 745 - 0
src/core/hid/emulated_controller.cpp

@@ -0,0 +1,745 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included
+
+#include <fmt/format.h>
+
+#include "core/hid/emulated_controller.h"
+#include "core/hid/input_converter.h"
+
+namespace Core::HID {
+constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
+constexpr s32 HID_TRIGGER_MAX = 0x7fff;
+
+EmulatedController::EmulatedController(NpadIdType npad_id_type_) : npad_id_type(npad_id_type_) {}
+
+EmulatedController::~EmulatedController() = default;
+
+NpadType EmulatedController::MapSettingsTypeToNPad(Settings::ControllerType type) {
+    switch (type) {
+    case Settings::ControllerType::ProController:
+        return NpadType::ProController;
+    case Settings::ControllerType::DualJoyconDetached:
+        return NpadType::JoyconDual;
+    case Settings::ControllerType::LeftJoycon:
+        return NpadType::JoyconLeft;
+    case Settings::ControllerType::RightJoycon:
+        return NpadType::JoyconRight;
+    case Settings::ControllerType::Handheld:
+        return NpadType::Handheld;
+    case Settings::ControllerType::GameCube:
+        return NpadType::GameCube;
+    default:
+        return NpadType::ProController;
+    }
+}
+
+Settings::ControllerType EmulatedController::MapNPadToSettingsType(NpadType type) {
+    switch (type) {
+    case NpadType::ProController:
+        return Settings::ControllerType::ProController;
+    case NpadType::JoyconDual:
+        return Settings::ControllerType::DualJoyconDetached;
+    case NpadType::JoyconLeft:
+        return Settings::ControllerType::LeftJoycon;
+    case NpadType::JoyconRight:
+        return Settings::ControllerType::RightJoycon;
+    case NpadType::Handheld:
+        return Settings::ControllerType::Handheld;
+    case NpadType::GameCube:
+        return Settings::ControllerType::GameCube;
+    default:
+        return Settings::ControllerType::ProController;
+    }
+}
+
+void EmulatedController::ReloadFromSettings() {
+    const auto player_index = NpadIdTypeToIndex(npad_id_type);
+    const auto& player = Settings::values.players.GetValue()[player_index];
+
+    for (std::size_t index = 0; index < player.buttons.size(); ++index) {
+        button_params[index] = Common::ParamPackage(player.buttons[index]);
+    }
+    for (std::size_t index = 0; index < player.analogs.size(); ++index) {
+        stick_params[index] = Common::ParamPackage(player.analogs[index]);
+    }
+    for (std::size_t index = 0; index < player.motions.size(); ++index) {
+        motion_params[index] = Common::ParamPackage(player.motions[index]);
+    }
+    ReloadInput();
+}
+
+void EmulatedController::ReloadInput() {
+    const auto player_index = NpadIdTypeToIndex(npad_id_type);
+    const auto& player = Settings::values.players.GetValue()[player_index];
+    const auto left_side = button_params[Settings::NativeButton::ZL];
+    const auto right_side = button_params[Settings::NativeButton::ZR];
+
+    std::transform(button_params.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
+                   button_params.begin() + Settings::NativeButton::BUTTON_NS_END,
+                   button_devices.begin(), Input::CreateDevice<Input::InputDevice>);
+    std::transform(stick_params.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
+                   stick_params.begin() + Settings::NativeAnalog::STICK_HID_END,
+                   stick_devices.begin(), Input::CreateDevice<Input::InputDevice>);
+    std::transform(motion_params.begin() + Settings::NativeMotion::MOTION_HID_BEGIN,
+                   motion_params.begin() + Settings::NativeMotion::MOTION_HID_END,
+                   motion_devices.begin(), Input::CreateDevice<Input::InputDevice>);
+
+    trigger_devices[0] =
+        Input::CreateDevice<Input::InputDevice>(button_params[Settings::NativeButton::ZL]);
+    trigger_devices[1] =
+        Input::CreateDevice<Input::InputDevice>(button_params[Settings::NativeButton::ZR]);
+
+    controller.colors_state.left = {
+        .body = player.body_color_left,
+        .button = player.button_color_left,
+    };
+
+    controller.colors_state.right = {
+        .body = player.body_color_right,
+        .button = player.button_color_right,
+    };
+
+    controller.colors_state.fullkey = controller.colors_state.left;
+
+    battery_devices[0] = Input::CreateDevice<Input::InputDevice>(left_side);
+    battery_devices[1] = Input::CreateDevice<Input::InputDevice>(right_side);
+
+    for (std::size_t index = 0; index < button_devices.size(); ++index) {
+        if (!button_devices[index]) {
+            continue;
+        }
+        Input::InputCallback button_callback{
+            [this, index](Input::CallbackStatus callback) { SetButton(callback, index); }};
+        button_devices[index]->SetCallback(button_callback);
+    }
+
+    for (std::size_t index = 0; index < stick_devices.size(); ++index) {
+        if (!stick_devices[index]) {
+            continue;
+        }
+        Input::InputCallback stick_callback{
+            [this, index](Input::CallbackStatus callback) { SetStick(callback, index); }};
+        stick_devices[index]->SetCallback(stick_callback);
+    }
+
+    for (std::size_t index = 0; index < trigger_devices.size(); ++index) {
+        if (!trigger_devices[index]) {
+            continue;
+        }
+        Input::InputCallback trigger_callback{
+            [this, index](Input::CallbackStatus callback) { SetTrigger(callback, index); }};
+        trigger_devices[index]->SetCallback(trigger_callback);
+    }
+
+    for (std::size_t index = 0; index < battery_devices.size(); ++index) {
+        if (!battery_devices[index]) {
+            continue;
+        }
+        Input::InputCallback battery_callback{
+            [this, index](Input::CallbackStatus callback) { SetBattery(callback, index); }};
+        battery_devices[index]->SetCallback(battery_callback);
+    }
+
+    for (std::size_t index = 0; index < motion_devices.size(); ++index) {
+        if (!motion_devices[index]) {
+            continue;
+        }
+        Input::InputCallback motion_callback{
+            [this, index](Input::CallbackStatus callback) { SetMotion(callback, index); }};
+        motion_devices[index]->SetCallback(motion_callback);
+    }
+
+    SetNpadType(MapSettingsTypeToNPad(player.controller_type));
+
+    if (player.connected) {
+        Connect();
+    } else {
+        Disconnect();
+    }
+}
+
+void EmulatedController::UnloadInput() {
+    for (auto& button : button_devices) {
+        button.reset();
+    }
+    for (auto& stick : stick_devices) {
+        stick.reset();
+    }
+    for (auto& motion : motion_devices) {
+        motion.reset();
+    }
+    for (auto& trigger : trigger_devices) {
+        trigger.reset();
+    }
+    for (auto& battery : battery_devices) {
+        battery.reset();
+    }
+}
+
+void EmulatedController::EnableConfiguration() {
+    is_configuring = true;
+    SaveCurrentConfig();
+}
+
+void EmulatedController::DisableConfiguration() {
+    is_configuring = false;
+}
+
+bool EmulatedController::IsConfiguring() const {
+    return is_configuring;
+}
+
+void EmulatedController::SaveCurrentConfig() {
+    if (!is_configuring) {
+        return;
+    }
+
+    const auto player_index = NpadIdTypeToIndex(npad_id_type);
+    auto& player = Settings::values.players.GetValue()[player_index];
+
+    for (std::size_t index = 0; index < player.buttons.size(); ++index) {
+        player.buttons[index] = button_params[index].Serialize();
+    }
+    for (std::size_t index = 0; index < player.analogs.size(); ++index) {
+        player.analogs[index] = stick_params[index].Serialize();
+    }
+    for (std::size_t index = 0; index < player.motions.size(); ++index) {
+        player.motions[index] = motion_params[index].Serialize();
+    }
+}
+
+void EmulatedController::RestoreConfig() {
+    if (!is_configuring) {
+        return;
+    }
+    ReloadFromSettings();
+}
+
+std::vector<Common::ParamPackage> EmulatedController::GetMappedDevices() const {
+    std::vector<Common::ParamPackage> devices;
+    for (const auto& param : button_params) {
+        if (!param.Has("engine")) {
+            continue;
+        }
+        const auto devices_it = std::find_if(
+            devices.begin(), devices.end(), [param](const Common::ParamPackage param_) {
+                return param.Get("engine", "") == param_.Get("engine", "") &&
+                       param.Get("guid", "") == param_.Get("guid", "") &&
+                       param.Get("port", "") == param_.Get("port", "");
+            });
+        if (devices_it != devices.end()) {
+            continue;
+        }
+        Common::ParamPackage device{};
+        device.Set("engine", param.Get("engine", ""));
+        device.Set("guid", param.Get("guid", ""));
+        device.Set("port", param.Get("port", ""));
+        devices.push_back(device);
+    }
+
+    for (const auto& param : stick_params) {
+        if (!param.Has("engine")) {
+            continue;
+        }
+        if (param.Get("engine", "") == "analog_from_button") {
+            continue;
+        }
+        const auto devices_it = std::find_if(
+            devices.begin(), devices.end(), [param](const Common::ParamPackage param_) {
+                return param.Get("engine", "") == param_.Get("engine", "") &&
+                       param.Get("guid", "") == param_.Get("guid", "") &&
+                       param.Get("port", "") == param_.Get("port", "");
+            });
+        if (devices_it != devices.end()) {
+            continue;
+        }
+        Common::ParamPackage device{};
+        device.Set("engine", param.Get("engine", ""));
+        device.Set("guid", param.Get("guid", ""));
+        device.Set("port", param.Get("port", ""));
+        devices.push_back(device);
+    }
+    return devices;
+}
+
+Common::ParamPackage EmulatedController::GetButtonParam(std::size_t index) const {
+    if (index >= button_params.size()) {
+        return {};
+    }
+    return button_params[index];
+}
+
+Common::ParamPackage EmulatedController::GetStickParam(std::size_t index) const {
+    if (index >= stick_params.size()) {
+        return {};
+    }
+    return stick_params[index];
+}
+
+Common::ParamPackage EmulatedController::GetMotionParam(std::size_t index) const {
+    if (index >= motion_params.size()) {
+        return {};
+    }
+    return motion_params[index];
+}
+
+void EmulatedController::SetButtonParam(std::size_t index, Common::ParamPackage param) {
+    if (index >= button_params.size()) {
+        return;
+    }
+    button_params[index] = param;
+    ReloadInput();
+}
+
+void EmulatedController::SetStickParam(std::size_t index, Common::ParamPackage param) {
+    if (index >= stick_params.size()) {
+        return;
+    }
+    stick_params[index] = param;
+    ReloadInput();
+}
+
+void EmulatedController::SetMotionParam(std::size_t index, Common::ParamPackage param) {
+    if (index >= motion_params.size()) {
+        return;
+    }
+    motion_params[index] = param;
+    ReloadInput();
+}
+
+void EmulatedController::SetButton(Input::CallbackStatus callback, std::size_t index) {
+    if (index >= controller.button_values.size()) {
+        return;
+    }
+    std::lock_guard lock{mutex};
+    bool value_changed = false;
+    const auto new_status = TransformToButton(callback);
+    auto& current_status = controller.button_values[index];
+    current_status.toggle = new_status.toggle;
+
+    // Update button status with current
+    if (!current_status.toggle) {
+        current_status.locked = false;
+        if (current_status.value != new_status.value) {
+            current_status.value = new_status.value;
+            value_changed = true;
+        }
+    } else {
+        // Toggle button and lock status
+        if (new_status.value && !current_status.locked) {
+            current_status.locked = true;
+            current_status.value = !current_status.value;
+            value_changed = true;
+        }
+
+        // Unlock button ready for next press
+        if (!new_status.value && current_status.locked) {
+            current_status.locked = false;
+        }
+    }
+
+    if (!value_changed) {
+        return;
+    }
+
+    if (is_configuring) {
+        controller.npad_button_state.raw = NpadButton::None;
+        controller.debug_pad_button_state.raw = 0;
+        TriggerOnChange(ControllerTriggerType::Button);
+        return;
+    }
+
+    switch (index) {
+    case Settings::NativeButton::A:
+        controller.npad_button_state.a.Assign(current_status.value);
+        controller.debug_pad_button_state.a.Assign(current_status.value);
+        break;
+    case Settings::NativeButton::B:
+        controller.npad_button_state.b.Assign(current_status.value);
+        controller.debug_pad_button_state.b.Assign(current_status.value);
+        break;
+    case Settings::NativeButton::X:
+        controller.npad_button_state.x.Assign(current_status.value);
+        controller.debug_pad_button_state.x.Assign(current_status.value);
+        break;
+    case Settings::NativeButton::Y:
+        controller.npad_button_state.y.Assign(current_status.value);
+        controller.debug_pad_button_state.y.Assign(current_status.value);
+        break;
+    case Settings::NativeButton::LStick:
+        controller.npad_button_state.stick_l.Assign(current_status.value);
+        break;
+    case Settings::NativeButton::RStick:
+        controller.npad_button_state.stick_r.Assign(current_status.value);
+        break;
+    case Settings::NativeButton::L:
+        controller.npad_button_state.l.Assign(current_status.value);
+        controller.debug_pad_button_state.l.Assign(current_status.value);
+        break;
+    case Settings::NativeButton::R:
+        controller.npad_button_state.r.Assign(current_status.value);
+        controller.debug_pad_button_state.r.Assign(current_status.value);
+        break;
+    case Settings::NativeButton::ZL:
+        controller.npad_button_state.zl.Assign(current_status.value);
+        controller.debug_pad_button_state.zl.Assign(current_status.value);
+        break;
+    case Settings::NativeButton::ZR:
+        controller.npad_button_state.zr.Assign(current_status.value);
+        controller.debug_pad_button_state.zr.Assign(current_status.value);
+        break;
+    case Settings::NativeButton::Plus:
+        controller.npad_button_state.plus.Assign(current_status.value);
+        controller.debug_pad_button_state.plus.Assign(current_status.value);
+        break;
+    case Settings::NativeButton::Minus:
+        controller.npad_button_state.minus.Assign(current_status.value);
+        controller.debug_pad_button_state.minus.Assign(current_status.value);
+        break;
+    case Settings::NativeButton::DLeft:
+        controller.npad_button_state.left.Assign(current_status.value);
+        controller.debug_pad_button_state.d_left.Assign(current_status.value);
+        break;
+    case Settings::NativeButton::DUp:
+        controller.npad_button_state.up.Assign(current_status.value);
+        controller.debug_pad_button_state.d_up.Assign(current_status.value);
+        break;
+    case Settings::NativeButton::DRight:
+        controller.npad_button_state.right.Assign(current_status.value);
+        controller.debug_pad_button_state.d_right.Assign(current_status.value);
+        break;
+    case Settings::NativeButton::DDown:
+        controller.npad_button_state.down.Assign(current_status.value);
+        controller.debug_pad_button_state.d_down.Assign(current_status.value);
+        break;
+    case Settings::NativeButton::SL:
+        controller.npad_button_state.left_sl.Assign(current_status.value);
+        controller.npad_button_state.right_sl.Assign(current_status.value);
+        break;
+    case Settings::NativeButton::SR:
+        controller.npad_button_state.left_sr.Assign(current_status.value);
+        controller.npad_button_state.right_sr.Assign(current_status.value);
+        break;
+    case Settings::NativeButton::Home:
+    case Settings::NativeButton::Screenshot:
+        break;
+    }
+    TriggerOnChange(ControllerTriggerType::Button);
+}
+
+void EmulatedController::SetStick(Input::CallbackStatus callback, std::size_t index) {
+    if (index >= controller.stick_values.size()) {
+        return;
+    }
+    std::lock_guard lock{mutex};
+    controller.stick_values[index] = TransformToStick(callback);
+
+    if (is_configuring) {
+        controller.analog_stick_state.left = {};
+        controller.analog_stick_state.right = {};
+        TriggerOnChange(ControllerTriggerType::Stick);
+        return;
+    }
+
+    const AnalogStickState stick{
+        .x = static_cast<s32>(controller.stick_values[index].x.value * HID_JOYSTICK_MAX),
+        .y = static_cast<s32>(controller.stick_values[index].y.value * HID_JOYSTICK_MAX),
+    };
+
+    switch (index) {
+    case Settings::NativeAnalog::LStick:
+        controller.analog_stick_state.left = stick;
+        controller.npad_button_state.stick_l_left.Assign(controller.stick_values[index].left);
+        controller.npad_button_state.stick_l_up.Assign(controller.stick_values[index].up);
+        controller.npad_button_state.stick_l_right.Assign(controller.stick_values[index].right);
+        controller.npad_button_state.stick_l_down.Assign(controller.stick_values[index].down);
+        break;
+    case Settings::NativeAnalog::RStick:
+        controller.analog_stick_state.right = stick;
+        controller.npad_button_state.stick_r_left.Assign(controller.stick_values[index].left);
+        controller.npad_button_state.stick_r_up.Assign(controller.stick_values[index].up);
+        controller.npad_button_state.stick_r_right.Assign(controller.stick_values[index].right);
+        controller.npad_button_state.stick_r_down.Assign(controller.stick_values[index].down);
+        break;
+    }
+
+    TriggerOnChange(ControllerTriggerType::Stick);
+}
+
+void EmulatedController::SetTrigger(Input::CallbackStatus callback, std::size_t index) {
+    if (index >= controller.trigger_values.size()) {
+        return;
+    }
+    std::lock_guard lock{mutex};
+    controller.trigger_values[index] = TransformToTrigger(callback);
+
+    if (is_configuring) {
+        controller.gc_trigger_state.left = 0;
+        controller.gc_trigger_state.right = 0;
+        TriggerOnChange(ControllerTriggerType::Trigger);
+        return;
+    }
+
+    const auto trigger = controller.trigger_values[index];
+
+    switch (index) {
+    case Settings::NativeTrigger::LTrigger:
+        controller.gc_trigger_state.left = static_cast<s32>(trigger.analog.value * HID_TRIGGER_MAX);
+        controller.npad_button_state.zl.Assign(trigger.pressed);
+        break;
+    case Settings::NativeTrigger::RTrigger:
+        controller.gc_trigger_state.right =
+            static_cast<s32>(trigger.analog.value * HID_TRIGGER_MAX);
+        controller.npad_button_state.zr.Assign(trigger.pressed);
+        break;
+    }
+
+    TriggerOnChange(ControllerTriggerType::Trigger);
+}
+
+void EmulatedController::SetMotion(Input::CallbackStatus callback, std::size_t index) {
+    if (index >= controller.motion_values.size()) {
+        return;
+    }
+    std::lock_guard lock{mutex};
+    auto& raw_status = controller.motion_values[index].raw_status;
+    auto& emulated = controller.motion_values[index].emulated;
+
+    raw_status = TransformToMotion(callback);
+    emulated.SetAcceleration(Common::Vec3f{
+        raw_status.accel.x.value,
+        raw_status.accel.y.value,
+        raw_status.accel.z.value,
+    });
+    emulated.SetGyroscope(Common::Vec3f{
+        raw_status.gyro.x.value,
+        raw_status.gyro.y.value,
+        raw_status.gyro.z.value,
+    });
+    emulated.UpdateRotation(raw_status.delta_timestamp);
+    emulated.UpdateOrientation(raw_status.delta_timestamp);
+
+    if (is_configuring) {
+        TriggerOnChange(ControllerTriggerType::Motion);
+        return;
+    }
+
+    auto& motion = controller.motion_state[index];
+    motion.accel = emulated.GetAcceleration();
+    motion.gyro = emulated.GetGyroscope();
+    motion.rotation = emulated.GetGyroscope();
+    motion.orientation = emulated.GetOrientation();
+    motion.is_at_rest = emulated.IsMoving(motion_sensitivity);
+
+    TriggerOnChange(ControllerTriggerType::Motion);
+}
+
+void EmulatedController::SetBattery(Input::CallbackStatus callback, std::size_t index) {
+    if (index >= controller.battery_values.size()) {
+        return;
+    }
+    std::lock_guard lock{mutex};
+    controller.battery_values[index] = TransformToBattery(callback);
+
+    if (is_configuring) {
+        TriggerOnChange(ControllerTriggerType::Battery);
+        return;
+    }
+
+    bool is_charging = false;
+    bool is_powered = false;
+    BatteryLevel battery_level = 0;
+    switch (controller.battery_values[index]) {
+    case Input::BatteryLevel::Charging:
+        is_charging = true;
+        is_powered = true;
+        battery_level = 6;
+        break;
+    case Input::BatteryLevel::Medium:
+        battery_level = 6;
+        break;
+    case Input::BatteryLevel::Low:
+        battery_level = 4;
+        break;
+    case Input::BatteryLevel::Critical:
+        battery_level = 2;
+        break;
+    case Input::BatteryLevel::Empty:
+        battery_level = 0;
+        break;
+    case Input::BatteryLevel::Full:
+    default:
+        is_powered = true;
+        battery_level = 8;
+        break;
+    }
+
+    switch (index) {
+    case 0:
+        controller.battery_state.left = {
+            .is_powered = is_powered,
+            .is_charging = is_charging,
+            .battery_level = battery_level,
+        };
+        break;
+    case 1:
+        controller.battery_state.right = {
+            .is_powered = is_powered,
+            .is_charging = is_charging,
+            .battery_level = battery_level,
+        };
+        break;
+    case 2:
+        controller.battery_state.dual = {
+            .is_powered = is_powered,
+            .is_charging = is_charging,
+            .battery_level = battery_level,
+        };
+        break;
+    }
+    TriggerOnChange(ControllerTriggerType::Battery);
+}
+
+bool EmulatedController::SetVibration([[maybe_unused]] std::size_t device_index,
+                                      [[maybe_unused]] VibrationValue vibration) {
+    return false;
+}
+
+int EmulatedController::TestVibration(std::size_t device_index) {
+    return 1;
+}
+
+void EmulatedController::Connect() {
+    std::lock_guard lock{mutex};
+    if (is_connected) {
+        LOG_WARNING(Service_HID, "Tried to turn on a connected controller {}", npad_id_type);
+        return;
+    }
+    is_connected = true;
+    TriggerOnChange(ControllerTriggerType::Connected);
+}
+
+void EmulatedController::Disconnect() {
+    std::lock_guard lock{mutex};
+    if (!is_connected) {
+        LOG_WARNING(Service_HID, "Tried to turn off a disconnected controller {}", npad_id_type);
+        return;
+    }
+    is_connected = false;
+    TriggerOnChange(ControllerTriggerType::Disconnected);
+}
+
+bool EmulatedController::IsConnected() const {
+    return is_connected;
+}
+
+bool EmulatedController::IsVibrationEnabled() const {
+    return is_vibration_enabled;
+}
+
+NpadIdType EmulatedController::GetNpadIdType() const {
+    return npad_id_type;
+}
+
+NpadType EmulatedController::GetNpadType() const {
+    return npad_type;
+}
+
+void EmulatedController::SetNpadType(NpadType npad_type_) {
+    std::lock_guard lock{mutex};
+    if (npad_type == npad_type_) {
+        return;
+    }
+    npad_type = npad_type_;
+    TriggerOnChange(ControllerTriggerType::Type);
+}
+
+ButtonValues EmulatedController::GetButtonsValues() const {
+    return controller.button_values;
+}
+
+SticksValues EmulatedController::GetSticksValues() const {
+    return controller.stick_values;
+}
+
+TriggerValues EmulatedController::GetTriggersValues() const {
+    return controller.trigger_values;
+}
+
+ControllerMotionValues EmulatedController::GetMotionValues() const {
+    return controller.motion_values;
+}
+
+ColorValues EmulatedController::GetColorsValues() const {
+    return controller.color_values;
+}
+
+BatteryValues EmulatedController::GetBatteryValues() const {
+    return controller.battery_values;
+}
+
+NpadButtonState EmulatedController::GetNpadButtons() const {
+    if (is_configuring) {
+        return {};
+    }
+    return controller.npad_button_state;
+}
+
+DebugPadButton EmulatedController::GetDebugPadButtons() const {
+    if (is_configuring) {
+        return {};
+    }
+    return controller.debug_pad_button_state;
+}
+
+AnalogSticks EmulatedController::GetSticks() const {
+    if (is_configuring) {
+        return {};
+    }
+    return controller.analog_stick_state;
+}
+
+NpadGcTriggerState EmulatedController::GetTriggers() const {
+    if (is_configuring) {
+        return {};
+    }
+    return controller.gc_trigger_state;
+}
+
+MotionState EmulatedController::GetMotions() const {
+    return controller.motion_state;
+}
+
+ControllerColors EmulatedController::GetColors() const {
+    return controller.colors_state;
+}
+
+BatteryLevelState EmulatedController::GetBattery() const {
+    return controller.battery_state;
+}
+
+void EmulatedController::TriggerOnChange(ControllerTriggerType type) {
+    for (const std::pair<int, ControllerUpdateCallback> poller_pair : callback_list) {
+        const ControllerUpdateCallback& poller = poller_pair.second;
+        if (poller.on_change) {
+            poller.on_change(type);
+        }
+    }
+}
+
+int EmulatedController::SetCallback(ControllerUpdateCallback update_callback) {
+    std::lock_guard lock{mutex};
+    callback_list.insert_or_assign(last_callback_key, update_callback);
+    return last_callback_key++;
+}
+
+void EmulatedController::DeleteCallback(int key) {
+    std::lock_guard lock{mutex};
+    if (!callback_list.contains(key)) {
+        LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
+        return;
+    }
+    callback_list.erase(key);
+}
+} // namespace Core::HID

+ 232 - 0
src/core/hid/emulated_controller.h

@@ -0,0 +1,232 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <mutex>
+#include <unordered_map>
+
+#include "common/input.h"
+#include "common/param_package.h"
+#include "common/point.h"
+#include "common/quaternion.h"
+#include "common/settings.h"
+#include "common/vector_math.h"
+#include "core/hid/hid_types.h"
+#include "core/hid/motion_input.h"
+
+namespace Core::HID {
+
+struct ControllerMotionInfo {
+    Input::MotionStatus raw_status;
+    MotionInput emulated{};
+};
+
+using ButtonDevices =
+    std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeButton::NumButtons>;
+using StickDevices =
+    std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeAnalog::NumAnalogs>;
+using ControllerMotionDevices =
+    std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeMotion::NumMotions>;
+using TriggerDevices =
+    std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeTrigger::NumTriggers>;
+using BatteryDevices = std::array<std::unique_ptr<Input::InputDevice>, 2>;
+
+using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>;
+using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>;
+using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>;
+using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>;
+using BatteryParams = std::array<Common::ParamPackage, 2>;
+
+using ButtonValues = std::array<Input::ButtonStatus, Settings::NativeButton::NumButtons>;
+using SticksValues = std::array<Input::StickStatus, Settings::NativeAnalog::NumAnalogs>;
+using TriggerValues = std::array<Input::TriggerStatus, Settings::NativeTrigger::NumTriggers>;
+using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::NativeMotion::NumMotions>;
+using ColorValues = std::array<Input::BodyColorStatus, 3>;
+using BatteryValues = std::array<Input::BatteryStatus, 3>;
+using VibrationValues = std::array<Input::VibrationStatus, 2>;
+
+struct AnalogSticks {
+    AnalogStickState left;
+    AnalogStickState right;
+};
+
+struct ControllerColors {
+    NpadControllerColor fullkey;
+    NpadControllerColor left;
+    NpadControllerColor right;
+};
+
+struct BatteryLevelState {
+    NpadPowerInfo dual;
+    NpadPowerInfo left;
+    NpadPowerInfo right;
+};
+
+struct ControllerMotion {
+    bool is_at_rest;
+    Common::Vec3f accel{};
+    Common::Vec3f gyro{};
+    Common::Vec3f rotation{};
+    std::array<Common::Vec3f, 3> orientation{};
+};
+
+using MotionState = std::array<ControllerMotion, 2>;
+
+struct ControllerStatus {
+    // Data from input_common
+    ButtonValues button_values{};
+    SticksValues stick_values{};
+    ControllerMotionValues motion_values{};
+    TriggerValues trigger_values{};
+    ColorValues color_values{};
+    BatteryValues battery_values{};
+    VibrationValues vibration_values{};
+
+    // Data for Nintendo devices
+    NpadButtonState npad_button_state{};
+    DebugPadButton debug_pad_button_state{};
+    AnalogSticks analog_stick_state{};
+    MotionState motion_state{};
+    NpadGcTriggerState gc_trigger_state{};
+    ControllerColors colors_state{};
+    BatteryLevelState battery_state{};
+};
+enum class ControllerTriggerType {
+    Button,
+    Stick,
+    Trigger,
+    Motion,
+    Color,
+    Battery,
+    Vibration,
+    Connected,
+    Disconnected,
+    Type,
+    All,
+};
+
+struct ControllerUpdateCallback {
+    std::function<void(ControllerTriggerType)> on_change;
+};
+
+class EmulatedController {
+public:
+    /**
+     * TODO: Write description
+     *
+     * @param npad_id_type
+     */
+    explicit EmulatedController(NpadIdType npad_id_type_);
+    ~EmulatedController();
+
+    YUZU_NON_COPYABLE(EmulatedController);
+    YUZU_NON_MOVEABLE(EmulatedController);
+
+    static NpadType MapSettingsTypeToNPad(Settings::ControllerType type);
+    static Settings::ControllerType MapNPadToSettingsType(NpadType type);
+
+    /// Gets the NpadIdType for this controller.
+    NpadIdType GetNpadIdType() const;
+
+    /// Sets the NpadType for this controller.
+    void SetNpadType(NpadType npad_type_);
+
+    /// Gets the NpadType for this controller.
+    NpadType GetNpadType() const;
+
+    void Connect();
+    void Disconnect();
+
+    bool IsConnected() const;
+    bool IsVibrationEnabled() const;
+
+    void ReloadFromSettings();
+    void ReloadInput();
+    void UnloadInput();
+
+    void EnableConfiguration();
+    void DisableConfiguration();
+    bool IsConfiguring() const;
+    void SaveCurrentConfig();
+    void RestoreConfig();
+
+    std::vector<Common::ParamPackage> GetMappedDevices() const;
+
+    Common::ParamPackage GetButtonParam(std::size_t index) const;
+    Common::ParamPackage GetStickParam(std::size_t index) const;
+    Common::ParamPackage GetMotionParam(std::size_t index) const;
+
+    void SetButtonParam(std::size_t index, Common::ParamPackage param);
+    void SetStickParam(std::size_t index, Common::ParamPackage param);
+    void SetMotionParam(std::size_t index, Common::ParamPackage param);
+
+    ButtonValues GetButtonsValues() const;
+    SticksValues GetSticksValues() const;
+    TriggerValues GetTriggersValues() const;
+    ControllerMotionValues GetMotionValues() const;
+    ColorValues GetColorsValues() const;
+    BatteryValues GetBatteryValues() const;
+
+    NpadButtonState GetNpadButtons() const;
+    DebugPadButton GetDebugPadButtons() const;
+    AnalogSticks GetSticks() const;
+    NpadGcTriggerState GetTriggers() const;
+    MotionState GetMotions() const;
+    ControllerColors GetColors() const;
+    BatteryLevelState GetBattery() const;
+
+    bool SetVibration(std::size_t device_index, VibrationValue vibration);
+    int TestVibration(std::size_t device_index);
+
+    int SetCallback(ControllerUpdateCallback update_callback);
+    void DeleteCallback(int key);
+
+private:
+    /**
+     * Sets the status of a button. Applies toggle properties to the output.
+     *
+     * @param A CallbackStatus and a button index number
+     */
+    void SetButton(Input::CallbackStatus callback, std::size_t index);
+    void SetStick(Input::CallbackStatus callback, std::size_t index);
+    void SetTrigger(Input::CallbackStatus callback, std::size_t index);
+    void SetMotion(Input::CallbackStatus callback, std::size_t index);
+    void SetBattery(Input::CallbackStatus callback, std::size_t index);
+
+    /**
+     * Triggers a callback that something has changed
+     *
+     * @param Input type of the trigger
+     */
+    void TriggerOnChange(ControllerTriggerType type);
+
+    NpadIdType npad_id_type;
+    NpadType npad_type{NpadType::None};
+    bool is_connected{false};
+    bool is_configuring{false};
+    bool is_vibration_enabled{true};
+    f32 motion_sensitivity{0.01f};
+
+    ButtonParams button_params;
+    StickParams stick_params;
+    ControllerMotionParams motion_params;
+    TriggerParams trigger_params;
+    BatteryParams battery_params;
+
+    ButtonDevices button_devices;
+    StickDevices stick_devices;
+    ControllerMotionDevices motion_devices;
+    TriggerDevices trigger_devices;
+    BatteryDevices battery_devices;
+    // VibrationDevices vibration_devices;
+
+    mutable std::mutex mutex;
+    std::unordered_map<int, ControllerUpdateCallback> callback_list;
+    int last_callback_key = 0;
+    ControllerStatus controller;
+};
+
+} // namespace Core::HID

+ 349 - 0
src/core/hid/emulated_devices.cpp

@@ -0,0 +1,349 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included
+
+#include <fmt/format.h>
+
+#include "core/hid/emulated_devices.h"
+#include "core/hid/input_converter.h"
+
+namespace Core::HID {
+
+EmulatedDevices::EmulatedDevices() {}
+
+EmulatedDevices::~EmulatedDevices() = default;
+
+void EmulatedDevices::ReloadFromSettings() {
+    const auto& mouse = Settings::values.mouse_buttons;
+
+    for (std::size_t index = 0; index < mouse.size(); ++index) {
+        mouse_button_params[index] = Common::ParamPackage(mouse[index]);
+    }
+    ReloadInput();
+}
+
+void EmulatedDevices::ReloadInput() {
+    std::transform(mouse_button_params.begin() + Settings::NativeMouseButton::MOUSE_HID_BEGIN,
+                   mouse_button_params.begin() + Settings::NativeMouseButton::MOUSE_HID_END,
+                   mouse_button_devices.begin(), Input::CreateDevice<Input::InputDevice>);
+
+    std::transform(Settings::values.keyboard_keys.begin(), Settings::values.keyboard_keys.end(),
+                   keyboard_devices.begin(), Input::CreateDeviceFromString<Input::InputDevice>);
+
+    std::transform(Settings::values.keyboard_mods.begin(), Settings::values.keyboard_mods.end(),
+                   keyboard_modifier_devices.begin(),
+                   Input::CreateDeviceFromString<Input::InputDevice>);
+
+    for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) {
+        if (!mouse_button_devices[index]) {
+            continue;
+        }
+        Input::InputCallback button_callback{
+            [this, index](Input::CallbackStatus callback) { SetMouseButton(callback, index); }};
+        mouse_button_devices[index]->SetCallback(button_callback);
+    }
+
+    for (std::size_t index = 0; index < keyboard_devices.size(); ++index) {
+        if (!keyboard_devices[index]) {
+            continue;
+        }
+        Input::InputCallback button_callback{
+            [this, index](Input::CallbackStatus callback) { SetKeyboardButton(callback, index); }};
+        keyboard_devices[index]->SetCallback(button_callback);
+    }
+
+    for (std::size_t index = 0; index < keyboard_modifier_devices.size(); ++index) {
+        if (!keyboard_modifier_devices[index]) {
+            continue;
+        }
+        Input::InputCallback button_callback{[this, index](Input::CallbackStatus callback) {
+            SetKeyboardModifier(callback, index);
+        }};
+        keyboard_modifier_devices[index]->SetCallback(button_callback);
+    }
+}
+
+void EmulatedDevices::UnloadInput() {
+    for (auto& button : mouse_button_devices) {
+        button.reset();
+    }
+    for (auto& button : keyboard_devices) {
+        button.reset();
+    }
+    for (auto& button : keyboard_modifier_devices) {
+        button.reset();
+    }
+}
+
+void EmulatedDevices::EnableConfiguration() {
+    is_configuring = true;
+    SaveCurrentConfig();
+}
+
+void EmulatedDevices::DisableConfiguration() {
+    is_configuring = false;
+}
+
+bool EmulatedDevices::IsConfiguring() const {
+    return is_configuring;
+}
+
+void EmulatedDevices::SaveCurrentConfig() {
+    if (!is_configuring) {
+        return;
+    }
+
+    auto& mouse = Settings::values.mouse_buttons;
+
+    for (std::size_t index = 0; index < mouse.size(); ++index) {
+        mouse[index] = mouse_button_params[index].Serialize();
+    }
+}
+
+void EmulatedDevices::RestoreConfig() {
+    if (!is_configuring) {
+        return;
+    }
+    ReloadFromSettings();
+}
+
+Common::ParamPackage EmulatedDevices::GetMouseButtonParam(std::size_t index) const {
+    if (index >= mouse_button_params.size()) {
+        return {};
+    }
+    return mouse_button_params[index];
+}
+
+void EmulatedDevices::SetButtonParam(std::size_t index, Common::ParamPackage param) {
+    if (index >= mouse_button_params.size()) {
+        return;
+    }
+    mouse_button_params[index] = param;
+    ReloadInput();
+}
+
+void EmulatedDevices::SetKeyboardButton(Input::CallbackStatus callback, std::size_t index) {
+    if (index >= device_status.keyboard_values.size()) {
+        return;
+    }
+    std::lock_guard lock{mutex};
+    bool value_changed = false;
+    const auto new_status = TransformToButton(callback);
+    auto& current_status = device_status.keyboard_values[index];
+    current_status.toggle = new_status.toggle;
+
+    // Update button status with current
+    if (!current_status.toggle) {
+        current_status.locked = false;
+        if (current_status.value != new_status.value) {
+            current_status.value = new_status.value;
+            value_changed = true;
+        }
+    } else {
+        // Toggle button and lock status
+        if (new_status.value && !current_status.locked) {
+            current_status.locked = true;
+            current_status.value = !current_status.value;
+            value_changed = true;
+        }
+
+        // Unlock button ready for next press
+        if (!new_status.value && current_status.locked) {
+            current_status.locked = false;
+        }
+    }
+
+    if (!value_changed) {
+        return;
+    }
+
+    if (is_configuring) {
+        TriggerOnChange(DeviceTriggerType::Keyboard);
+        return;
+    }
+
+    // TODO(german77): Do this properly
+    // switch (index) {
+    // case Settings::NativeKeyboard::A:
+    //    interface_status.keyboard_state.a.Assign(current_status.value);
+    //    break;
+    //    ....
+    //}
+
+    TriggerOnChange(DeviceTriggerType::Keyboard);
+}
+
+void EmulatedDevices::SetKeyboardModifier(Input::CallbackStatus callback, std::size_t index) {
+    if (index >= device_status.keyboard_moddifier_values.size()) {
+        return;
+    }
+    std::lock_guard lock{mutex};
+    bool value_changed = false;
+    const auto new_status = TransformToButton(callback);
+    auto& current_status = device_status.keyboard_moddifier_values[index];
+    current_status.toggle = new_status.toggle;
+
+    // Update button status with current
+    if (!current_status.toggle) {
+        current_status.locked = false;
+        if (current_status.value != new_status.value) {
+            current_status.value = new_status.value;
+            value_changed = true;
+        }
+    } else {
+        // Toggle button and lock status
+        if (new_status.value && !current_status.locked) {
+            current_status.locked = true;
+            current_status.value = !current_status.value;
+            value_changed = true;
+        }
+
+        // Unlock button ready for next press
+        if (!new_status.value && current_status.locked) {
+            current_status.locked = false;
+        }
+    }
+
+    if (!value_changed) {
+        return;
+    }
+
+    if (is_configuring) {
+        TriggerOnChange(DeviceTriggerType::KeyboardModdifier);
+        return;
+    }
+
+    switch (index) {
+    case Settings::NativeKeyboard::LeftControl:
+    case Settings::NativeKeyboard::RightControl:
+        device_status.keyboard_moddifier_state.control.Assign(current_status.value);
+        break;
+    case Settings::NativeKeyboard::LeftShift:
+    case Settings::NativeKeyboard::RightShift:
+        device_status.keyboard_moddifier_state.shift.Assign(current_status.value);
+        break;
+    case Settings::NativeKeyboard::LeftAlt:
+        device_status.keyboard_moddifier_state.left_alt.Assign(current_status.value);
+        break;
+    case Settings::NativeKeyboard::RightAlt:
+        device_status.keyboard_moddifier_state.right_alt.Assign(current_status.value);
+        break;
+    case Settings::NativeKeyboard::CapsLock:
+        device_status.keyboard_moddifier_state.caps_lock.Assign(current_status.value);
+        break;
+    case Settings::NativeKeyboard::ScrollLock:
+        device_status.keyboard_moddifier_state.scroll_lock.Assign(current_status.value);
+        break;
+    case Settings::NativeKeyboard::NumLock:
+        device_status.keyboard_moddifier_state.num_lock.Assign(current_status.value);
+        break;
+    }
+
+    TriggerOnChange(DeviceTriggerType::KeyboardModdifier);
+}
+
+void EmulatedDevices::SetMouseButton(Input::CallbackStatus callback, std::size_t index) {
+    if (index >= device_status.mouse_button_values.size()) {
+        return;
+    }
+    std::lock_guard lock{mutex};
+    bool value_changed = false;
+    const auto new_status = TransformToButton(callback);
+    auto& current_status = device_status.mouse_button_values[index];
+    current_status.toggle = new_status.toggle;
+
+    // Update button status with current
+    if (!current_status.toggle) {
+        current_status.locked = false;
+        if (current_status.value != new_status.value) {
+            current_status.value = new_status.value;
+            value_changed = true;
+        }
+    } else {
+        // Toggle button and lock status
+        if (new_status.value && !current_status.locked) {
+            current_status.locked = true;
+            current_status.value = !current_status.value;
+            value_changed = true;
+        }
+
+        // Unlock button ready for next press
+        if (!new_status.value && current_status.locked) {
+            current_status.locked = false;
+        }
+    }
+
+    if (!value_changed) {
+        return;
+    }
+
+    if (is_configuring) {
+        TriggerOnChange(DeviceTriggerType::Mouse);
+        return;
+    }
+
+    switch (index) {
+    case Settings::NativeMouseButton::Left:
+        device_status.mouse_button_state.left.Assign(current_status.value);
+        break;
+    case Settings::NativeMouseButton::Right:
+        device_status.mouse_button_state.right.Assign(current_status.value);
+        break;
+    case Settings::NativeMouseButton::Middle:
+        device_status.mouse_button_state.middle.Assign(current_status.value);
+        break;
+    case Settings::NativeMouseButton::Forward:
+        device_status.mouse_button_state.forward.Assign(current_status.value);
+        break;
+    case Settings::NativeMouseButton::Back:
+        device_status.mouse_button_state.back.Assign(current_status.value);
+        break;
+    }
+
+    TriggerOnChange(DeviceTriggerType::Mouse);
+}
+
+MouseButtonValues EmulatedDevices::GetMouseButtonsValues() const {
+    return device_status.mouse_button_values;
+}
+
+KeyboardKey EmulatedDevices::GetKeyboard() const {
+    return device_status.keyboard_state;
+}
+
+KeyboardModifier EmulatedDevices::GetKeyboardModifier() const {
+    return device_status.keyboard_moddifier_state;
+}
+
+MouseButton EmulatedDevices::GetMouseButtons() const {
+    return device_status.mouse_button_state;
+}
+
+MousePosition EmulatedDevices::GetMousePosition() const {
+    return device_status.mouse_position_state;
+}
+
+void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {
+    for (const std::pair<int, InterfaceUpdateCallback> poller_pair : callback_list) {
+        const InterfaceUpdateCallback& poller = poller_pair.second;
+        if (poller.on_change) {
+            poller.on_change(type);
+        }
+    }
+}
+
+int EmulatedDevices::SetCallback(InterfaceUpdateCallback update_callback) {
+    std::lock_guard lock{mutex};
+    callback_list.insert_or_assign(last_callback_key, update_callback);
+    return last_callback_key++;
+}
+
+void EmulatedDevices::DeleteCallback(int key) {
+    std::lock_guard lock{mutex};
+    if (!callback_list.contains(key)) {
+        LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
+        return;
+    }
+    callback_list.erase(key);
+}
+} // namespace Core::HID

+ 137 - 0
src/core/hid/emulated_devices.h

@@ -0,0 +1,137 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <mutex>
+#include <unordered_map>
+
+#include "common/input.h"
+#include "common/param_package.h"
+#include "common/settings.h"
+#include "core/hid/hid_types.h"
+#include "core/hid/motion_input.h"
+
+namespace Core::HID {
+
+using KeyboardDevices =
+    std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeKeyboard::NumKeyboardKeys>;
+using KeyboardModifierDevices =
+    std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeKeyboard::NumKeyboardMods>;
+using MouseButtonDevices =
+    std::array<std::unique_ptr<Input::InputDevice>, Settings::NativeMouseButton::NumMouseButtons>;
+
+using MouseButtonParams =
+    std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>;
+
+using KeyboardValues = std::array<Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>;
+using KeyboardModifierValues =
+    std::array<Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardMods>;
+using MouseButtonValues =
+    std::array<Input::ButtonStatus, Settings::NativeMouseButton::NumMouseButtons>;
+
+struct MousePosition {
+    s32 x;
+    s32 y;
+    s32 delta_wheel_x;
+    s32 delta_wheel_y;
+};
+
+struct DeviceStatus {
+    // Data from input_common
+    KeyboardValues keyboard_values{};
+    KeyboardModifierValues keyboard_moddifier_values{};
+    MouseButtonValues mouse_button_values{};
+
+    // Data for Nintendo devices
+    KeyboardKey keyboard_state{};
+    KeyboardModifier keyboard_moddifier_state{};
+    MouseButton mouse_button_state{};
+    MousePosition mouse_position_state{};
+};
+
+enum class DeviceTriggerType {
+    Keyboard,
+    KeyboardModdifier,
+    Mouse,
+};
+
+struct InterfaceUpdateCallback {
+    std::function<void(DeviceTriggerType)> on_change;
+};
+
+class EmulatedDevices {
+public:
+    /**
+     * TODO: Write description
+     *
+     * @param npad_id_type
+     */
+    explicit EmulatedDevices();
+    ~EmulatedDevices();
+
+    YUZU_NON_COPYABLE(EmulatedDevices);
+    YUZU_NON_MOVEABLE(EmulatedDevices);
+
+    void ReloadFromSettings();
+    void ReloadInput();
+    void UnloadInput();
+
+    void EnableConfiguration();
+    void DisableConfiguration();
+    bool IsConfiguring() const;
+    void SaveCurrentConfig();
+    void RestoreConfig();
+
+    std::vector<Common::ParamPackage> GetMappedDevices() const;
+
+    Common::ParamPackage GetMouseButtonParam(std::size_t index) const;
+
+    void SetButtonParam(std::size_t index, Common::ParamPackage param);
+
+    KeyboardValues GetKeyboardValues() const;
+    KeyboardModifierValues GetKeyboardModdifierValues() const;
+    MouseButtonValues GetMouseButtonsValues() const;
+
+    KeyboardKey GetKeyboard() const;
+    KeyboardModifier GetKeyboardModifier() const;
+    MouseButton GetMouseButtons() const;
+    MousePosition GetMousePosition() const;
+
+    int SetCallback(InterfaceUpdateCallback update_callback);
+    void DeleteCallback(int key);
+
+private:
+    /**
+     * Sets the status of a button. Applies toggle properties to the output.
+     *
+     * @param A CallbackStatus and a button index number
+     */
+    void SetKeyboardButton(Input::CallbackStatus callback, std::size_t index);
+    void SetKeyboardModifier(Input::CallbackStatus callback, std::size_t index);
+    void SetMouseButton(Input::CallbackStatus callback, std::size_t index);
+
+    /**
+     * Triggers a callback that something has changed
+     *
+     * @param Input type of the trigger
+     */
+    void TriggerOnChange(DeviceTriggerType type);
+
+    bool is_configuring{false};
+
+    MouseButtonParams mouse_button_params;
+
+    KeyboardDevices keyboard_devices;
+    KeyboardModifierDevices keyboard_modifier_devices;
+    MouseButtonDevices mouse_button_devices;
+
+    mutable std::mutex mutex;
+    std::unordered_map<int, InterfaceUpdateCallback> callback_list;
+    int last_callback_key = 0;
+    DeviceStatus device_status;
+};
+
+} // namespace Core::HID

+ 144 - 0
src/core/hid/hid_core.cpp

@@ -0,0 +1,144 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "core/hid/hid_core.h"
+
+namespace Core::HID {
+
+HIDCore::HIDCore()
+    : player_1{std::make_unique<EmulatedController>(NpadIdType::Player1)},
+      player_2{std::make_unique<EmulatedController>(NpadIdType::Player2)},
+      player_3{std::make_unique<EmulatedController>(NpadIdType::Player3)},
+      player_4{std::make_unique<EmulatedController>(NpadIdType::Player4)},
+      player_5{std::make_unique<EmulatedController>(NpadIdType::Player5)},
+      player_6{std::make_unique<EmulatedController>(NpadIdType::Player6)},
+      player_7{std::make_unique<EmulatedController>(NpadIdType::Player7)},
+      player_8{std::make_unique<EmulatedController>(NpadIdType::Player8)},
+      other{std::make_unique<EmulatedController>(NpadIdType::Other)},
+      handheld{std::make_unique<EmulatedController>(NpadIdType::Handheld)},
+      console{std::make_unique<EmulatedConsole>()}, devices{std::make_unique<EmulatedDevices>()} {}
+
+HIDCore::~HIDCore() = default;
+
+EmulatedController* HIDCore::GetEmulatedController(NpadIdType npad_id_type) {
+    switch (npad_id_type) {
+    case NpadIdType::Player1:
+        return player_1.get();
+    case NpadIdType::Player2:
+        return player_2.get();
+    case NpadIdType::Player3:
+        return player_3.get();
+    case NpadIdType::Player4:
+        return player_4.get();
+    case NpadIdType::Player5:
+        return player_5.get();
+    case NpadIdType::Player6:
+        return player_6.get();
+    case NpadIdType::Player7:
+        return player_7.get();
+    case NpadIdType::Player8:
+        return player_8.get();
+    case NpadIdType::Other:
+        return other.get();
+    case NpadIdType::Handheld:
+        return handheld.get();
+    case NpadIdType::Invalid:
+    default:
+        UNREACHABLE_MSG("Invalid NpadIdType={}", npad_id_type);
+        return nullptr;
+    }
+}
+
+const EmulatedController* HIDCore::GetEmulatedController(NpadIdType npad_id_type) const {
+    switch (npad_id_type) {
+    case NpadIdType::Player1:
+        return player_1.get();
+    case NpadIdType::Player2:
+        return player_2.get();
+    case NpadIdType::Player3:
+        return player_3.get();
+    case NpadIdType::Player4:
+        return player_4.get();
+    case NpadIdType::Player5:
+        return player_5.get();
+    case NpadIdType::Player6:
+        return player_6.get();
+    case NpadIdType::Player7:
+        return player_7.get();
+    case NpadIdType::Player8:
+        return player_8.get();
+    case NpadIdType::Other:
+        return other.get();
+    case NpadIdType::Handheld:
+        return handheld.get();
+    case NpadIdType::Invalid:
+    default:
+        UNREACHABLE_MSG("Invalid NpadIdType={}", npad_id_type);
+        return nullptr;
+    }
+}
+EmulatedConsole* HIDCore::GetEmulatedConsole() {
+    return console.get();
+}
+
+const EmulatedConsole* HIDCore::GetEmulatedConsole() const {
+    return console.get();
+}
+
+EmulatedDevices* HIDCore::GetEmulatedDevices() {
+    return devices.get();
+}
+
+const EmulatedDevices* HIDCore::GetEmulatedDevices() const {
+    return devices.get();
+}
+
+EmulatedController* HIDCore::GetEmulatedControllerByIndex(std::size_t index) {
+    return GetEmulatedController(IndexToNpadIdType(index));
+}
+
+const EmulatedController* HIDCore::GetEmulatedControllerByIndex(std::size_t index) const {
+    return GetEmulatedController(IndexToNpadIdType(index));
+}
+
+void HIDCore::SetSupportedStyleTag(NpadStyleTag style_tag) {
+    supported_style_tag.raw = style_tag.raw;
+}
+
+NpadStyleTag HIDCore::GetSupportedStyleTag() const {
+    return supported_style_tag;
+}
+
+void HIDCore::ReloadInputDevices() {
+    player_1->ReloadFromSettings();
+    player_2->ReloadFromSettings();
+    player_3->ReloadFromSettings();
+    player_4->ReloadFromSettings();
+    player_5->ReloadFromSettings();
+    player_6->ReloadFromSettings();
+    player_7->ReloadFromSettings();
+    player_8->ReloadFromSettings();
+    other->ReloadFromSettings();
+    handheld->ReloadFromSettings();
+    console->ReloadFromSettings();
+    devices->ReloadFromSettings();
+}
+
+void HIDCore::UnloadInputDevices() {
+    player_1->UnloadInput();
+    player_2->UnloadInput();
+    player_3->UnloadInput();
+    player_4->UnloadInput();
+    player_5->UnloadInput();
+    player_6->UnloadInput();
+    player_7->UnloadInput();
+    player_8->UnloadInput();
+    other->UnloadInput();
+    handheld->UnloadInput();
+    console->UnloadInput();
+    devices->UnloadInput();
+}
+
+} // namespace Core::HID

+ 60 - 0
src/core/hid/hid_core.h

@@ -0,0 +1,60 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+
+#include "core/hid/emulated_console.h"
+#include "core/hid/emulated_controller.h"
+#include "core/hid/emulated_devices.h"
+
+namespace Core::HID {
+
+class HIDCore {
+public:
+    explicit HIDCore();
+    ~HIDCore();
+
+    YUZU_NON_COPYABLE(HIDCore);
+    YUZU_NON_MOVEABLE(HIDCore);
+
+    EmulatedController* GetEmulatedController(NpadIdType npad_id_type);
+    const EmulatedController* GetEmulatedController(NpadIdType npad_id_type) const;
+
+    EmulatedController* GetEmulatedControllerByIndex(std::size_t index);
+    const EmulatedController* GetEmulatedControllerByIndex(std::size_t index) const;
+
+    EmulatedConsole* GetEmulatedConsole();
+    const EmulatedConsole* GetEmulatedConsole() const;
+
+    EmulatedDevices* GetEmulatedDevices();
+    const EmulatedDevices* GetEmulatedDevices() const;
+
+    void SetSupportedStyleTag(NpadStyleTag style_tag);
+    NpadStyleTag GetSupportedStyleTag() const;
+
+    // Reloads all input devices from settings
+    void ReloadInputDevices();
+
+    // Removes all callbacks from input common
+    void UnloadInputDevices();
+
+private:
+    std::unique_ptr<EmulatedController> player_1;
+    std::unique_ptr<EmulatedController> player_2;
+    std::unique_ptr<EmulatedController> player_3;
+    std::unique_ptr<EmulatedController> player_4;
+    std::unique_ptr<EmulatedController> player_5;
+    std::unique_ptr<EmulatedController> player_6;
+    std::unique_ptr<EmulatedController> player_7;
+    std::unique_ptr<EmulatedController> player_8;
+    std::unique_ptr<EmulatedController> other;
+    std::unique_ptr<EmulatedController> handheld;
+    std::unique_ptr<EmulatedConsole> console;
+    std::unique_ptr<EmulatedDevices> devices;
+    NpadStyleTag supported_style_tag;
+};
+
+} // namespace Core::HID