early-access version 2177

This commit is contained in:
pineappleEA
2021-11-02 05:02:57 +01:00
parent 2b3decfff3
commit 0d005353e8
115 changed files with 13315 additions and 3835 deletions

View File

@@ -132,11 +132,23 @@ add_library(core STATIC
frontend/emu_window.h
frontend/framebuffer_layout.cpp
frontend/framebuffer_layout.h
frontend/input_interpreter.cpp
frontend/input_interpreter.h
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
hid/input_interpreter.cpp
hid/input_interpreter.h
hid/motion_input.cpp
hid/motion_input.h
hle/api_version.h
hle/ipc.h
hle/ipc_helpers.h
@@ -402,6 +414,7 @@ add_library(core STATIC
hle/service/hid/hid.h
hle/service/hid/irs.cpp
hle/service/hid/irs.h
hle/service/hid/ring_lifo.h
hle/service/hid/xcd.cpp
hle/service/hid/xcd.h
hle/service/hid/errors.h

View File

@@ -29,6 +29,7 @@
#include "core/file_sys/vfs_concat.h"
#include "core/file_sys/vfs_real.h"
#include "core/hardware_interrupt_manager.h"
#include "core/hid/hid_core.h"
#include "core/hle/kernel/k_client_port.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_scheduler.h"
@@ -130,7 +131,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
struct System::Impl {
explicit Impl(System& system)
: kernel{system}, fs_controller{system}, memory{system},
: kernel{system}, fs_controller{system}, memory{system}, hid_core{},
cpu_manager{system}, reporter{system}, applet_manager{system}, time_manager{system} {}
SystemResultStatus Run() {
@@ -395,6 +396,7 @@ struct System::Impl {
std::unique_ptr<Hardware::InterruptManager> interrupt_manager;
std::unique_ptr<Core::DeviceMemory> device_memory;
Core::Memory::Memory memory;
Core::HID::HIDCore hid_core;
CpuManager cpu_manager;
std::atomic_bool is_powered_on{};
bool exit_lock = false;
@@ -619,6 +621,14 @@ const Kernel::KernelCore& System::Kernel() const {
return impl->kernel;
}
HID::HIDCore& System::HIDCore() {
return impl->hid_core;
}
const HID::HIDCore& System::HIDCore() const {
return impl->hid_core;
}
Timing::CoreTiming& System::CoreTiming() {
return impl->core_timing;
}
@@ -825,8 +835,6 @@ void System::ApplySettings() {
if (IsPoweredOn()) {
Renderer().RefreshBaseSettings();
}
Service::HID::ReloadInputDevices();
}
} // namespace Core

View File

@@ -89,6 +89,10 @@ namespace Core::Hardware {
class InterruptManager;
}
namespace Core::HID {
class HIDCore;
}
namespace Core {
class ARM_Interface;
@@ -285,6 +289,12 @@ public:
/// Provides a constant reference to the kernel instance.
[[nodiscard]] const Kernel::KernelCore& Kernel() const;
/// Gets a mutable reference to the HID interface.
[[nodiscard]] HID::HIDCore& HIDCore();
/// Gets an immutable reference to the HID interface.
[[nodiscard]] const HID::HIDCore& HIDCore() const;
/// Provides a reference to the internal PerfStats instance.
[[nodiscard]] Core::PerfStats& GetPerfStats();

View File

@@ -5,16 +5,15 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/frontend/applets/controller.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"
namespace Core::Frontend {
ControllerApplet::~ControllerApplet() = default;
DefaultControllerApplet::DefaultControllerApplet(Service::SM::ServiceManager& service_manager_)
: service_manager{service_manager_} {}
DefaultControllerApplet::DefaultControllerApplet(HID::HIDCore& hid_core_) : hid_core{hid_core_} {}
DefaultControllerApplet::~DefaultControllerApplet() = default;
@@ -22,24 +21,20 @@ void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callb
const ControllerParameters& parameters) const {
LOG_INFO(Service_HID, "called, deducing the best configuration based on the given parameters!");
auto& npad =
service_manager.GetService<Service::HID::Hid>("hid")
->GetAppletResource()
->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
auto& players = Settings::values.players.GetValue();
const std::size_t min_supported_players =
parameters.enable_single_mode ? 1 : parameters.min_players;
// Disconnect Handheld first.
npad.DisconnectNpadAtIndex(8);
auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
handheld->Disconnect();
// Deduce the best configuration based on the input parameters.
for (std::size_t index = 0; index < players.size() - 2; ++index) {
for (std::size_t index = 0; index < hid_core.available_controllers - 2; ++index) {
auto* controller = hid_core.GetEmulatedControllerByIndex(index);
// First, disconnect all controllers regardless of the value of keep_controllers_connected.
// This makes it easy to connect the desired controllers.
npad.DisconnectNpadAtIndex(index);
controller->Disconnect();
// Only connect the minimum number of required players.
if (index >= min_supported_players) {
@@ -49,27 +44,27 @@ void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callb
// Connect controllers based on the following priority list from highest to lowest priority:
// Pro Controller -> Dual Joycons -> Left Joycon/Right Joycon -> Handheld
if (parameters.allow_pro_controller) {
npad.AddNewControllerAt(
npad.MapSettingsTypeToNPad(Settings::ControllerType::ProController), index);
controller->SetNpadType(Core::HID::NpadType::ProController);
controller->Connect();
} else if (parameters.allow_dual_joycons) {
npad.AddNewControllerAt(
npad.MapSettingsTypeToNPad(Settings::ControllerType::DualJoyconDetached), index);
controller->SetNpadType(Core::HID::NpadType::JoyconDual);
controller->Connect();
} else if (parameters.allow_left_joycon && parameters.allow_right_joycon) {
// Assign left joycons to even player indices and right joycons to odd player indices.
// We do this since Captain Toad Treasure Tracker expects a left joycon for Player 1 and
// a right Joycon for Player 2 in 2 Player Assist mode.
if (index % 2 == 0) {
npad.AddNewControllerAt(
npad.MapSettingsTypeToNPad(Settings::ControllerType::LeftJoycon), index);
controller->SetNpadType(Core::HID::NpadType::JoyconLeft);
controller->Connect();
} else {
npad.AddNewControllerAt(
npad.MapSettingsTypeToNPad(Settings::ControllerType::RightJoycon), index);
controller->SetNpadType(Core::HID::NpadType::JoyconRight);
controller->Connect();
}
} else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld &&
!Settings::values.use_docked_mode.GetValue()) {
// We should *never* reach here under any normal circumstances.
npad.AddNewControllerAt(npad.MapSettingsTypeToNPad(Settings::ControllerType::Handheld),
index);
controller->SetNpadType(Core::HID::NpadType::Handheld);
controller->Connect();
} else {
UNREACHABLE_MSG("Unable to add a new controller based on the given parameters!");
}

View File

@@ -8,8 +8,8 @@
#include "common/common_types.h"
namespace Service::SM {
class ServiceManager;
namespace Core::HID {
class HIDCore;
}
namespace Core::Frontend {
@@ -44,14 +44,14 @@ public:
class DefaultControllerApplet final : public ControllerApplet {
public:
explicit DefaultControllerApplet(Service::SM::ServiceManager& service_manager_);
explicit DefaultControllerApplet(HID::HIDCore& hid_core_);
~DefaultControllerApplet() override;
void ReconfigureControllers(std::function<void()> callback,
const ControllerParameters& parameters) const override;
private:
Service::SM::ServiceManager& service_manager;
HID::HIDCore& hid_core;
};
} // namespace Core::Frontend

View File

@@ -4,66 +4,31 @@
#include <cmath>
#include <mutex>
#include "common/settings.h"
#include "core/frontend/emu_window.h"
#include "core/frontend/input.h"
namespace Core::Frontend {
GraphicsContext::~GraphicsContext() = default;
class EmuWindow::TouchState : public Input::Factory<Input::TouchDevice>,
public std::enable_shared_from_this<TouchState> {
public:
std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage&) override {
return std::make_unique<Device>(shared_from_this());
}
std::mutex mutex;
Input::TouchStatus status;
private:
class Device : public Input::TouchDevice {
public:
explicit Device(std::weak_ptr<TouchState>&& touch_state_) : touch_state(touch_state_) {}
Input::TouchStatus GetStatus() const override {
if (auto state = touch_state.lock()) {
std::lock_guard guard{state->mutex};
return state->status;
}
return {};
}
private:
std::weak_ptr<TouchState> touch_state;
};
};
EmuWindow::EmuWindow() {
// TODO: Find a better place to set this.
config.min_client_area_size =
std::make_pair(Layout::MinimumSize::Width, Layout::MinimumSize::Height);
active_config = config;
touch_state = std::make_shared<TouchState>();
Input::RegisterFactory<Input::TouchDevice>("emu_window", touch_state);
}
EmuWindow::~EmuWindow() {
Input::UnregisterFactory<Input::TouchDevice>("emu_window");
}
EmuWindow::~EmuWindow() {}
/**
* Check if the given x/y coordinates are within the touchpad specified by the framebuffer layout
* @param layout FramebufferLayout object describing the framebuffer size and screen positions
* @param framebuffer_x Framebuffer x-coordinate to check
* @param framebuffer_y Framebuffer y-coordinate to check
* @return True if the coordinates are within the touchpad, otherwise false
*/
static bool IsWithinTouchscreen(const Layout::FramebufferLayout& layout, u32 framebuffer_x,
u32 framebuffer_y) {
return (framebuffer_y >= layout.screen.top && framebuffer_y < layout.screen.bottom &&
framebuffer_x >= layout.screen.left && framebuffer_x < layout.screen.right);
std::pair<f32, f32> EmuWindow::MapToTouchScreen(u32 framebuffer_x, u32 framebuffer_y) const {
std::tie(framebuffer_x, framebuffer_y) = ClipToTouchScreen(framebuffer_x, framebuffer_y);
const float x =
static_cast<float>(framebuffer_x - framebuffer_layout.screen.left) /
static_cast<float>(framebuffer_layout.screen.right - framebuffer_layout.screen.left);
const float y =
static_cast<float>(framebuffer_y - framebuffer_layout.screen.top) /
static_cast<float>(framebuffer_layout.screen.bottom - framebuffer_layout.screen.top);
return std::make_pair(x, y);
}
std::pair<u32, u32> EmuWindow::ClipToTouchScreen(u32 new_x, u32 new_y) const {
@@ -76,49 +41,6 @@ std::pair<u32, u32> EmuWindow::ClipToTouchScreen(u32 new_x, u32 new_y) const {
return std::make_pair(new_x, new_y);
}
void EmuWindow::TouchPressed(u32 framebuffer_x, u32 framebuffer_y, size_t id) {
if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) {
return;
}
if (id >= touch_state->status.size()) {
return;
}
std::lock_guard guard{touch_state->mutex};
const float x =
static_cast<float>(framebuffer_x - framebuffer_layout.screen.left) /
static_cast<float>(framebuffer_layout.screen.right - framebuffer_layout.screen.left);
const float y =
static_cast<float>(framebuffer_y - framebuffer_layout.screen.top) /
static_cast<float>(framebuffer_layout.screen.bottom - framebuffer_layout.screen.top);
touch_state->status[id] = std::make_tuple(x, y, true);
}
void EmuWindow::TouchReleased(size_t id) {
if (id >= touch_state->status.size()) {
return;
}
std::lock_guard guard{touch_state->mutex};
touch_state->status[id] = std::make_tuple(0.0f, 0.0f, false);
}
void EmuWindow::TouchMoved(u32 framebuffer_x, u32 framebuffer_y, size_t id) {
if (id >= touch_state->status.size()) {
return;
}
if (!std::get<2>(touch_state->status[id])) {
return;
}
if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) {
std::tie(framebuffer_x, framebuffer_y) = ClipToTouchScreen(framebuffer_x, framebuffer_y);
}
TouchPressed(framebuffer_x, framebuffer_y, id);
}
void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height) {
NotifyFramebufferLayoutChanged(Layout::DefaultFrameLayout(width, height));
}

View File

@@ -113,28 +113,6 @@ public:
/// Returns if window is shown (not minimized)
virtual bool IsShown() const = 0;
/**
* Signal that a touch pressed event has occurred (e.g. mouse click pressed)
* @param framebuffer_x Framebuffer x-coordinate that was pressed
* @param framebuffer_y Framebuffer y-coordinate that was pressed
* @param id Touch event ID
*/
void TouchPressed(u32 framebuffer_x, u32 framebuffer_y, size_t id);
/**
* Signal that a touch released event has occurred (e.g. mouse click released)
* @param id Touch event ID
*/
void TouchReleased(size_t id);
/**
* Signal that a touch movement event has occurred (e.g. mouse was moved over the emu window)
* @param framebuffer_x Framebuffer x-coordinate
* @param framebuffer_y Framebuffer y-coordinate
* @param id Touch event ID
*/
void TouchMoved(u32 framebuffer_x, u32 framebuffer_y, size_t id);
/**
* Returns currently active configuration.
* @note Accesses to the returned object need not be consistent because it may be modified in
@@ -213,6 +191,11 @@ protected:
client_area_height = size.second;
}
/**
* Converts a screen postion into the equivalent touchscreen position.
*/
std::pair<f32, f32> MapToTouchScreen(u32 framebuffer_x, u32 framebuffer_y) const;
WindowSystemInfo window_info;
private:
@@ -238,9 +221,6 @@ private:
WindowConfig config; ///< Internal configuration (changes pending for being applied in
/// ProcessConfigurationChanges)
WindowConfig active_config; ///< Internal active configuration
class TouchState;
std::shared_ptr<TouchState> touch_state;
};
} // namespace Core::Frontend

219
src/core/hid/emulated_console.cpp Executable file
View File

@@ -0,0 +1,219 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#include "core/hid/emulated_console.h"
#include "core/hid/input_converter.h"
namespace Core::HID {
EmulatedConsole::EmulatedConsole() = default;
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::SetTouchParams() {
// TODO(german77): Support any number of fingers
std::size_t index = 0;
// Hardcode mouse, touchscreen and cemuhook parameters
touch_params[index++] = Common::ParamPackage{"engine:mouse,axis_x:10,axis_y:11,button:0"};
touch_params[index++] = Common::ParamPackage{"engine:touch,axis_x:0,axis_y:1,button:0"};
touch_params[index++] = Common::ParamPackage{"engine:touch,axis_x:2,axis_y:3,button:1"};
touch_params[index++] = Common::ParamPackage{"engine:cemuhookudp,axis_x:0,axis_y:1,button:0"};
touch_params[index++] = Common::ParamPackage{"engine:cemuhookudp,axis_x:2,axis_y:3,button:1"};
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));
touch_params[index] = touch_button_params;
index++;
if (index >= touch_params.size()) {
return;
}
}
}
void EmulatedConsole::ReloadInput() {
SetTouchParams();
motion_devices = Common::Input::CreateDevice<Common::Input::InputDevice>(motion_params);
if (motion_devices) {
Common::Input::InputCallback motion_callback{
[this](Common::Input::CallbackStatus callback) { SetMotion(callback); }};
motion_devices->SetCallback(motion_callback);
}
std::size_t index = 0;
for (auto& touch_device : touch_devices) {
touch_device = Common::Input::CreateDevice<Common::Input::InputDevice>(touch_params[index]);
if (!touch_device) {
continue;
}
Common::Input::InputCallback touch_callback{
[this, index](Common::Input::CallbackStatus callback) { SetTouch(callback, index); }};
touch_device->SetCallback(touch_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(Common::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(Common::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;
}
// TODO(german77): Remap touch id in sequential order
console.touch_state[index] = {
.position = {console.touch_values[index].x.value, console.touch_values[index].y.value},
.id = static_cast<u32>(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 auto& 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};
const auto& iterator = callback_list.find(key);
if (iterator == callback_list.end()) {
LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
return;
}
callback_list.erase(iterator);
}
} // namespace Core::HID

189
src/core/hid/emulated_console.h Executable file
View File

@@ -0,0 +1,189 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <functional>
#include <memory>
#include <mutex>
#include <unordered_map>
#include "common/common_types.h"
#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 {
Common::Input::MotionStatus raw_status{};
MotionInput emulated{};
};
using ConsoleMotionDevices = std::unique_ptr<Common::Input::InputDevice>;
using TouchDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, 16>;
using ConsoleMotionParams = Common::ParamPackage;
using TouchParams = std::array<Common::ParamPackage, 16>;
using ConsoleMotionValues = ConsoleMotionInfo;
using TouchValues = std::array<Common::Input::TouchStatus, 16>;
struct TouchFinger {
u64 last_touch{};
Common::Point<float> position{};
u32 id{};
TouchAttribute attribute{};
bool pressed{};
};
// Contains all motion related data that is used on the services
struct ConsoleMotion {
Common::Vec3f accel{};
Common::Vec3f gyro{};
Common::Vec3f rotation{};
std::array<Common::Vec3f, 3> orientation{};
Common::Quaternion<f32> quaternion{};
bool is_at_rest{};
};
using TouchFingerState = std::array<TouchFinger, 16>;
struct ConsoleStatus {
// Data from input_common
ConsoleMotionValues motion_values{};
TouchValues touch_values{};
// Data for HID services
ConsoleMotion motion_state{};
TouchFingerState touch_state{};
};
enum class ConsoleTriggerType {
Motion,
Touch,
All,
};
struct ConsoleUpdateCallback {
std::function<void(ConsoleTriggerType)> on_change;
};
class EmulatedConsole {
public:
/**
* Contains all input data related to the console like motion and touch input
*/
EmulatedConsole();
~EmulatedConsole();
YUZU_NON_COPYABLE(EmulatedConsole);
YUZU_NON_MOVEABLE(EmulatedConsole);
/// Removes all callbacks created from input devices
void UnloadInput();
/// Sets the emulated console into configuring mode. Locking all HID service events from being
/// moddified
void EnableConfiguration();
/// Returns the emulated console to the normal behaivour
void DisableConfiguration();
/// Returns true if the emulated console is on configuring mode
bool IsConfiguring() const;
/// Reload all input devices
void ReloadInput();
/// Overrides current mapped devices with the stored configuration and reloads all input devices
void ReloadFromSettings();
/// Saves the current mapped configuration
void SaveCurrentConfig();
/// Reverts any mapped changes made that weren't saved
void RestoreConfig();
// Returns the current mapped motion device
Common::ParamPackage GetMotionParam() const;
/**
* Updates the current mapped motion device
* @param ParamPackage with controller data to be mapped
*/
void SetMotionParam(Common::ParamPackage param);
/// Returns the latest status of motion input from the console with parameters
ConsoleMotionValues GetMotionValues() const;
/// Returns the latest status of touch input from the console with parameters
TouchValues GetTouchValues() const;
/// Returns the latest status of motion input from the console
ConsoleMotion GetMotion() const;
/// Returns the latest status of touch input from the console
TouchFingerState GetTouch() const;
/**
* Adds a callback to the list of events
* @param ConsoleUpdateCallback that will be triggered
* @return an unique key corresponding to the callback index in the list
*/
int SetCallback(ConsoleUpdateCallback update_callback);
/**
* Removes a callback from the list stopping any future events to this object
* @param Key corresponding to the callback index in the list
*/
void DeleteCallback(int key);
private:
/// Creates and stores the touch params
void SetTouchParams();
/**
* Updates the motion status of the console
* @param A CallbackStatus containing gyro and accelerometer data
*/
void SetMotion(Common::Input::CallbackStatus callback);
/**
* Updates the touch status of the console
* @param callback: A CallbackStatus containing the touch position
* @param index: Finger ID to be updated
*/
void SetTouch(Common::Input::CallbackStatus callback, std::size_t index);
/**
* Triggers a callback that something has changed on the console status
* @param Input type of the event to 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;
// Stores the current status of all console input
ConsoleStatus console;
};
} // namespace Core::HID

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,392 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <functional>
#include <memory>
#include <mutex>
#include <unordered_map>
#include "common/common_types.h"
#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 {
const std::size_t max_emulated_controllers = 2;
struct ControllerMotionInfo {
Common::Input::MotionStatus raw_status{};
MotionInput emulated{};
};
using ButtonDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeButton::NumButtons>;
using StickDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeAnalog::NumAnalogs>;
using ControllerMotionDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeMotion::NumMotions>;
using TriggerDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeTrigger::NumTriggers>;
using BatteryDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
using OutputDevices =
std::array<std::unique_ptr<Common::Input::OutputDevice>, max_emulated_controllers>;
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, max_emulated_controllers>;
using OutputParams = std::array<Common::ParamPackage, max_emulated_controllers>;
using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>;
using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>;
using TriggerValues =
std::array<Common::Input::TriggerStatus, Settings::NativeTrigger::NumTriggers>;
using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::NativeMotion::NumMotions>;
using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>;
using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>;
using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>;
struct AnalogSticks {
AnalogStickState left{};
AnalogStickState right{};
};
struct ControllerColors {
NpadControllerColor fullkey{};
NpadControllerColor left{};
NpadControllerColor right{};
};
struct BatteryLevelState {
NpadPowerInfo dual{};
NpadPowerInfo left{};
NpadPowerInfo right{};
};
struct ControllerMotion {
Common::Vec3f accel{};
Common::Vec3f gyro{};
Common::Vec3f rotation{};
std::array<Common::Vec3f, 3> orientation{};
bool is_at_rest{};
};
enum DeviceIndex : u8 {
LeftIndex,
RightIndex,
DualIndex,
AllDevices,
};
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 HID serices
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;
bool is_npad_service;
};
class EmulatedController {
public:
/**
* Contains all input data related to this controller. Like buttons, joysticks, motion.
* @param Npad id type for this specific controller
*/
explicit EmulatedController(NpadIdType npad_id_type_);
~EmulatedController();
YUZU_NON_COPYABLE(EmulatedController);
YUZU_NON_MOVEABLE(EmulatedController);
/// Converts the controller type from settings to npad type
static NpadType MapSettingsTypeToNPad(Settings::ControllerType type);
/// Converts npad type to the equivalent of controller type from settings
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
* @param If true tmp_npad_type will be returned
* @return NpadType set on the controller
*/
NpadType GetNpadType(bool get_temporary_value = false) const;
/// Sets the connected status to true
void Connect();
/// Sets the connected status to false
void Disconnect();
/**
* Is the emulated connected
* @param If true tmp_is_connected will be returned
* @return true if the controller has the connected status
*/
bool IsConnected(bool get_temporary_value = false) const;
/// Returns true if vibration is enabled
bool IsVibrationEnabled() const;
/// Removes all callbacks created from input devices
void UnloadInput();
/// Sets the emulated console into configuring mode. Locking all HID service events from being
/// moddified
void EnableConfiguration();
/// Returns the emulated console to the normal behaivour
void DisableConfiguration();
/// Returns true if the emulated device is on configuring mode
bool IsConfiguring() const;
/// Reload all input devices
void ReloadInput();
/// Overrides current mapped devices with the stored configuration and reloads all input devices
void ReloadFromSettings();
/// Saves the current mapped configuration
void SaveCurrentConfig();
/// Reverts any mapped changes made that weren't saved
void RestoreConfig();
/// Returns a vector of mapped devices from the mapped button and stick parameters
std::vector<Common::ParamPackage> GetMappedDevices(DeviceIndex device_index) const;
// Returns the current mapped button device
Common::ParamPackage GetButtonParam(std::size_t index) const;
// Returns the current mapped stick device
Common::ParamPackage GetStickParam(std::size_t index) const;
// Returns the current mapped motion device
Common::ParamPackage GetMotionParam(std::size_t index) const;
/**
* Updates the current mapped button device
* @param ParamPackage with controller data to be mapped
*/
void SetButtonParam(std::size_t index, Common::ParamPackage param);
/**
* Updates the current mapped stick device
* @param ParamPackage with controller data to be mapped
*/
void SetStickParam(std::size_t index, Common::ParamPackage param);
/**
* Updates the current mapped motion device
* @param ParamPackage with controller data to be mapped
*/
void SetMotionParam(std::size_t index, Common::ParamPackage param);
/// Returns the latest button status from the controller with parameters
ButtonValues GetButtonsValues() const;
/// Returns the latest analog stick status from the controller with parameters
SticksValues GetSticksValues() const;
/// Returns the latest trigger status from the controller with parameters
TriggerValues GetTriggersValues() const;
/// Returns the latest motion status from the controller with parameters
ControllerMotionValues GetMotionValues() const;
/// Returns the latest color status from the controller with parameters
ColorValues GetColorsValues() const;
/// Returns the latest battery status from the controller with parameters
BatteryValues GetBatteryValues() const;
/// Returns the latest status of button input for the npad service
NpadButtonState GetNpadButtons() const;
/// Returns the latest status of button input for the debug pad service
DebugPadButton GetDebugPadButtons() const;
/// Returns the latest status of stick input from the mouse
AnalogSticks GetSticks() const;
/// Returns the latest status of trigger input from the mouse
NpadGcTriggerState GetTriggers() const;
/// Returns the latest status of motion input from the mouse
MotionState GetMotions() const;
/// Returns the latest color value from the controller
ControllerColors GetColors() const;
/// Returns the latest battery status from the controller
BatteryLevelState GetBattery() const;
/*
* Sends a specific vibration to the output device
* @return returns true if vibration had no errors
*/
bool SetVibration(std::size_t device_index, VibrationValue vibration);
/*
* Sends a small vibration to the output device
* @return returns true if SetVibration was successfull
*/
bool TestVibration(std::size_t device_index);
/// Returns the led pattern corresponding to this emulated controller
LedPattern GetLedPattern() const;
/// Asks the output device to change the player led pattern
void SetLedPattern();
/**
* Adds a callback to the list of events
* @param ConsoleUpdateCallback that will be triggered
* @return an unique key corresponding to the callback index in the list
*/
int SetCallback(ControllerUpdateCallback update_callback);
/**
* Removes a callback from the list stopping any future events to this object
* @param Key corresponding to the callback index in the list
*/
void DeleteCallback(int key);
private:
/// creates input devices from params
void LoadDevices();
/// Set the params for TAS devices
void LoadTASParams();
/**
* Updates the button status of the controller
* @param callback: A CallbackStatus containing the button status
* @param index: Button ID of the to be updated
*/
void SetButton(Common::Input::CallbackStatus callback, std::size_t index);
/**
* Updates the analog stick status of the controller
* @param callback: A CallbackStatus containing the analog stick status
* @param index: stick ID of the to be updated
*/
void SetStick(Common::Input::CallbackStatus callback, std::size_t index);
/**
* Updates the trigger status of the controller
* @param callback: A CallbackStatus containing the trigger status
* @param index: trigger ID of the to be updated
*/
void SetTrigger(Common::Input::CallbackStatus callback, std::size_t index);
/**
* Updates the motion status of the controller
* @param callback: A CallbackStatus containing gyro and accelerometer data
* @param index: motion ID of the to be updated
*/
void SetMotion(Common::Input::CallbackStatus callback, std::size_t index);
/**
* Updates the battery status of the controller
* @param callback: A CallbackStatus containing the battery status
* @param index: Button ID of the to be updated
*/
void SetBattery(Common::Input::CallbackStatus callback, std::size_t index);
/**
* Triggers a callback that something has changed on the controller status
* @param type: Input type of the event to trigger
* @param is_service_update: indicates if this event should be sended to only services
*/
void TriggerOnChange(ControllerTriggerType type, bool is_service_update);
NpadIdType npad_id_type;
NpadType npad_type{NpadType::None};
bool is_connected{false};
bool is_configuring{false};
f32 motion_sensitivity{0.01f};
bool force_update_motion{false};
// Temporary values to avoid doing changes while the controller is on configuration mode
NpadType tmp_npad_type{NpadType::None};
bool tmp_is_connected{false};
ButtonParams button_params;
StickParams stick_params;
ControllerMotionParams motion_params;
TriggerParams trigger_params;
BatteryParams battery_params;
OutputParams output_params;
ButtonDevices button_devices;
StickDevices stick_devices;
ControllerMotionDevices motion_devices;
TriggerDevices trigger_devices;
BatteryDevices battery_devices;
OutputDevices output_devices;
// TAS related variables
ButtonParams tas_button_params;
StickParams tas_stick_params;
ButtonDevices tas_button_devices;
StickDevices tas_stick_devices;
mutable std::mutex mutex;
std::unordered_map<int, ControllerUpdateCallback> callback_list;
int last_callback_key = 0;
// Stores the current status of all controller input
ControllerStatus controller;
};
} // namespace Core::HID

372
src/core/hid/emulated_devices.cpp Executable file
View File

@@ -0,0 +1,372 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#include <algorithm>
#include <fmt/format.h>
#include "core/hid/emulated_devices.h"
#include "core/hid/input_converter.h"
namespace Core::HID {
EmulatedDevices::EmulatedDevices() = default;
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(),
Common::Input::CreateDevice<Common::Input::InputDevice>);
std::transform(Settings::values.keyboard_keys.begin(), Settings::values.keyboard_keys.end(),
keyboard_devices.begin(),
Common::Input::CreateDeviceFromString<Common::Input::InputDevice>);
std::transform(Settings::values.keyboard_mods.begin(), Settings::values.keyboard_mods.end(),
keyboard_modifier_devices.begin(),
Common::Input::CreateDeviceFromString<Common::Input::InputDevice>);
for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) {
if (!mouse_button_devices[index]) {
continue;
}
Common::Input::InputCallback button_callback{
[this, index](Common::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;
}
Common::Input::InputCallback button_callback{
[this, index](Common::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;
}
Common::Input::InputCallback button_callback{
[this, index](Common::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::SetMouseButtonParam(std::size_t index, Common::ParamPackage param) {
if (index >= mouse_button_params.size()) {
return;
}
mouse_button_params[index] = param;
ReloadInput();
}
void EmulatedDevices::SetKeyboardButton(Common::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 status
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;
}
UpdateKey(index, current_status.value);
TriggerOnChange(DeviceTriggerType::Keyboard);
}
void EmulatedDevices::UpdateKey(std::size_t key_index, bool status) {
constexpr u8 KEYS_PER_BYTE = 8;
auto& entry = device_status.keyboard_state.key[key_index / KEYS_PER_BYTE];
const u8 mask = static_cast<u8>(1 << (key_index % KEYS_PER_BYTE));
if (status) {
entry = entry | mask;
} else {
entry = static_cast<u8>(entry & ~mask);
}
}
void EmulatedDevices::SetKeyboardModifier(Common::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(Common::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);
}
KeyboardValues EmulatedDevices::GetKeyboardValues() const {
return device_status.keyboard_values;
}
KeyboardModifierValues EmulatedDevices::GetKeyboardModdifierValues() const {
return device_status.keyboard_moddifier_values;
}
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 auto& 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};
const auto& iterator = callback_list.find(key);
if (iterator == callback_list.end()) {
LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
return;
}
callback_list.erase(iterator);
}
} // namespace Core::HID

196
src/core/hid/emulated_devices.h Executable file
View File

@@ -0,0 +1,196 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <functional>
#include <memory>
#include <mutex>
#include <unordered_map>
#include "common/common_types.h"
#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<Common::Input::InputDevice>,
Settings::NativeKeyboard::NumKeyboardKeys>;
using KeyboardModifierDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
Settings::NativeKeyboard::NumKeyboardMods>;
using MouseButtonDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
Settings::NativeMouseButton::NumMouseButtons>;
using MouseButtonParams =
std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>;
using KeyboardValues =
std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>;
using KeyboardModifierValues =
std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardMods>;
using MouseButtonValues =
std::array<Common::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 HID serices
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:
/**
* Contains all input data related to external devices that aren't necesarily a controller
* like keyboard and mouse
*/
EmulatedDevices();
~EmulatedDevices();
YUZU_NON_COPYABLE(EmulatedDevices);
YUZU_NON_MOVEABLE(EmulatedDevices);
/// Removes all callbacks created from input devices
void UnloadInput();
/// Sets the emulated console into configuring mode. Locking all HID service events from being
/// moddified
void EnableConfiguration();
/// Returns the emulated console to the normal behaivour
void DisableConfiguration();
/// Returns true if the emulated device is on configuring mode
bool IsConfiguring() const;
/// Reload all input devices
void ReloadInput();
/// Overrides current mapped devices with the stored configuration and reloads all input devices
void ReloadFromSettings();
/// Saves the current mapped configuration
void SaveCurrentConfig();
/// Reverts any mapped changes made that weren't saved
void RestoreConfig();
/// Returns the current mapped motion device
Common::ParamPackage GetMouseButtonParam(std::size_t index) const;
/**
* Updates the current mapped mouse button device
* @param ParamPackage with controller data to be mapped
*/
void SetMouseButtonParam(std::size_t index, Common::ParamPackage param);
/// Returns the latest status of button input from the keyboard with parameters
KeyboardValues GetKeyboardValues() const;
/// Returns the latest status of button input from the keyboard modifiers with parameters
KeyboardModifierValues GetKeyboardModdifierValues() const;
/// Returns the latest status of button input from the mouse with parameters
MouseButtonValues GetMouseButtonsValues() const;
/// Returns the latest status of button input from the keyboard
KeyboardKey GetKeyboard() const;
/// Returns the latest status of button input from the keyboard modifiers
KeyboardModifier GetKeyboardModifier() const;
/// Returns the latest status of button input from the mouse
MouseButton GetMouseButtons() const;
/// Returns the latest mouse coordinates
MousePosition GetMousePosition() const;
/**
* Adds a callback to the list of events
* @param ConsoleUpdateCallback that will be triggered
* @return an unique key corresponding to the callback index in the list
*/
int SetCallback(InterfaceUpdateCallback update_callback);
/**
* Removes a callback from the list stopping any future events to this object
* @param Key corresponding to the callback index in the list
*/
void DeleteCallback(int key);
private:
/// Helps assigning a value to keyboard_state
void UpdateKey(std::size_t key_index, bool status);
/**
* Updates the touch status of the console
* @param callback: A CallbackStatus containing the key status
* @param index: key ID to be updated
*/
void SetKeyboardButton(Common::Input::CallbackStatus callback, std::size_t index);
/**
* Updates the touch status of the console
* @param callback: A CallbackStatus containing the modifier key status
* @param index: modifier key ID to be updated
*/
void SetKeyboardModifier(Common::Input::CallbackStatus callback, std::size_t index);
/**
* Updates the touch status of the console
* @param callback: A CallbackStatus containing the button status
* @param index: Button ID of the to be updated
*/
void SetMouseButton(Common::Input::CallbackStatus callback, std::size_t index);
/**
* Triggers a callback that something has changed on the device status
* @param Input type of the event to 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;
// Stores the current status of all external device input
DeviceStatus device_status;
};
} // namespace Core::HID

168
src/core/hid/hid_core.cpp Executable file
View File

@@ -0,0 +1,168 @@
// 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/emulated_console.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/emulated_devices.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;
}
s8 HIDCore::GetPlayerCount() const {
s8 active_players = 0;
for (std::size_t player_index = 0; player_index < available_controllers - 2; ++player_index) {
const auto* const controller = GetEmulatedControllerByIndex(player_index);
if (controller->IsConnected()) {
active_players++;
}
}
return active_players;
}
NpadIdType HIDCore::GetFirstNpadId() const {
for (std::size_t player_index = 0; player_index < available_controllers; ++player_index) {
const auto* const controller = GetEmulatedControllerByIndex(player_index);
if (controller->IsConnected()) {
return controller->GetNpadIdType();
}
}
return NpadIdType::Player1;
}
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

73
src/core/hid/hid_core.h Executable file
View File

@@ -0,0 +1,73 @@
// 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/hid_types.h"
namespace Core::HID {
class EmulatedConsole;
class EmulatedController;
class EmulatedDevices;
} // namespace Core::HID
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;
/// Counts the connected players from P1-P8
s8 GetPlayerCount() const;
/// Returns the first connected npad id
NpadIdType GetFirstNpadId() const;
/// Reloads all input devices from settings
void ReloadInputDevices();
/// Removes all callbacks from input common
void UnloadInputDevices();
/// Number of emulated controllers
const std::size_t available_controllers{10};
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

416
src/core/hid/hid_types.h Executable file
View File

@@ -0,0 +1,416 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/point.h"
#include "common/uuid.h"
namespace Core::HID {
// This is nn::hid::NpadIdType
enum class NpadIdType : u8 {
Player1 = 0x0,
Player2 = 0x1,
Player3 = 0x2,
Player4 = 0x3,
Player5 = 0x4,
Player6 = 0x5,
Player7 = 0x6,
Player8 = 0x7,
Other = 0x10,
Handheld = 0x20,
Invalid = 0xFF,
};
/// Converts a NpadIdType to an array index.
constexpr size_t NpadIdTypeToIndex(NpadIdType npad_id_type) {
switch (npad_id_type) {
case NpadIdType::Player1:
return 0;
case NpadIdType::Player2:
return 1;
case NpadIdType::Player3:
return 2;
case NpadIdType::Player4:
return 3;
case NpadIdType::Player5:
return 4;
case NpadIdType::Player6:
return 5;
case NpadIdType::Player7:
return 6;
case NpadIdType::Player8:
return 7;
case NpadIdType::Handheld:
return 8;
case NpadIdType::Other:
return 9;
default:
return 0;
}
}
/// Converts an array index to a NpadIdType
constexpr NpadIdType IndexToNpadIdType(size_t index) {
switch (index) {
case 0:
return NpadIdType::Player1;
case 1:
return NpadIdType::Player2;
case 2:
return NpadIdType::Player3;
case 3:
return NpadIdType::Player4;
case 4:
return NpadIdType::Player5;
case 5:
return NpadIdType::Player6;
case 6:
return NpadIdType::Player7;
case 7:
return NpadIdType::Player8;
case 8:
return NpadIdType::Handheld;
case 9:
return NpadIdType::Other;
default:
return NpadIdType::Invalid;
}
}
// This is nn::hid::NpadType
enum class NpadType : u8 {
None = 0,
ProController = 3,
Handheld = 4,
JoyconDual = 5,
JoyconLeft = 6,
JoyconRight = 7,
GameCube = 8,
Pokeball = 9,
MaxNpadType = 10,
};
// This is nn::hid::NpadStyleTag
struct NpadStyleTag {
union {
u32 raw{};
BitField<0, 1, u32> fullkey;
BitField<1, 1, u32> handheld;
BitField<2, 1, u32> joycon_dual;
BitField<3, 1, u32> joycon_left;
BitField<4, 1, u32> joycon_right;
BitField<5, 1, u32> gamecube;
BitField<6, 1, u32> palma;
BitField<7, 1, u32> lark;
BitField<8, 1, u32> handheld_lark;
BitField<9, 1, u32> lucia;
BitField<10, 1, u32> lagoon;
BitField<11, 1, u32> lager;
BitField<29, 1, u32> system_ext;
BitField<30, 1, u32> system;
};
};
static_assert(sizeof(NpadStyleTag) == 4, "NpadStyleTag is an invalid size");
// This is nn::hid::TouchAttribute
struct TouchAttribute {
union {
u32 raw{};
BitField<0, 1, u32> start_touch;
BitField<1, 1, u32> end_touch;
};
};
static_assert(sizeof(TouchAttribute) == 0x4, "TouchAttribute is an invalid size");
// This is nn::hid::TouchState
struct TouchState {
u64 delta_time;
TouchAttribute attribute;
u32 finger;
Common::Point<u32> position;
u32 diameter_x;
u32 diameter_y;
u32 rotation_angle;
};
static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size");
// This is nn::hid::NpadControllerColor
struct NpadControllerColor {
u32 body;
u32 button;
};
static_assert(sizeof(NpadControllerColor) == 8, "NpadControllerColor is an invalid size");
// This is nn::hid::AnalogStickState
struct AnalogStickState {
s32 x;
s32 y;
};
static_assert(sizeof(AnalogStickState) == 8, "AnalogStickState is an invalid size");
// This is nn::hid::server::NpadGcTriggerState
struct NpadGcTriggerState {
s64 sampling_number{};
s32 left{};
s32 right{};
};
static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size");
// This is nn::hid::system::NpadBatteryLevel
using BatteryLevel = u32;
static_assert(sizeof(BatteryLevel) == 0x4, "BatteryLevel is an invalid size");
// This is nn::hid::system::NpadPowerInfo
struct NpadPowerInfo {
bool is_powered;
bool is_charging;
INSERT_PADDING_BYTES(0x6);
BatteryLevel battery_level;
};
static_assert(sizeof(NpadPowerInfo) == 0xC, "NpadPowerInfo is an invalid size");
struct LedPattern {
explicit LedPattern(u64 light1, u64 light2, u64 light3, u64 light4) {
position1.Assign(light1);
position2.Assign(light2);
position3.Assign(light3);
position4.Assign(light4);
}
union {
u64 raw{};
BitField<0, 1, u64> position1;
BitField<1, 1, u64> position2;
BitField<2, 1, u64> position3;
BitField<3, 1, u64> position4;
};
};
// This is nn::hid::NpadButton
enum class NpadButton : u64 {
None = 0,
A = 1U << 0,
B = 1U << 1,
X = 1U << 2,
Y = 1U << 3,
StickL = 1U << 4,
StickR = 1U << 5,
L = 1U << 6,
R = 1U << 7,
ZL = 1U << 8,
ZR = 1U << 9,
Plus = 1U << 10,
Minus = 1U << 11,
Left = 1U << 12,
Up = 1U << 13,
Right = 1U << 14,
Down = 1U << 15,
StickLLeft = 1U << 16,
StickLUp = 1U << 17,
StickLRight = 1U << 18,
StickLDown = 1U << 19,
StickRLeft = 1U << 20,
StickRUp = 1U << 21,
StickRRight = 1U << 22,
StickRDown = 1U << 23,
LeftSL = 1U << 24,
LeftSR = 1U << 25,
RightSL = 1U << 26,
RightSR = 1U << 27,
Palma = 1U << 28,
Verification = 1U << 29,
HandheldLeftB = 1U << 30,
LagonCLeft = 1U << 31,
LagonCUp = 1ULL << 32,
LagonCRight = 1ULL << 33,
LagonCDown = 1ULL << 34,
};
DECLARE_ENUM_FLAG_OPERATORS(NpadButton);
struct NpadButtonState {
union {
NpadButton raw{};
// Buttons
BitField<0, 1, u64> a;
BitField<1, 1, u64> b;
BitField<2, 1, u64> x;
BitField<3, 1, u64> y;
BitField<4, 1, u64> stick_l;
BitField<5, 1, u64> stick_r;
BitField<6, 1, u64> l;
BitField<7, 1, u64> r;
BitField<8, 1, u64> zl;
BitField<9, 1, u64> zr;
BitField<10, 1, u64> plus;
BitField<11, 1, u64> minus;
// D-Pad
BitField<12, 1, u64> left;
BitField<13, 1, u64> up;
BitField<14, 1, u64> right;
BitField<15, 1, u64> down;
// Left JoyStick
BitField<16, 1, u64> stick_l_left;
BitField<17, 1, u64> stick_l_up;
BitField<18, 1, u64> stick_l_right;
BitField<19, 1, u64> stick_l_down;
// Right JoyStick
BitField<20, 1, u64> stick_r_left;
BitField<21, 1, u64> stick_r_up;
BitField<22, 1, u64> stick_r_right;
BitField<23, 1, u64> stick_r_down;
BitField<24, 1, u64> left_sl;
BitField<25, 1, u64> left_sr;
BitField<26, 1, u64> right_sl;
BitField<27, 1, u64> right_sr;
BitField<28, 1, u64> palma;
BitField<29, 1, u64> verification;
BitField<30, 1, u64> handheld_left_b;
BitField<31, 1, u64> lagon_c_left;
BitField<32, 1, u64> lagon_c_up;
BitField<33, 1, u64> lagon_c_right;
BitField<34, 1, u64> lagon_c_down;
};
};
static_assert(sizeof(NpadButtonState) == 0x8, "NpadButtonState has incorrect size.");
// This is nn::hid::DebugPadButton
struct DebugPadButton {
union {
u32 raw{};
BitField<0, 1, u32> a;
BitField<1, 1, u32> b;
BitField<2, 1, u32> x;
BitField<3, 1, u32> y;
BitField<4, 1, u32> l;
BitField<5, 1, u32> r;
BitField<6, 1, u32> zl;
BitField<7, 1, u32> zr;
BitField<8, 1, u32> plus;
BitField<9, 1, u32> minus;
BitField<10, 1, u32> d_left;
BitField<11, 1, u32> d_up;
BitField<12, 1, u32> d_right;
BitField<13, 1, u32> d_down;
};
};
static_assert(sizeof(DebugPadButton) == 0x4, "DebugPadButton is an invalid size");
// This is nn::hid::VibrationDeviceType
enum class VibrationDeviceType : u32 {
Unknown = 0,
LinearResonantActuator = 1,
GcErm = 2,
};
// This is nn::hid::VibrationDevicePosition
enum class VibrationDevicePosition : u32 {
None = 0,
Left = 1,
Right = 2,
};
// This is nn::hid::VibrationValue
struct VibrationValue {
f32 low_amplitude;
f32 low_frequency;
f32 high_amplitude;
f32 high_frequency;
};
static_assert(sizeof(VibrationValue) == 0x10, "VibrationValue has incorrect size.");
// This is nn::hid::VibrationGcErmCommand
enum class VibrationGcErmCommand : u64 {
Stop = 0,
Start = 1,
StopHard = 2,
};
// This is nn::hid::VibrationDeviceInfo
struct VibrationDeviceInfo {
VibrationDeviceType type{};
VibrationDevicePosition position{};
};
static_assert(sizeof(VibrationDeviceInfo) == 0x8, "VibrationDeviceInfo has incorrect size.");
// This is nn::hid::KeyboardModifier
struct KeyboardModifier {
union {
u32 raw{};
BitField<0, 1, u32> control;
BitField<1, 1, u32> shift;
BitField<2, 1, u32> left_alt;
BitField<3, 1, u32> right_alt;
BitField<4, 1, u32> gui;
BitField<8, 1, u32> caps_lock;
BitField<9, 1, u32> scroll_lock;
BitField<10, 1, u32> num_lock;
BitField<11, 1, u32> katakana;
BitField<12, 1, u32> hiragana;
};
};
static_assert(sizeof(KeyboardModifier) == 0x4, "KeyboardModifier is an invalid size");
// This is nn::hid::KeyboardKey
struct KeyboardKey {
// This should be a 256 bit flag
std::array<u8, 32> key;
};
static_assert(sizeof(KeyboardKey) == 0x20, "KeyboardKey is an invalid size");
// This is nn::hid::MouseButton
struct MouseButton {
union {
u32_le raw{};
BitField<0, 1, u32> left;
BitField<1, 1, u32> right;
BitField<2, 1, u32> middle;
BitField<3, 1, u32> forward;
BitField<4, 1, u32> back;
};
};
static_assert(sizeof(MouseButton) == 0x4, "MouseButton is an invalid size");
// This is nn::hid::MouseAttribute
struct MouseAttribute {
union {
u32 raw{};
BitField<0, 1, u32> transferable;
BitField<1, 1, u32> is_connected;
};
};
static_assert(sizeof(MouseAttribute) == 0x4, "MouseAttribute is an invalid size");
// This is nn::hid::detail::MouseState
struct MouseState {
s64 sampling_number;
s32 x;
s32 y;
s32 delta_x;
s32 delta_y;
s32 delta_wheel_x;
s32 delta_wheel_y;
MouseButton button;
MouseAttribute attribute;
};
static_assert(sizeof(MouseState) == 0x28, "MouseState is an invalid size");
} // namespace Core::HID

355
src/core/hid/input_converter.cpp Executable file
View File

@@ -0,0 +1,355 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#include <random>
#include "common/input.h"
#include "core/hid/input_converter.h"
namespace Core::HID {
Common::Input::BatteryStatus TransformToBattery(const Common::Input::CallbackStatus& callback) {
Common::Input::BatteryStatus battery{Common::Input::BatteryStatus::None};
switch (callback.type) {
case Common::Input::InputType::Analog:
case Common::Input::InputType::Trigger: {
const auto value = TransformToTrigger(callback).analog.value;
battery = Common::Input::BatteryLevel::Empty;
if (value > 0.2f) {
battery = Common::Input::BatteryLevel::Critical;
}
if (value > 0.4f) {
battery = Common::Input::BatteryLevel::Low;
}
if (value > 0.6f) {
battery = Common::Input::BatteryLevel::Medium;
}
if (value > 0.8f) {
battery = Common::Input::BatteryLevel::Full;
}
if (value >= 1.0f) {
battery = Common::Input::BatteryLevel::Charging;
}
break;
}
case Common::Input::InputType::Button:
battery = callback.button_status.value ? Common::Input::BatteryLevel::Charging
: Common::Input::BatteryLevel::Critical;
break;
case Common::Input::InputType::Battery:
battery = callback.battery_status;
break;
default:
LOG_ERROR(Input, "Conversion from type {} to battery not implemented", callback.type);
break;
}
return battery;
}
Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatus& callback) {
Common::Input::ButtonStatus status{};
switch (callback.type) {
case Common::Input::InputType::Analog:
case Common::Input::InputType::Trigger:
status.value = TransformToTrigger(callback).pressed.value;
break;
case Common::Input::InputType::Button:
status = callback.button_status;
break;
default:
LOG_ERROR(Input, "Conversion from type {} to button not implemented", callback.type);
break;
}
if (status.inverted) {
status.value = !status.value;
}
return status;
}
Common::Input::MotionStatus TransformToMotion(const Common::Input::CallbackStatus& callback) {
Common::Input::MotionStatus status{};
switch (callback.type) {
case Common::Input::InputType::Button: {
Common::Input::AnalogProperties properties{
.deadzone = 0.0,
.range = 1.0f,
.offset = 0.0,
};
status.delta_timestamp = 5000;
status.force_update = true;
status.accel.x = {
.value = 0.0f,
.raw_value = 0.0f,
.properties = properties,
};
status.accel.y = {
.value = 0.0f,
.raw_value = 0.0f,
.properties = properties,
};
status.accel.z = {
.value = 0.0f,
.raw_value = -1.0f,
.properties = properties,
};
if (TransformToButton(callback).value) {
std::random_device device;
std::mt19937 gen(device());
std::uniform_int_distribution<s16> distribution(-1000, 1000);
status.accel.x.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
status.accel.y.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
status.accel.z.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
status.gyro.x = {
.value = 0,
.raw_value = static_cast<f32>(distribution(gen)) * 0.001f,
.properties = properties,
};
status.gyro.y = {
.value = 0,
.raw_value = static_cast<f32>(distribution(gen)) * 0.001f,
.properties = properties,
};
status.gyro.z = {
.value = 0,
.raw_value = static_cast<f32>(distribution(gen)) * 0.001f,
.properties = properties,
};
}
break;
}
case Common::Input::InputType::Motion:
status = callback.motion_status;
break;
default:
LOG_ERROR(Input, "Conversion from type {} to motion not implemented", callback.type);
break;
}
SanitizeAnalog(status.accel.x, false);
SanitizeAnalog(status.accel.y, false);
SanitizeAnalog(status.accel.z, false);
SanitizeAnalog(status.gyro.x, false);
SanitizeAnalog(status.gyro.y, false);
SanitizeAnalog(status.gyro.z, false);
return status;
}
Common::Input::StickStatus TransformToStick(const Common::Input::CallbackStatus& callback) {
Common::Input::StickStatus status{};
switch (callback.type) {
case Common::Input::InputType::Stick:
status = callback.stick_status;
break;
default:
LOG_ERROR(Input, "Conversion from type {} to stick not implemented", callback.type);
break;
}
SanitizeStick(status.x, status.y, true);
const auto& properties_x = status.x.properties;
const auto& properties_y = status.y.properties;
const float x = status.x.value;
const float y = status.y.value;
// Set directional buttons
status.right = x > properties_x.threshold;
status.left = x < -properties_x.threshold;
status.up = y > properties_y.threshold;
status.down = y < -properties_y.threshold;
return status;
}
Common::Input::TouchStatus TransformToTouch(const Common::Input::CallbackStatus& callback) {
Common::Input::TouchStatus status{};
switch (callback.type) {
case Common::Input::InputType::Touch:
status = callback.touch_status;
break;
default:
LOG_ERROR(Input, "Conversion from type {} to touch not implemented", callback.type);
break;
}
SanitizeAnalog(status.x, true);
SanitizeAnalog(status.y, true);
float& x = status.x.value;
float& y = status.y.value;
// Adjust if value is inverted
x = status.x.properties.inverted ? 1.0f + x : x;
y = status.y.properties.inverted ? 1.0f + y : y;
// clamp value
x = std::clamp(x, 0.0f, 1.0f);
y = std::clamp(y, 0.0f, 1.0f);
if (status.pressed.inverted) {
status.pressed.value = !status.pressed.value;
}
return status;
}
Common::Input::TriggerStatus TransformToTrigger(const Common::Input::CallbackStatus& callback) {
Common::Input::TriggerStatus status{};
float& raw_value = status.analog.raw_value;
bool calculate_button_value = true;
switch (callback.type) {
case Common::Input::InputType::Analog:
status.analog.properties = callback.analog_status.properties;
raw_value = callback.analog_status.raw_value;
break;
case Common::Input::InputType::Button:
status.analog.properties.range = 1.0f;
status.analog.properties.inverted = callback.button_status.inverted;
raw_value = callback.button_status.value ? 1.0f : 0.0f;
break;
case Common::Input::InputType::Trigger:
status = callback.trigger_status;
calculate_button_value = false;
break;
default:
LOG_ERROR(Input, "Conversion from type {} to trigger not implemented", callback.type);
break;
}
SanitizeAnalog(status.analog, true);
const auto& properties = status.analog.properties;
float& value = status.analog.value;
// Set button status
if (calculate_button_value) {
status.pressed.value = value > properties.threshold;
}
// Adjust if value is inverted
value = properties.inverted ? 1.0f + value : value;
// clamp value
value = std::clamp(value, 0.0f, 1.0f);
return status;
}
void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) {
const auto& properties = analog.properties;
float& raw_value = analog.raw_value;
float& value = analog.value;
if (!std::isnormal(raw_value)) {
raw_value = 0;
}
// Apply center offset
raw_value -= properties.offset;
// Set initial values to be formated
value = raw_value;
// Calculate vector size
const float r = std::abs(value);
// Return zero if value is smaller than the deadzone
if (r <= properties.deadzone || properties.deadzone == 1.0f) {
analog.value = 0;
return;
}
// Adjust range of value
const float deadzone_factor =
1.0f / r * (r - properties.deadzone) / (1.0f - properties.deadzone);
value = value * deadzone_factor / properties.range;
// Invert direction if needed
if (properties.inverted) {
value = -value;
}
// Clamp value
if (clamp_value) {
value = std::clamp(value, -1.0f, 1.0f);
}
}
void SanitizeStick(Common::Input::AnalogStatus& analog_x, Common::Input::AnalogStatus& analog_y,
bool clamp_value) {
const auto& properties_x = analog_x.properties;
const auto& properties_y = analog_y.properties;
float& raw_x = analog_x.raw_value;
float& raw_y = analog_y.raw_value;
float& x = analog_x.value;
float& y = analog_y.value;
if (!std::isnormal(raw_x)) {
raw_x = 0;
}
if (!std::isnormal(raw_y)) {
raw_y = 0;
}
// Apply center offset
raw_x += properties_x.offset;
raw_y += properties_y.offset;
// Apply X scale correction from offset
if (std::abs(properties_x.offset) < 0.5f) {
if (raw_x > 0) {
raw_x /= 1 + properties_x.offset;
} else {
raw_x /= 1 - properties_x.offset;
}
}
// Apply Y scale correction from offset
if (std::abs(properties_y.offset) < 0.5f) {
if (raw_y > 0) {
raw_y /= 1 + properties_y.offset;
} else {
raw_y /= 1 - properties_y.offset;
}
}
// Invert direction if needed
raw_x = properties_x.inverted ? -raw_x : raw_x;
raw_y = properties_y.inverted ? -raw_y : raw_y;
// Set initial values to be formated
x = raw_x;
y = raw_y;
// Calculate vector size
float r = x * x + y * y;
r = std::sqrt(r);
// TODO(German77): Use deadzone and range of both axis
// Return zero if values are smaller than the deadzone
if (r <= properties_x.deadzone || properties_x.deadzone >= 1.0f) {
x = 0;
y = 0;
return;
}
// Adjust range of joystick
const float deadzone_factor =
1.0f / r * (r - properties_x.deadzone) / (1.0f - properties_x.deadzone);
x = x * deadzone_factor / properties_x.range;
y = y * deadzone_factor / properties_x.range;
r = r * deadzone_factor / properties_x.range;
// Normalize joystick
if (clamp_value && r > 1.0f) {
x /= r;
y /= r;
}
}
} // namespace Core::HID

86
src/core/hid/input_converter.h Executable file
View File

@@ -0,0 +1,86 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#pragma once
namespace Common::Input {
struct CallbackStatus;
enum class BatteryLevel : u32;
using BatteryStatus = BatteryLevel;
struct AnalogStatus;
struct ButtonStatus;
struct MotionStatus;
struct StickStatus;
struct TouchStatus;
struct TriggerStatus;
}; // namespace Common::Input
namespace Core::HID {
/**
* Converts raw input data into a valid battery status.
*
* @param Supported callbacks: Analog, Battery, Trigger.
* @return A valid BatteryStatus object.
*/
Common::Input::BatteryStatus TransformToBattery(const Common::Input::CallbackStatus& callback);
/**
* Converts raw input data into a valid button status. Applies invert properties to the output.
*
* @param Supported callbacks: Analog, Button, Trigger.
* @return A valid TouchStatus object.
*/
Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatus& callback);
/**
* Converts raw input data into a valid motion status.
*
* @param Supported callbacks: Motion.
* @return A valid TouchStatus object.
*/
Common::Input::MotionStatus TransformToMotion(const Common::Input::CallbackStatus& callback);
/**
* Converts raw input data into a valid stick status. Applies offset, deadzone, range and invert
* properties to the output.
*
* @param Supported callbacks: Stick.
* @return A valid StickStatus object.
*/
Common::Input::StickStatus TransformToStick(const Common::Input::CallbackStatus& callback);
/**
* Converts raw input data into a valid touch status.
*
* @param Supported callbacks: Touch.
* @return A valid TouchStatus object.
*/
Common::Input::TouchStatus TransformToTouch(const Common::Input::CallbackStatus& callback);
/**
* Converts raw input data into a valid trigger status. Applies offset, deadzone, range and
* invert properties to the output. Button status uses the threshold property if necessary.
*
* @param Supported callbacks: Analog, Button, Trigger.
* @return A valid TriggerStatus object.
*/
Common::Input::TriggerStatus TransformToTrigger(const Common::Input::CallbackStatus& callback);
/**
* Converts raw analog data into a valid analog value
* @param An analog object containing raw data and properties, bool that determines if the value
* needs to be clamped between -1.0f and 1.0f.
*/
void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value);
/**
* Converts raw stick data into a valid stick value
* @param Two analog objects containing raw data and properties, bool that determines if the value
* needs to be clamped into the unit circle.
*/
void SanitizeStick(Common::Input::AnalogStatus& analog_x, Common::Input::AnalogStatus& analog_y,
bool clamp_value);
} // namespace Core::HID

View File

@@ -0,0 +1,61 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/core.h"
#include "core/hid/hid_types.h"
#include "core/hid/input_interpreter.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/sm/sm.h"
InputInterpreter::InputInterpreter(Core::System& system)
: npad{system.ServiceManager()
.GetService<Service::HID::Hid>("hid")
->GetAppletResource()
->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)} {
ResetButtonStates();
}
InputInterpreter::~InputInterpreter() = default;
void InputInterpreter::PollInput() {
const u64 button_state = npad.GetAndResetPressState();
previous_index = current_index;
current_index = (current_index + 1) % button_states.size();
button_states[current_index] = button_state;
}
void InputInterpreter::ResetButtonStates() {
previous_index = 0;
current_index = 0;
button_states[0] = 0xFFFFFFFFFFFFFFFF;
for (std::size_t i = 1; i < button_states.size(); ++i) {
button_states[i] = 0;
}
}
bool InputInterpreter::IsButtonPressed(Core::HID::NpadButton button) const {
return (button_states[current_index] & static_cast<u64>(button)) != 0;
}
bool InputInterpreter::IsButtonPressedOnce(Core::HID::NpadButton button) const {
const bool current_press = (button_states[current_index] & static_cast<u64>(button)) != 0;
const bool previous_press = (button_states[previous_index] & static_cast<u64>(button)) != 0;
return current_press && !previous_press;
}
bool InputInterpreter::IsButtonHeld(Core::HID::NpadButton button) const {
u64 held_buttons{button_states[0]};
for (std::size_t i = 1; i < button_states.size(); ++i) {
held_buttons &= button_states[i];
}
return (held_buttons & static_cast<u64>(button)) != 0;
}

112
src/core/hid/input_interpreter.h Executable file
View File

@@ -0,0 +1,112 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include "common/common_types.h"
namespace Core {
class System;
}
namespace Core::HID {
enum class NpadButton : u64;
}
namespace Service::HID {
class Controller_NPad;
}
/**
* The InputInterpreter class interfaces with HID to retrieve button press states.
* Input is intended to be polled every 50ms so that a button is considered to be
* held down after 400ms has elapsed since the initial button press and subsequent
* repeated presses occur every 50ms.
*/
class InputInterpreter {
public:
explicit InputInterpreter(Core::System& system);
virtual ~InputInterpreter();
/// Gets a button state from HID and inserts it into the array of button states.
void PollInput();
/// Resets all the button states to their defaults.
void ResetButtonStates();
/**
* Checks whether the button is pressed.
*
* @param button The button to check.
*
* @returns True when the button is pressed.
*/
[[nodiscard]] bool IsButtonPressed(Core::HID::NpadButton button) const;
/**
* Checks whether any of the buttons in the parameter list is pressed.
*
* @tparam HIDButton The buttons to check.
*
* @returns True when at least one of the buttons is pressed.
*/
template <Core::HID::NpadButton... T>
[[nodiscard]] bool IsAnyButtonPressed() {
return (IsButtonPressed(T) || ...);
}
/**
* The specified button is considered to be pressed once
* if it is currently pressed and not pressed previously.
*
* @param button The button to check.
*
* @returns True when the button is pressed once.
*/
[[nodiscard]] bool IsButtonPressedOnce(Core::HID::NpadButton button) const;
/**
* Checks whether any of the buttons in the parameter list is pressed once.
*
* @tparam T The buttons to check.
*
* @returns True when at least one of the buttons is pressed once.
*/
template <Core::HID::NpadButton... T>
[[nodiscard]] bool IsAnyButtonPressedOnce() const {
return (IsButtonPressedOnce(T) || ...);
}
/**
* The specified button is considered to be held down if it is pressed in all 9 button states.
*
* @param button The button to check.
*
* @returns True when the button is held down.
*/
[[nodiscard]] bool IsButtonHeld(Core::HID::NpadButton button) const;
/**
* Checks whether any of the buttons in the parameter list is held down.
*
* @tparam T The buttons to check.
*
* @returns True when at least one of the buttons is held down.
*/
template <Core::HID::NpadButton... T>
[[nodiscard]] bool IsAnyButtonHeld() const {
return (IsButtonHeld(T) || ...);
}
private:
Service::HID::Controller_NPad& npad;
/// Stores 9 consecutive button states polled from HID.
std::array<u64, 9> button_states{};
std::size_t previous_index{};
std::size_t current_index{};
};

280
src/core/hid/motion_input.cpp Executable file
View File

@@ -0,0 +1,280 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#include "common/math_util.h"
#include "core/hid/motion_input.h"
namespace Core::HID {
MotionInput::MotionInput() {
// Initialize PID constants with default values
SetPID(0.3f, 0.005f, 0.0f);
}
void MotionInput::SetPID(f32 new_kp, f32 new_ki, f32 new_kd) {
kp = new_kp;
ki = new_ki;
kd = new_kd;
}
void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) {
accel = acceleration;
}
void MotionInput::SetGyroscope(const Common::Vec3f& gyroscope) {
gyro = gyroscope - gyro_drift;
// Auto adjust drift to minimize drift
if (!IsMoving(0.1f)) {
gyro_drift = (gyro_drift * 0.9999f) + (gyroscope * 0.0001f);
}
if (gyro.Length2() < gyro_threshold) {
gyro = {};
} else {
only_accelerometer = false;
}
}
void MotionInput::SetQuaternion(const Common::Quaternion<f32>& quaternion) {
quat = quaternion;
}
void MotionInput::SetGyroDrift(const Common::Vec3f& drift) {
gyro_drift = drift;
}
void MotionInput::SetGyroThreshold(f32 threshold) {
gyro_threshold = threshold;
}
void MotionInput::EnableReset(bool reset) {
reset_enabled = reset;
}
void MotionInput::ResetRotations() {
rotations = {};
}
bool MotionInput::IsMoving(f32 sensitivity) const {
return gyro.Length() >= sensitivity || accel.Length() <= 0.9f || accel.Length() >= 1.1f;
}
bool MotionInput::IsCalibrated(f32 sensitivity) const {
return real_error.Length() < sensitivity;
}
void MotionInput::UpdateRotation(u64 elapsed_time) {
const auto sample_period = static_cast<f32>(elapsed_time) / 1000000.0f;
if (sample_period > 0.1f) {
return;
}
rotations += gyro * sample_period;
}
// Based on Madgwick's implementation of Mayhony's AHRS algorithm.
// https://github.com/xioTechnologies/Open-Source-AHRS-With-x-IMU/blob/master/x-IMU%20IMU%20and%20AHRS%20Algorithms/x-IMU%20IMU%20and%20AHRS%20Algorithms/AHRS/MahonyAHRS.cs
void MotionInput::UpdateOrientation(u64 elapsed_time) {
if (!IsCalibrated(0.1f)) {
ResetOrientation();
}
// Short name local variable for readability
f32 q1 = quat.w;
f32 q2 = quat.xyz[0];
f32 q3 = quat.xyz[1];
f32 q4 = quat.xyz[2];
const auto sample_period = static_cast<f32>(elapsed_time) / 1000000.0f;
// Ignore invalid elapsed time
if (sample_period > 0.1f) {
return;
}
const auto normal_accel = accel.Normalized();
auto rad_gyro = gyro * Common::PI * 2;
const f32 swap = rad_gyro.x;
rad_gyro.x = rad_gyro.y;
rad_gyro.y = -swap;
rad_gyro.z = -rad_gyro.z;
// Clear gyro values if there is no gyro present
if (only_accelerometer) {
rad_gyro.x = 0;
rad_gyro.y = 0;
rad_gyro.z = 0;
}
// Ignore drift correction if acceleration is not reliable
if (accel.Length() >= 0.75f && accel.Length() <= 1.25f) {
const f32 ax = -normal_accel.x;
const f32 ay = normal_accel.y;
const f32 az = -normal_accel.z;
// Estimated direction of gravity
const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
const f32 vy = 2.0f * (q1 * q2 + q3 * q4);
const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
// Error is cross product between estimated direction and measured direction of gravity
const Common::Vec3f new_real_error = {
az * vx - ax * vz,
ay * vz - az * vy,
ax * vy - ay * vx,
};
derivative_error = new_real_error - real_error;
real_error = new_real_error;
// Prevent integral windup
if (ki != 0.0f && !IsCalibrated(0.05f)) {
integral_error += real_error;
} else {
integral_error = {};
}
// Apply feedback terms
if (!only_accelerometer) {
rad_gyro += kp * real_error;
rad_gyro += ki * integral_error;
rad_gyro += kd * derivative_error;
} else {
// Give more weight to accelerometer values to compensate for the lack of gyro
rad_gyro += 35.0f * kp * real_error;
rad_gyro += 10.0f * ki * integral_error;
rad_gyro += 10.0f * kd * derivative_error;
// Emulate gyro values for games that need them
gyro.x = -rad_gyro.y;
gyro.y = rad_gyro.x;
gyro.z = -rad_gyro.z;
UpdateRotation(elapsed_time);
}
}
const f32 gx = rad_gyro.y;
const f32 gy = rad_gyro.x;
const f32 gz = rad_gyro.z;
// Integrate rate of change of quaternion
const f32 pa = q2;
const f32 pb = q3;
const f32 pc = q4;
q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period);
q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period);
q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
quat.w = q1;
quat.xyz[0] = q2;
quat.xyz[1] = q3;
quat.xyz[2] = q4;
quat = quat.Normalized();
}
std::array<Common::Vec3f, 3> MotionInput::GetOrientation() const {
const Common::Quaternion<float> quad{
.xyz = {-quat.xyz[1], -quat.xyz[0], -quat.w},
.w = -quat.xyz[2],
};
const std::array<float, 16> matrix4x4 = quad.ToMatrix();
return {Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]),
Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]),
Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10])};
}
Common::Vec3f MotionInput::GetAcceleration() const {
return accel;
}
Common::Vec3f MotionInput::GetGyroscope() const {
return gyro;
}
Common::Quaternion<f32> MotionInput::GetQuaternion() const {
return quat;
}
Common::Vec3f MotionInput::GetRotations() const {
return rotations;
}
void MotionInput::ResetOrientation() {
if (!reset_enabled || only_accelerometer) {
return;
}
if (!IsMoving(0.5f) && accel.z <= -0.9f) {
++reset_counter;
if (reset_counter > 900) {
quat.w = 0;
quat.xyz[0] = 0;
quat.xyz[1] = 0;
quat.xyz[2] = -1;
SetOrientationFromAccelerometer();
integral_error = {};
reset_counter = 0;
}
} else {
reset_counter = 0;
}
}
void MotionInput::SetOrientationFromAccelerometer() {
int iterations = 0;
const f32 sample_period = 0.015f;
const auto normal_accel = accel.Normalized();
while (!IsCalibrated(0.01f) && ++iterations < 100) {
// Short name local variable for readability
f32 q1 = quat.w;
f32 q2 = quat.xyz[0];
f32 q3 = quat.xyz[1];
f32 q4 = quat.xyz[2];
Common::Vec3f rad_gyro;
const f32 ax = -normal_accel.x;
const f32 ay = normal_accel.y;
const f32 az = -normal_accel.z;
// Estimated direction of gravity
const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
const f32 vy = 2.0f * (q1 * q2 + q3 * q4);
const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
// Error is cross product between estimated direction and measured direction of gravity
const Common::Vec3f new_real_error = {
az * vx - ax * vz,
ay * vz - az * vy,
ax * vy - ay * vx,
};
derivative_error = new_real_error - real_error;
real_error = new_real_error;
rad_gyro += 10.0f * kp * real_error;
rad_gyro += 5.0f * ki * integral_error;
rad_gyro += 10.0f * kd * derivative_error;
const f32 gx = rad_gyro.y;
const f32 gy = rad_gyro.x;
const f32 gz = rad_gyro.z;
// Integrate rate of change of quaternion
const f32 pa = q2;
const f32 pb = q3;
const f32 pc = q4;
q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period);
q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period);
q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
quat.w = q1;
quat.xyz[0] = q2;
quat.xyz[1] = q3;
quat.xyz[2] = q4;
quat = quat.Normalized();
}
}
} // namespace Core::HID

87
src/core/hid/motion_input.h Executable file
View File

@@ -0,0 +1,87 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#pragma once
#include "common/common_types.h"
#include "common/quaternion.h"
#include "common/vector_math.h"
namespace Core::HID {
class MotionInput {
public:
explicit MotionInput();
MotionInput(const MotionInput&) = default;
MotionInput& operator=(const MotionInput&) = default;
MotionInput(MotionInput&&) = default;
MotionInput& operator=(MotionInput&&) = default;
void SetPID(f32 new_kp, f32 new_ki, f32 new_kd);
void SetAcceleration(const Common::Vec3f& acceleration);
void SetGyroscope(const Common::Vec3f& gyroscope);
void SetQuaternion(const Common::Quaternion<f32>& quaternion);
void SetGyroDrift(const Common::Vec3f& drift);
void SetGyroThreshold(f32 threshold);
void EnableReset(bool reset);
void ResetRotations();
void UpdateRotation(u64 elapsed_time);
void UpdateOrientation(u64 elapsed_time);
[[nodiscard]] std::array<Common::Vec3f, 3> GetOrientation() const;
[[nodiscard]] Common::Vec3f GetAcceleration() const;
[[nodiscard]] Common::Vec3f GetGyroscope() const;
[[nodiscard]] Common::Vec3f GetRotations() const;
[[nodiscard]] Common::Quaternion<f32> GetQuaternion() const;
[[nodiscard]] bool IsMoving(f32 sensitivity) const;
[[nodiscard]] bool IsCalibrated(f32 sensitivity) const;
private:
void ResetOrientation();
void SetOrientationFromAccelerometer();
// PID constants
f32 kp;
f32 ki;
f32 kd;
// PID errors
Common::Vec3f real_error;
Common::Vec3f integral_error;
Common::Vec3f derivative_error;
// Quaternion containing the device orientation
Common::Quaternion<f32> quat{{0.0f, 0.0f, -1.0f}, 0.0f};
// Number of full rotations in each axis
Common::Vec3f rotations;
// Acceleration vector measurement in G force
Common::Vec3f accel;
// Gyroscope vector measurement in radians/s.
Common::Vec3f gyro;
// Vector to be substracted from gyro measurements
Common::Vec3f gyro_drift;
// Minimum gyro amplitude to detect if the device is moving
f32 gyro_threshold = 0.0f;
// Number of invalid secuential data
u32 reset_counter = 0;
// If the provided data is invalid the device will be autocalibrated
bool reset_enabled = true;
// Use accelerometer values to calculate position
bool only_accelerometer = true;
};
} // namespace Core::HID

View File

@@ -10,6 +10,8 @@
#include "common/string_util.h"
#include "core/core.h"
#include "core/frontend/applets/controller.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
#include "core/hle/result.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/applet_controller.h"
@@ -25,7 +27,7 @@ namespace Service::AM::Applets {
static Core::Frontend::ControllerParameters ConvertToFrontendParameters(
ControllerSupportArgPrivate private_arg, ControllerSupportArgHeader header, bool enable_text,
std::vector<IdentificationColor> identification_colors, std::vector<ExplainText> text) {
HID::Controller_NPad::NpadStyleSet npad_style_set;
Core::HID::NpadStyleTag npad_style_set;
npad_style_set.raw = private_arg.style_set;
return {
@@ -243,19 +245,11 @@ void Controller::Execute() {
void Controller::ConfigurationComplete() {
ControllerSupportResultInfo result_info{};
const auto& players = Settings::values.players.GetValue();
// If enable_single_mode is enabled, player_count is 1 regardless of any other parameters.
// Otherwise, only count connected players from P1-P8.
result_info.player_count =
is_single_mode
? 1
: static_cast<s8>(std::count_if(players.begin(), players.end() - 2,
[](const auto& player) { return player.connected; }));
result_info.player_count = is_single_mode ? 1 : system.HIDCore().GetPlayerCount();
result_info.selected_id = HID::Controller_NPad::IndexToNPad(std::distance(
players.begin(), std::find_if(players.begin(), players.end(),
[](const auto& player) { return player.connected; })));
result_info.selected_id = static_cast<u32>(system.HIDCore().GetFirstNpadId());
result_info.result = 0;

View File

@@ -231,7 +231,7 @@ void AppletManager::SetDefaultAppletFrontendSet() {
void AppletManager::SetDefaultAppletsIfMissing() {
if (frontend.controller == nullptr) {
frontend.controller =
std::make_unique<Core::Frontend::DefaultControllerApplet>(system.ServiceManager());
std::make_unique<Core::Frontend::DefaultControllerApplet>(system.HIDCore());
}
if (frontend.error == nullptr) {

View File

@@ -3,14 +3,19 @@
// Refer to the license.txt file included.
#include "common/settings.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hid/emulated_console.h"
#include "core/hle/service/hid/controllers/console_sixaxis.h"
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3C200;
Controller_ConsoleSixAxis::Controller_ConsoleSixAxis(Core::System& system_)
: ControllerBase{system_} {}
: ControllerBase{system_} {
console = system.HIDCore().GetEmulatedConsole();
}
Controller_ConsoleSixAxis::~Controller_ConsoleSixAxis() = default;
void Controller_ConsoleSixAxis::OnInit() {}
@@ -38,25 +43,21 @@ void Controller_ConsoleSixAxis::OnUpdate(const Core::Timing::CoreTiming& core_ti
cur_entry.sampling_number2 = cur_entry.sampling_number;
// Try to read sixaxis sensor states
MotionDevice motion_device{};
const auto& device = motions[0];
if (device) {
std::tie(motion_device.accel, motion_device.gyro, motion_device.rotation,
motion_device.orientation, motion_device.quaternion) = device->GetStatus();
console_six_axis.is_seven_six_axis_sensor_at_rest = motion_device.gyro.Length2() < 0.0001f;
}
const auto motion_status = console->GetMotion();
cur_entry.accel = motion_device.accel;
console_six_axis.is_seven_six_axis_sensor_at_rest = motion_status.is_at_rest;
cur_entry.accel = motion_status.accel;
// Zero gyro values as they just mess up with the camera
// Note: Probably a correct sensivity setting must be set
cur_entry.gyro = {};
cur_entry.quaternion = {
{
motion_device.quaternion.xyz.y,
motion_device.quaternion.xyz.x,
-motion_device.quaternion.w,
motion_status.quaternion.xyz.y,
motion_status.quaternion.xyz.x,
-motion_status.quaternion.w,
},
-motion_device.quaternion.xyz.z,
-motion_status.quaternion.xyz.z,
};
console_six_axis.sampling_number++;
@@ -70,13 +71,6 @@ void Controller_ConsoleSixAxis::OnUpdate(const Core::Timing::CoreTiming& core_ti
std::memcpy(transfer_memory, &seven_six_axis, sizeof(seven_six_axis));
}
void Controller_ConsoleSixAxis::OnLoadInputDevices() {
const auto player = Settings::values.players.GetValue()[0];
std::transform(player.motions.begin() + Settings::NativeMotion::MOTION_HID_BEGIN,
player.motions.begin() + Settings::NativeMotion::MOTION_HID_END, motions.begin(),
Input::CreateDevice<Input::MotionDevice>);
}
void Controller_ConsoleSixAxis::SetTransferMemoryPointer(u8* t_mem) {
is_transfer_memory_set = true;
transfer_memory = t_mem;

View File

@@ -5,10 +5,10 @@
#pragma once
#include <array>
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/quaternion.h"
#include "core/frontend/input.h"
#include "core/hid/hid_core.h"
#include "core/hid/hid_types.h"
#include "core/hle/service/hid/controllers/controller_base.h"
namespace Service::HID {
@@ -26,9 +26,6 @@ public:
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
// Called on InitializeSevenSixAxisSensor
void SetTransferMemoryPointer(u8* t_mem);
@@ -38,8 +35,8 @@ public:
private:
struct SevenSixAxisState {
INSERT_PADDING_WORDS(4); // unused
s64_le sampling_number{};
s64_le sampling_number2{};
s64 sampling_number{};
s64 sampling_number2{};
u64 unknown{};
Common::Vec3f accel{};
Common::Vec3f gyro{};
@@ -47,14 +44,24 @@ private:
};
static_assert(sizeof(SevenSixAxisState) == 0x50, "SevenSixAxisState is an invalid size");
struct CommonHeader {
s64 timestamp;
s64 total_entry_count;
s64 last_entry_index;
s64 entry_count;
};
static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size");
// TODO(german77): SevenSixAxisMemory doesn't follow the standard lifo. Investigate
struct SevenSixAxisMemory {
CommonHeader header{};
std::array<SevenSixAxisState, 0x21> sevensixaxis_states{};
};
static_assert(sizeof(SevenSixAxisMemory) == 0xA70, "SevenSixAxisMemory is an invalid size");
// This is nn::hid::detail::ConsoleSixAxisSensorSharedMemoryFormat
struct ConsoleSharedMemory {
u64_le sampling_number{};
u64 sampling_number{};
bool is_seven_six_axis_sensor_at_rest{};
f32 verticalization_error{};
Common::Vec3f gyro_bias{};
@@ -69,9 +76,7 @@ private:
Common::Quaternion<f32> quaternion;
};
using MotionArray =
std::array<std::unique_ptr<Input::MotionDevice>, Settings::NativeMotion::NUM_MOTIONS_HID>;
MotionArray motions;
Core::HID::EmulatedConsole* console;
u8* transfer_memory = nullptr;
bool is_transfer_memory_set = false;
ConsoleSharedMemory console_six_axis{};

View File

@@ -11,7 +11,7 @@ ControllerBase::~ControllerBase() = default;
void ControllerBase::ActivateController() {
if (is_activated) {
OnRelease();
return;
}
is_activated = true;
OnInit();

View File

@@ -35,9 +35,6 @@ public:
virtual void OnMotionUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) {}
// Called when input devices should be loaded
virtual void OnLoadInputDevices() = 0;
void ActivateController();
void DeactivateController();
@@ -47,14 +44,6 @@ public:
protected:
bool is_activated{false};
struct CommonHeader {
s64_le timestamp;
s64_le total_entry_count;
s64_le last_entry_index;
s64_le entry_count;
};
static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size");
Core::System& system;
};
} // namespace Service::HID

View File

@@ -5,16 +5,20 @@
#include <cstring>
#include "common/common_types.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
#include "core/hid/hid_types.h"
#include "core/hle/service/hid/controllers/debug_pad.h"
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x00000;
constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
[[maybe_unused]] constexpr s32 HID_JOYSTICK_MIN = -0x7fff;
enum class JoystickId : std::size_t { Joystick_Left, Joystick_Right };
Controller_DebugPad::Controller_DebugPad(Core::System& system_) : ControllerBase{system_} {
controller = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Other);
}
Controller_DebugPad::Controller_DebugPad(Core::System& system_) : ControllerBase{system_} {}
Controller_DebugPad::~Controller_DebugPad() = default;
void Controller_DebugPad::OnInit() {}
@@ -23,63 +27,29 @@ void Controller_DebugPad::OnRelease() {}
void Controller_DebugPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) {
shared_memory.header.timestamp = core_timing.GetCPUTicks();
shared_memory.header.total_entry_count = 17;
if (!IsControllerActivated()) {
shared_memory.header.entry_count = 0;
shared_memory.header.last_entry_index = 0;
debug_pad_lifo.buffer_count = 0;
debug_pad_lifo.buffer_tail = 0;
std::memcpy(data + SHARED_MEMORY_OFFSET, &debug_pad_lifo, sizeof(debug_pad_lifo));
return;
}
shared_memory.header.entry_count = 16;
const auto& last_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
auto& cur_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
const auto& last_entry = debug_pad_lifo.ReadCurrentEntry().state;
next_state.sampling_number = last_entry.sampling_number + 1;
if (Settings::values.debug_pad_enabled) {
cur_entry.attribute.connected.Assign(1);
auto& pad = cur_entry.pad_state;
next_state.attribute.connected.Assign(1);
using namespace Settings::NativeButton;
pad.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
pad.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
pad.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
pad.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
pad.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
pad.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
pad.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus());
pad.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus());
pad.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus());
pad.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus());
pad.d_left.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus());
pad.d_up.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus());
pad.d_right.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus());
pad.d_down.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus());
const auto& button_state = controller->GetDebugPadButtons();
const auto& stick_state = controller->GetSticks();
const auto [stick_l_x_f, stick_l_y_f] =
analogs[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus();
const auto [stick_r_x_f, stick_r_y_f] =
analogs[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus();
cur_entry.l_stick.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX);
cur_entry.l_stick.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX);
cur_entry.r_stick.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX);
cur_entry.r_stick.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX);
next_state.pad_state = button_state;
next_state.l_stick = stick_state.left;
next_state.r_stick = stick_state.right;
}
std::memcpy(data, &shared_memory, sizeof(SharedMemory));
debug_pad_lifo.WriteNextEntry(next_state);
std::memcpy(data + SHARED_MEMORY_OFFSET, &debug_pad_lifo, sizeof(debug_pad_lifo));
}
void Controller_DebugPad::OnLoadInputDevices() {
std::transform(Settings::values.debug_pad_buttons.begin(),
Settings::values.debug_pad_buttons.begin() +
Settings::NativeButton::NUM_BUTTONS_HID,
buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
std::transform(Settings::values.debug_pad_analogs.begin(),
Settings::values.debug_pad_analogs.end(), analogs.begin(),
Input::CreateDevice<Input::AnalogDevice>);
}
} // namespace Service::HID

View File

@@ -10,8 +10,14 @@
#include "common/common_types.h"
#include "common/settings.h"
#include "common/swap.h"
#include "core/frontend/input.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/ring_lifo.h"
namespace Core::HID {
class EmulatedController;
struct DebugPadButton;
struct AnalogStickState;
} // namespace Core::HID
namespace Service::HID {
class Controller_DebugPad final : public ControllerBase {
@@ -28,66 +34,31 @@ public:
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
private:
struct AnalogStick {
s32_le x;
s32_le y;
};
static_assert(sizeof(AnalogStick) == 0x8);
struct PadState {
// This is nn::hid::DebugPadAttribute
struct DebugPadAttribute {
union {
u32_le raw{};
BitField<0, 1, u32> a;
BitField<1, 1, u32> b;
BitField<2, 1, u32> x;
BitField<3, 1, u32> y;
BitField<4, 1, u32> l;
BitField<5, 1, u32> r;
BitField<6, 1, u32> zl;
BitField<7, 1, u32> zr;
BitField<8, 1, u32> plus;
BitField<9, 1, u32> minus;
BitField<10, 1, u32> d_left;
BitField<11, 1, u32> d_up;
BitField<12, 1, u32> d_right;
BitField<13, 1, u32> d_down;
};
};
static_assert(sizeof(PadState) == 0x4, "PadState is an invalid size");
struct Attributes {
union {
u32_le raw{};
u32 raw{};
BitField<0, 1, u32> connected;
};
};
static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size");
static_assert(sizeof(DebugPadAttribute) == 0x4, "DebugPadAttribute is an invalid size");
struct PadStates {
s64_le sampling_number;
s64_le sampling_number2;
Attributes attribute;
PadState pad_state;
AnalogStick r_stick;
AnalogStick l_stick;
// This is nn::hid::DebugPadState
struct DebugPadState {
s64 sampling_number;
DebugPadAttribute attribute;
Core::HID::DebugPadButton pad_state;
Core::HID::AnalogStickState r_stick;
Core::HID::AnalogStickState l_stick;
};
static_assert(sizeof(PadStates) == 0x28, "PadStates is an invalid state");
static_assert(sizeof(DebugPadState) == 0x20, "DebugPadState is an invalid state");
struct SharedMemory {
CommonHeader header;
std::array<PadStates, 17> pad_states;
INSERT_PADDING_BYTES(0x138);
};
static_assert(sizeof(SharedMemory) == 0x400, "SharedMemory is an invalid size");
SharedMemory shared_memory{};
// This is nn::hid::detail::DebugPadLifo
Lifo<DebugPadState> debug_pad_lifo{};
static_assert(sizeof(debug_pad_lifo) == 0x2C8, "debug_pad_lifo is an invalid size");
DebugPadState next_state{};
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
buttons;
std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>
analogs;
Core::HID::EmulatedController* controller;
};
} // namespace Service::HID

View File

@@ -5,8 +5,10 @@
#include "common/logging/log.h"
#include "common/math_util.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/frontend/emu_window.h"
#include "core/hid/hid_core.h"
#include "core/hle/service/hid/controllers/gesture.h"
namespace Service::HID {
@@ -23,16 +25,14 @@ constexpr f32 Square(s32 num) {
return static_cast<f32>(num * num);
}
Controller_Gesture::Controller_Gesture(Core::System& system_) : ControllerBase(system_) {}
Controller_Gesture::Controller_Gesture(Core::System& system_) : ControllerBase(system_) {
console = system.HIDCore().GetEmulatedConsole();
}
Controller_Gesture::~Controller_Gesture() = default;
void Controller_Gesture::OnInit() {
for (std::size_t id = 0; id < MAX_FINGERS; ++id) {
mouse_finger_id[id] = MAX_POINTS;
keyboard_finger_id[id] = MAX_POINTS;
udp_finger_id[id] = MAX_POINTS;
}
shared_memory.header.entry_count = 0;
gesture_lifo.buffer_count = 0;
gesture_lifo.buffer_tail = 0;
force_update = true;
}
@@ -40,50 +40,38 @@ void Controller_Gesture::OnRelease() {}
void Controller_Gesture::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) {
shared_memory.header.timestamp = core_timing.GetCPUTicks();
shared_memory.header.total_entry_count = 17;
if (!IsControllerActivated()) {
shared_memory.header.entry_count = 0;
shared_memory.header.last_entry_index = 0;
gesture_lifo.buffer_count = 0;
gesture_lifo.buffer_tail = 0;
std::memcpy(data + SHARED_MEMORY_OFFSET, &gesture_lifo, sizeof(gesture_lifo));
return;
}
ReadTouchInput();
GestureProperties gesture = GetGestureProperties();
f32 time_difference = static_cast<f32>(shared_memory.header.timestamp - last_update_timestamp) /
(1000 * 1000 * 1000);
f32 time_difference =
static_cast<f32>(gesture_lifo.timestamp - last_update_timestamp) / (1000 * 1000 * 1000);
// Only update if necesary
if (!ShouldUpdateGesture(gesture, time_difference)) {
return;
}
last_update_timestamp = shared_memory.header.timestamp;
last_update_timestamp = gesture_lifo.timestamp;
UpdateGestureSharedMemory(data, size, gesture, time_difference);
}
void Controller_Gesture::ReadTouchInput() {
const Input::TouchStatus& mouse_status = touch_mouse_device->GetStatus();
const Input::TouchStatus& udp_status = touch_udp_device->GetStatus();
for (std::size_t id = 0; id < mouse_status.size(); ++id) {
mouse_finger_id[id] = UpdateTouchInputEvent(mouse_status[id], mouse_finger_id[id]);
udp_finger_id[id] = UpdateTouchInputEvent(udp_status[id], udp_finger_id[id]);
}
if (Settings::values.use_touch_from_button) {
const Input::TouchStatus& keyboard_status = touch_btn_device->GetStatus();
for (std::size_t id = 0; id < mouse_status.size(); ++id) {
keyboard_finger_id[id] =
UpdateTouchInputEvent(keyboard_status[id], keyboard_finger_id[id]);
}
const auto touch_status = console->GetTouch();
for (std::size_t id = 0; id < fingers.size(); ++id) {
fingers[id] = touch_status[id];
}
}
bool Controller_Gesture::ShouldUpdateGesture(const GestureProperties& gesture,
f32 time_difference) {
const auto& last_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index];
const auto& last_entry = GetLastGestureEntry();
if (force_update) {
force_update = false;
return true;
@@ -97,7 +85,7 @@ bool Controller_Gesture::ShouldUpdateGesture(const GestureProperties& gesture,
}
// Update on press and hold event after 0.5 seconds
if (last_entry.type == TouchType::Touch && last_entry.point_count == 1 &&
if (last_entry.type == GestureType::Touch && last_entry.point_count == 1 &&
time_difference > press_delay) {
return enable_press_and_tap;
}
@@ -108,27 +96,19 @@ bool Controller_Gesture::ShouldUpdateGesture(const GestureProperties& gesture,
void Controller_Gesture::UpdateGestureSharedMemory(u8* data, std::size_t size,
GestureProperties& gesture,
f32 time_difference) {
TouchType type = TouchType::Idle;
Attribute attributes{};
GestureType type = GestureType::Idle;
GestureAttribute attributes{};
const auto& last_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index];
shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
auto& cur_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index];
const auto& last_entry = gesture_lifo.ReadCurrentEntry().state;
if (shared_memory.header.entry_count < 16) {
shared_memory.header.entry_count++;
}
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
// Reset values to default
cur_entry.delta = {};
cur_entry.vel_x = 0;
cur_entry.vel_y = 0;
cur_entry.direction = Direction::None;
cur_entry.rotation_angle = 0;
cur_entry.scale = 0;
// Reset next state to default
next_state.sampling_number = last_entry.sampling_number + 1;
next_state.delta = {};
next_state.vel_x = 0;
next_state.vel_y = 0;
next_state.direction = GestureDirection::None;
next_state.rotation_angle = 0;
next_state.scale = 0;
if (gesture.active_points > 0) {
if (last_gesture.active_points == 0) {
@@ -141,46 +121,47 @@ void Controller_Gesture::UpdateGestureSharedMemory(u8* data, std::size_t size,
}
// Apply attributes
cur_entry.detection_count = gesture.detection_count;
cur_entry.type = type;
cur_entry.attributes = attributes;
cur_entry.pos = gesture.mid_point;
cur_entry.point_count = static_cast<s32>(gesture.active_points);
cur_entry.points = gesture.points;
next_state.detection_count = gesture.detection_count;
next_state.type = type;
next_state.attributes = attributes;
next_state.pos = gesture.mid_point;
next_state.point_count = static_cast<s32>(gesture.active_points);
next_state.points = gesture.points;
last_gesture = gesture;
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
gesture_lifo.WriteNextEntry(next_state);
std::memcpy(data + SHARED_MEMORY_OFFSET, &gesture_lifo, sizeof(gesture_lifo));
}
void Controller_Gesture::NewGesture(GestureProperties& gesture, TouchType& type,
Attribute& attributes) {
void Controller_Gesture::NewGesture(GestureProperties& gesture, GestureType& type,
GestureAttribute& attributes) {
const auto& last_entry = GetLastGestureEntry();
gesture.detection_count++;
type = TouchType::Touch;
type = GestureType::Touch;
// New touch after cancel is not considered new
if (last_entry.type != TouchType::Cancel) {
if (last_entry.type != GestureType::Cancel) {
attributes.is_new_touch.Assign(1);
enable_press_and_tap = true;
}
}
void Controller_Gesture::UpdateExistingGesture(GestureProperties& gesture, TouchType& type,
void Controller_Gesture::UpdateExistingGesture(GestureProperties& gesture, GestureType& type,
f32 time_difference) {
const auto& last_entry = GetLastGestureEntry();
// Promote to pan type if touch moved
for (size_t id = 0; id < MAX_POINTS; id++) {
if (gesture.points[id] != last_gesture.points[id]) {
type = TouchType::Pan;
type = GestureType::Pan;
break;
}
}
// Number of fingers changed cancel the last event and clear data
if (gesture.active_points != last_gesture.active_points) {
type = TouchType::Cancel;
type = GestureType::Cancel;
enable_press_and_tap = false;
gesture.active_points = 0;
gesture.mid_point = {};
@@ -189,41 +170,41 @@ void Controller_Gesture::UpdateExistingGesture(GestureProperties& gesture, Touch
}
// Calculate extra parameters of panning
if (type == TouchType::Pan) {
if (type == GestureType::Pan) {
UpdatePanEvent(gesture, last_gesture, type, time_difference);
return;
}
// Promote to press type
if (last_entry.type == TouchType::Touch) {
type = TouchType::Press;
if (last_entry.type == GestureType::Touch) {
type = GestureType::Press;
}
}
void Controller_Gesture::EndGesture(GestureProperties& gesture,
GestureProperties& last_gesture_props, TouchType& type,
Attribute& attributes, f32 time_difference) {
GestureProperties& last_gesture_props, GestureType& type,
GestureAttribute& attributes, f32 time_difference) {
const auto& last_entry = GetLastGestureEntry();
if (last_gesture_props.active_points != 0) {
switch (last_entry.type) {
case TouchType::Touch:
case GestureType::Touch:
if (enable_press_and_tap) {
SetTapEvent(gesture, last_gesture_props, type, attributes);
return;
}
type = TouchType::Cancel;
type = GestureType::Cancel;
force_update = true;
break;
case TouchType::Press:
case TouchType::Tap:
case TouchType::Swipe:
case TouchType::Pinch:
case TouchType::Rotate:
type = TouchType::Complete;
case GestureType::Press:
case GestureType::Tap:
case GestureType::Swipe:
case GestureType::Pinch:
case GestureType::Rotate:
type = GestureType::Complete;
force_update = true;
break;
case TouchType::Pan:
case GestureType::Pan:
EndPanEvent(gesture, last_gesture_props, type, time_difference);
break;
default:
@@ -231,15 +212,15 @@ void Controller_Gesture::EndGesture(GestureProperties& gesture,
}
return;
}
if (last_entry.type == TouchType::Complete || last_entry.type == TouchType::Cancel) {
if (last_entry.type == GestureType::Complete || last_entry.type == GestureType::Cancel) {
gesture.detection_count++;
}
}
void Controller_Gesture::SetTapEvent(GestureProperties& gesture,
GestureProperties& last_gesture_props, TouchType& type,
Attribute& attributes) {
type = TouchType::Tap;
GestureProperties& last_gesture_props, GestureType& type,
GestureAttribute& attributes) {
type = GestureType::Tap;
gesture = last_gesture_props;
force_update = true;
f32 tap_time_difference =
@@ -251,44 +232,42 @@ void Controller_Gesture::SetTapEvent(GestureProperties& gesture,
}
void Controller_Gesture::UpdatePanEvent(GestureProperties& gesture,
GestureProperties& last_gesture_props, TouchType& type,
GestureProperties& last_gesture_props, GestureType& type,
f32 time_difference) {
auto& cur_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index];
const auto& last_entry = GetLastGestureEntry();
cur_entry.delta = gesture.mid_point - last_entry.pos;
cur_entry.vel_x = static_cast<f32>(cur_entry.delta.x) / time_difference;
cur_entry.vel_y = static_cast<f32>(cur_entry.delta.y) / time_difference;
next_state.delta = gesture.mid_point - last_entry.pos;
next_state.vel_x = static_cast<f32>(next_state.delta.x) / time_difference;
next_state.vel_y = static_cast<f32>(next_state.delta.y) / time_difference;
last_pan_time_difference = time_difference;
// Promote to pinch type
if (std::abs(gesture.average_distance - last_gesture_props.average_distance) >
pinch_threshold) {
type = TouchType::Pinch;
cur_entry.scale = gesture.average_distance / last_gesture_props.average_distance;
type = GestureType::Pinch;
next_state.scale = gesture.average_distance / last_gesture_props.average_distance;
}
const f32 angle_between_two_lines = std::atan((gesture.angle - last_gesture_props.angle) /
(1 + (gesture.angle * last_gesture_props.angle)));
// Promote to rotate type
if (std::abs(angle_between_two_lines) > angle_threshold) {
type = TouchType::Rotate;
cur_entry.scale = 0;
cur_entry.rotation_angle = angle_between_two_lines * 180.0f / Common::PI;
type = GestureType::Rotate;
next_state.scale = 0;
next_state.rotation_angle = angle_between_two_lines * 180.0f / Common::PI;
}
}
void Controller_Gesture::EndPanEvent(GestureProperties& gesture,
GestureProperties& last_gesture_props, TouchType& type,
GestureProperties& last_gesture_props, GestureType& type,
f32 time_difference) {
auto& cur_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index];
const auto& last_entry = GetLastGestureEntry();
cur_entry.vel_x =
next_state.vel_x =
static_cast<f32>(last_entry.delta.x) / (last_pan_time_difference + time_difference);
cur_entry.vel_y =
next_state.vel_y =
static_cast<f32>(last_entry.delta.y) / (last_pan_time_difference + time_difference);
const f32 curr_vel =
std::sqrt((cur_entry.vel_x * cur_entry.vel_x) + (cur_entry.vel_y * cur_entry.vel_y));
std::sqrt((next_state.vel_x * next_state.vel_x) + (next_state.vel_y * next_state.vel_y));
// Set swipe event with parameters
if (curr_vel > swipe_threshold) {
@@ -297,105 +276,50 @@ void Controller_Gesture::EndPanEvent(GestureProperties& gesture,
}
// End panning without swipe
type = TouchType::Complete;
cur_entry.vel_x = 0;
cur_entry.vel_y = 0;
type = GestureType::Complete;
next_state.vel_x = 0;
next_state.vel_y = 0;
force_update = true;
}
void Controller_Gesture::SetSwipeEvent(GestureProperties& gesture,
GestureProperties& last_gesture_props, TouchType& type) {
auto& cur_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index];
GestureProperties& last_gesture_props, GestureType& type) {
const auto& last_entry = GetLastGestureEntry();
type = TouchType::Swipe;
type = GestureType::Swipe;
gesture = last_gesture_props;
force_update = true;
cur_entry.delta = last_entry.delta;
next_state.delta = last_entry.delta;
if (std::abs(cur_entry.delta.x) > std::abs(cur_entry.delta.y)) {
if (cur_entry.delta.x > 0) {
cur_entry.direction = Direction::Right;
if (std::abs(next_state.delta.x) > std::abs(next_state.delta.y)) {
if (next_state.delta.x > 0) {
next_state.direction = GestureDirection::Right;
return;
}
cur_entry.direction = Direction::Left;
next_state.direction = GestureDirection::Left;
return;
}
if (cur_entry.delta.y > 0) {
cur_entry.direction = Direction::Down;
if (next_state.delta.y > 0) {
next_state.direction = GestureDirection::Down;
return;
}
cur_entry.direction = Direction::Up;
}
void Controller_Gesture::OnLoadInputDevices() {
touch_mouse_device = Input::CreateDevice<Input::TouchDevice>("engine:emu_window");
touch_udp_device = Input::CreateDevice<Input::TouchDevice>("engine:cemuhookudp");
touch_btn_device = Input::CreateDevice<Input::TouchDevice>("engine:touch_from_button");
}
std::optional<std::size_t> Controller_Gesture::GetUnusedFingerID() const {
// Dont assign any touch input to a point if disabled
if (!Settings::values.touchscreen.enabled) {
return std::nullopt;
}
std::size_t first_free_id = 0;
while (first_free_id < MAX_POINTS) {
if (!fingers[first_free_id].pressed) {
return first_free_id;
} else {
first_free_id++;
}
}
return std::nullopt;
}
Controller_Gesture::GestureState& Controller_Gesture::GetLastGestureEntry() {
return shared_memory.gesture_states[(shared_memory.header.last_entry_index + 16) % 17];
next_state.direction = GestureDirection::Up;
}
const Controller_Gesture::GestureState& Controller_Gesture::GetLastGestureEntry() const {
return shared_memory.gesture_states[(shared_memory.header.last_entry_index + 16) % 17];
}
std::size_t Controller_Gesture::UpdateTouchInputEvent(
const std::tuple<float, float, bool>& touch_input, std::size_t finger_id) {
const auto& [x, y, pressed] = touch_input;
if (finger_id > MAX_POINTS) {
LOG_ERROR(Service_HID, "Invalid finger id {}", finger_id);
return MAX_POINTS;
}
if (pressed) {
if (finger_id == MAX_POINTS) {
const auto first_free_id = GetUnusedFingerID();
if (!first_free_id) {
// Invalid finger id do nothing
return MAX_POINTS;
}
finger_id = first_free_id.value();
fingers[finger_id].pressed = true;
}
fingers[finger_id].pos = {x, y};
return finger_id;
}
if (finger_id != MAX_POINTS) {
fingers[finger_id].pressed = false;
}
return MAX_POINTS;
return gesture_lifo.ReadCurrentEntry().state;
}
Controller_Gesture::GestureProperties Controller_Gesture::GetGestureProperties() {
GestureProperties gesture;
std::array<Finger, MAX_POINTS> active_fingers;
std::array<Core::HID::TouchFinger, MAX_POINTS> active_fingers;
const auto end_iter = std::copy_if(fingers.begin(), fingers.end(), active_fingers.begin(),
[](const auto& finger) { return finger.pressed; });
gesture.active_points =
static_cast<std::size_t>(std::distance(active_fingers.begin(), end_iter));
for (size_t id = 0; id < gesture.active_points; ++id) {
const auto& [active_x, active_y] = active_fingers[id].pos;
const auto& [active_x, active_y] = active_fingers[id].position;
gesture.points[id] = {
.x = static_cast<s32>(active_x * Layout::ScreenUndocked::Width),
.y = static_cast<s32>(active_y * Layout::ScreenUndocked::Height),

View File

@@ -8,8 +8,9 @@
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/point.h"
#include "core/frontend/input.h"
#include "core/hid/emulated_console.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/ring_lifo.h"
namespace Service::HID {
class Controller_Gesture final : public ControllerBase {
@@ -26,14 +27,12 @@ public:
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
private:
static constexpr size_t MAX_FINGERS = 16;
static constexpr size_t MAX_POINTS = 4;
enum class TouchType : u32 {
// This is nn::hid::GestureType
enum class GestureType : u32 {
Idle, // Nothing touching the screen
Complete, // Set at the end of a touch event
Cancel, // Set when the number of fingers change
@@ -46,7 +45,8 @@ private:
Rotate, // All points rotating from the midpoint
};
enum class Direction : u32 {
// This is nn::hid::GestureDirection
enum class GestureDirection : u32 {
None,
Left,
Up,
@@ -54,51 +54,41 @@ private:
Down,
};
struct Attribute {
// This is nn::hid::GestureAttribute
struct GestureAttribute {
union {
u32_le raw{};
u32 raw{};
BitField<4, 1, u32> is_new_touch;
BitField<8, 1, u32> is_double_tap;
};
};
static_assert(sizeof(Attribute) == 4, "Attribute is an invalid size");
static_assert(sizeof(GestureAttribute) == 4, "GestureAttribute is an invalid size");
// This is nn::hid::GestureState
struct GestureState {
s64_le sampling_number;
s64_le sampling_number2;
s64_le detection_count;
TouchType type;
Direction direction;
Common::Point<s32_le> pos;
Common::Point<s32_le> delta;
s64 sampling_number;
s64 detection_count;
GestureType type;
GestureDirection direction;
Common::Point<s32> pos;
Common::Point<s32> delta;
f32 vel_x;
f32 vel_y;
Attribute attributes;
GestureAttribute attributes;
f32 scale;
f32 rotation_angle;
s32_le point_count;
std::array<Common::Point<s32_le>, 4> points;
};
static_assert(sizeof(GestureState) == 0x68, "GestureState is an invalid size");
struct SharedMemory {
CommonHeader header;
std::array<GestureState, 17> gesture_states;
};
static_assert(sizeof(SharedMemory) == 0x708, "SharedMemory is an invalid size");
struct Finger {
Common::Point<f32> pos{};
bool pressed{};
s32 point_count;
std::array<Common::Point<s32>, 4> points;
};
static_assert(sizeof(GestureState) == 0x60, "GestureState is an invalid size");
struct GestureProperties {
std::array<Common::Point<s32_le>, MAX_POINTS> points{};
std::array<Common::Point<s32>, MAX_POINTS> points{};
std::size_t active_points{};
Common::Point<s32_le> mid_point{};
s64_le detection_count{};
u64_le delta_time{};
Common::Point<s32> mid_point{};
s64 detection_count{};
u64 delta_time{};
f32 average_distance{};
f32 angle{};
};
@@ -114,61 +104,48 @@ private:
f32 time_difference);
// Initializes new gesture
void NewGesture(GestureProperties& gesture, TouchType& type, Attribute& attributes);
void NewGesture(GestureProperties& gesture, GestureType& type, GestureAttribute& attributes);
// Updates existing gesture state
void UpdateExistingGesture(GestureProperties& gesture, TouchType& type, f32 time_difference);
void UpdateExistingGesture(GestureProperties& gesture, GestureType& type, f32 time_difference);
// Terminates exiting gesture
void EndGesture(GestureProperties& gesture, GestureProperties& last_gesture_props,
TouchType& type, Attribute& attributes, f32 time_difference);
GestureType& type, GestureAttribute& attributes, f32 time_difference);
// Set current event to a tap event
void SetTapEvent(GestureProperties& gesture, GestureProperties& last_gesture_props,
TouchType& type, Attribute& attributes);
GestureType& type, GestureAttribute& attributes);
// Calculates and set the extra parameters related to a pan event
void UpdatePanEvent(GestureProperties& gesture, GestureProperties& last_gesture_props,
TouchType& type, f32 time_difference);
GestureType& type, f32 time_difference);
// Terminates the pan event
void EndPanEvent(GestureProperties& gesture, GestureProperties& last_gesture_props,
TouchType& type, f32 time_difference);
GestureType& type, f32 time_difference);
// Set current event to a swipe event
void SetSwipeEvent(GestureProperties& gesture, GestureProperties& last_gesture_props,
TouchType& type);
// Returns an unused finger id, if there is no fingers available std::nullopt is returned.
[[nodiscard]] std::optional<size_t> GetUnusedFingerID() const;
GestureType& type);
// Retrieves the last gesture entry, as indicated by shared memory indices.
[[nodiscard]] GestureState& GetLastGestureEntry();
[[nodiscard]] const GestureState& GetLastGestureEntry() const;
/**
* If the touch is new it tries to assign a new finger id, if there is no fingers available no
* changes will be made. Updates the coordinates if the finger id it's already set. If the touch
* ends delays the output by one frame to set the end_touch flag before finally freeing the
* finger id
*/
size_t UpdateTouchInputEvent(const std::tuple<float, float, bool>& touch_input,
size_t finger_id);
// Returns the average distance, angle and middle point of the active fingers
GestureProperties GetGestureProperties();
SharedMemory shared_memory{};
std::unique_ptr<Input::TouchDevice> touch_mouse_device;
std::unique_ptr<Input::TouchDevice> touch_udp_device;
std::unique_ptr<Input::TouchDevice> touch_btn_device;
std::array<size_t, MAX_FINGERS> mouse_finger_id{};
std::array<size_t, MAX_FINGERS> keyboard_finger_id{};
std::array<size_t, MAX_FINGERS> udp_finger_id{};
std::array<Finger, MAX_POINTS> fingers{};
// This is nn::hid::detail::GestureLifo
Lifo<GestureState> gesture_lifo{};
static_assert(sizeof(gesture_lifo) == 0x708, "gesture_lifo is an invalid size");
GestureState next_state{};
Core::HID::EmulatedConsole* console;
std::array<Core::HID::TouchFinger, MAX_POINTS> fingers{};
GestureProperties last_gesture{};
s64_le last_update_timestamp{};
s64_le last_tap_timestamp{};
s64 last_update_timestamp{};
s64 last_tap_timestamp{};
f32 last_pan_time_difference{};
bool force_update{false};
bool enable_press_and_tap{false};

View File

@@ -5,14 +5,19 @@
#include <cstring>
#include "common/common_types.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hid/emulated_devices.h"
#include "core/hid/hid_core.h"
#include "core/hle/service/hid/controllers/keyboard.h"
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3800;
constexpr u8 KEYS_PER_BYTE = 8;
Controller_Keyboard::Controller_Keyboard(Core::System& system_) : ControllerBase{system_} {}
Controller_Keyboard::Controller_Keyboard(Core::System& system_) : ControllerBase{system_} {
emulated_devices = system.HIDCore().GetEmulatedDevices();
}
Controller_Keyboard::~Controller_Keyboard() = default;
void Controller_Keyboard::OnInit() {}
@@ -21,51 +26,26 @@ void Controller_Keyboard::OnRelease() {}
void Controller_Keyboard::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) {
shared_memory.header.timestamp = core_timing.GetCPUTicks();
shared_memory.header.total_entry_count = 17;
if (!IsControllerActivated()) {
shared_memory.header.entry_count = 0;
shared_memory.header.last_entry_index = 0;
keyboard_lifo.buffer_count = 0;
keyboard_lifo.buffer_tail = 0;
std::memcpy(data + SHARED_MEMORY_OFFSET, &keyboard_lifo, sizeof(keyboard_lifo));
return;
}
shared_memory.header.entry_count = 16;
const auto& last_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
auto& cur_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
const auto& last_entry = keyboard_lifo.ReadCurrentEntry().state;
next_state.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
cur_entry.key.fill(0);
if (Settings::values.keyboard_enabled) {
for (std::size_t i = 0; i < keyboard_keys.size(); ++i) {
auto& entry = cur_entry.key[i / KEYS_PER_BYTE];
entry = static_cast<u8>(entry | (keyboard_keys[i]->GetStatus() << (i % KEYS_PER_BYTE)));
}
const auto& keyboard_state = emulated_devices->GetKeyboard();
const auto& keyboard_modifier_state = emulated_devices->GetKeyboardModifier();
using namespace Settings::NativeKeyboard;
// TODO: Assign the correct key to all modifiers
cur_entry.modifier.control.Assign(keyboard_mods[LeftControl]->GetStatus());
cur_entry.modifier.shift.Assign(keyboard_mods[LeftShift]->GetStatus());
cur_entry.modifier.left_alt.Assign(keyboard_mods[LeftAlt]->GetStatus());
cur_entry.modifier.right_alt.Assign(keyboard_mods[RightAlt]->GetStatus());
cur_entry.modifier.gui.Assign(0);
cur_entry.modifier.caps_lock.Assign(keyboard_mods[CapsLock]->GetStatus());
cur_entry.modifier.scroll_lock.Assign(keyboard_mods[ScrollLock]->GetStatus());
cur_entry.modifier.num_lock.Assign(keyboard_mods[NumLock]->GetStatus());
cur_entry.modifier.katakana.Assign(0);
cur_entry.modifier.hiragana.Assign(0);
next_state.key = keyboard_state;
next_state.modifier = keyboard_modifier_state;
}
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
keyboard_lifo.WriteNextEntry(next_state);
std::memcpy(data + SHARED_MEMORY_OFFSET, &keyboard_lifo, sizeof(keyboard_lifo));
}
void Controller_Keyboard::OnLoadInputDevices() {
std::transform(Settings::values.keyboard_keys.begin(), Settings::values.keyboard_keys.end(),
keyboard_keys.begin(), Input::CreateDevice<Input::ButtonDevice>);
std::transform(Settings::values.keyboard_mods.begin(), Settings::values.keyboard_mods.end(),
keyboard_mods.begin(), Input::CreateDevice<Input::ButtonDevice>);
}
} // namespace Service::HID

View File

@@ -10,8 +10,14 @@
#include "common/common_types.h"
#include "common/settings.h"
#include "common/swap.h"
#include "core/frontend/input.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/ring_lifo.h"
namespace Core::HID {
class EmulatedDevices;
struct KeyboardModifier;
struct KeyboardKey;
} // namespace Core::HID
namespace Service::HID {
class Controller_Keyboard final : public ControllerBase {
@@ -28,47 +34,20 @@ public:
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
private:
struct Modifiers {
union {
u32_le raw{};
BitField<0, 1, u32> control;
BitField<1, 1, u32> shift;
BitField<2, 1, u32> left_alt;
BitField<3, 1, u32> right_alt;
BitField<4, 1, u32> gui;
BitField<8, 1, u32> caps_lock;
BitField<9, 1, u32> scroll_lock;
BitField<10, 1, u32> num_lock;
BitField<11, 1, u32> katakana;
BitField<12, 1, u32> hiragana;
};
};
static_assert(sizeof(Modifiers) == 0x4, "Modifiers is an invalid size");
// This is nn::hid::detail::KeyboardState
struct KeyboardState {
s64_le sampling_number;
s64_le sampling_number2;
Modifiers modifier;
std::array<u8, 32> key;
s64 sampling_number;
Core::HID::KeyboardModifier modifier;
Core::HID::KeyboardKey key;
};
static_assert(sizeof(KeyboardState) == 0x38, "KeyboardState is an invalid size");
static_assert(sizeof(KeyboardState) == 0x30, "KeyboardState is an invalid size");
struct SharedMemory {
CommonHeader header;
std::array<KeyboardState, 17> pad_states;
INSERT_PADDING_BYTES(0x28);
};
static_assert(sizeof(SharedMemory) == 0x400, "SharedMemory is an invalid size");
SharedMemory shared_memory{};
// This is nn::hid::detail::KeyboardLifo
Lifo<KeyboardState> keyboard_lifo{};
static_assert(sizeof(keyboard_lifo) == 0x3D8, "keyboard_lifo is an invalid size");
KeyboardState next_state{};
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeKeyboard::NumKeyboardKeys>
keyboard_keys;
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeKeyboard::NumKeyboardMods>
keyboard_mods;
Core::HID::EmulatedDevices* emulated_devices;
};
} // namespace Service::HID

View File

@@ -4,14 +4,20 @@
#include <cstring>
#include "common/common_types.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/frontend/emu_window.h"
#include "core/hid/emulated_devices.h"
#include "core/hid/hid_core.h"
#include "core/hle/service/hid/controllers/mouse.h"
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3400;
Controller_Mouse::Controller_Mouse(Core::System& system_) : ControllerBase{system_} {}
Controller_Mouse::Controller_Mouse(Core::System& system_) : ControllerBase{system_} {
emulated_devices = system.HIDCore().GetEmulatedDevices();
}
Controller_Mouse::~Controller_Mouse() = default;
void Controller_Mouse::OnInit() {}
@@ -19,50 +25,33 @@ void Controller_Mouse::OnRelease() {}
void Controller_Mouse::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) {
shared_memory.header.timestamp = core_timing.GetCPUTicks();
shared_memory.header.total_entry_count = 17;
if (!IsControllerActivated()) {
shared_memory.header.entry_count = 0;
shared_memory.header.last_entry_index = 0;
mouse_lifo.buffer_count = 0;
mouse_lifo.buffer_tail = 0;
std::memcpy(data + SHARED_MEMORY_OFFSET, &mouse_lifo, sizeof(mouse_lifo));
return;
}
shared_memory.header.entry_count = 16;
auto& last_entry = shared_memory.mouse_states[shared_memory.header.last_entry_index];
shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
auto& cur_entry = shared_memory.mouse_states[shared_memory.header.last_entry_index];
const auto& last_entry = mouse_lifo.ReadCurrentEntry().state;
next_state.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
cur_entry.attribute.raw = 0;
next_state.attribute.raw = 0;
if (Settings::values.mouse_enabled) {
const auto [px, py, sx, sy] = mouse_device->GetStatus();
const auto x = static_cast<s32>(px * Layout::ScreenUndocked::Width);
const auto y = static_cast<s32>(py * Layout::ScreenUndocked::Height);
cur_entry.x = x;
cur_entry.y = y;
cur_entry.delta_x = x - last_entry.x;
cur_entry.delta_y = y - last_entry.y;
cur_entry.mouse_wheel_x = sx;
cur_entry.mouse_wheel_y = sy;
cur_entry.attribute.is_connected.Assign(1);
const auto& mouse_button_state = emulated_devices->GetMouseButtons();
const auto& mouse_position_state = emulated_devices->GetMousePosition();
next_state.attribute.is_connected.Assign(1);
next_state.x = mouse_position_state.x;
next_state.y = mouse_position_state.y;
next_state.delta_x = next_state.x - last_entry.x;
next_state.delta_y = next_state.y - last_entry.y;
next_state.delta_wheel_x = mouse_position_state.delta_wheel_x;
next_state.delta_wheel_y = mouse_position_state.delta_wheel_y;
using namespace Settings::NativeMouseButton;
cur_entry.button.left.Assign(mouse_button_devices[Left]->GetStatus());
cur_entry.button.right.Assign(mouse_button_devices[Right]->GetStatus());
cur_entry.button.middle.Assign(mouse_button_devices[Middle]->GetStatus());
cur_entry.button.forward.Assign(mouse_button_devices[Forward]->GetStatus());
cur_entry.button.back.Assign(mouse_button_devices[Back]->GetStatus());
next_state.button = mouse_button_state;
}
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
mouse_lifo.WriteNextEntry(next_state);
std::memcpy(data + SHARED_MEMORY_OFFSET, &mouse_lifo, sizeof(mouse_lifo));
}
void Controller_Mouse::OnLoadInputDevices() {
mouse_device = Input::CreateDevice<Input::MouseDevice>(Settings::values.mouse_device);
std::transform(Settings::values.mouse_buttons.begin(), Settings::values.mouse_buttons.end(),
mouse_button_devices.begin(), Input::CreateDevice<Input::ButtonDevice>);
}
} // namespace Service::HID

View File

@@ -9,8 +9,13 @@
#include "common/common_types.h"
#include "common/settings.h"
#include "common/swap.h"
#include "core/frontend/input.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/ring_lifo.h"
namespace Core::HID {
class EmulatedDevices;
struct MouseState;
} // namespace Core::HID
namespace Service::HID {
class Controller_Mouse final : public ControllerBase {
@@ -27,53 +32,12 @@ public:
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
private:
struct Buttons {
union {
u32_le raw{};
BitField<0, 1, u32> left;
BitField<1, 1, u32> right;
BitField<2, 1, u32> middle;
BitField<3, 1, u32> forward;
BitField<4, 1, u32> back;
};
};
static_assert(sizeof(Buttons) == 0x4, "Buttons is an invalid size");
// This is nn::hid::detail::MouseLifo
Lifo<Core::HID::MouseState> mouse_lifo{};
static_assert(sizeof(mouse_lifo) == 0x350, "mouse_lifo is an invalid size");
Core::HID::MouseState next_state{};
struct Attributes {
union {
u32_le raw{};
BitField<0, 1, u32> transferable;
BitField<1, 1, u32> is_connected;
};
};
static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size");
struct MouseState {
s64_le sampling_number;
s64_le sampling_number2;
s32_le x;
s32_le y;
s32_le delta_x;
s32_le delta_y;
s32_le mouse_wheel_x;
s32_le mouse_wheel_y;
Buttons button;
Attributes attribute;
};
static_assert(sizeof(MouseState) == 0x30, "MouseState is an invalid size");
struct SharedMemory {
CommonHeader header;
std::array<MouseState, 17> mouse_states;
};
SharedMemory shared_memory{};
std::unique_ptr<Input::MouseDevice> mouse_device;
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeMouseButton::NumMouseButtons>
mouse_button_devices;
Core::HID::EmulatedDevices* emulated_devices;
};
} // namespace Service::HID

File diff suppressed because it is too large Load Diff

View File

@@ -12,8 +12,14 @@
#include "common/common_types.h"
#include "common/quaternion.h"
#include "common/settings.h"
#include "core/frontend/input.h"
#include "core/hid/hid_types.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/ring_lifo.h"
namespace Core::HID {
class EmulatedController;
enum class ControllerTriggerType;
} // namespace Core::HID
namespace Kernel {
class KEvent;
@@ -48,31 +54,6 @@ public:
void OnMotionUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
enum class NPadControllerType {
None,
ProController,
Handheld,
JoyDual,
JoyLeft,
JoyRight,
GameCube,
Pokeball,
};
enum class NpadType : u8 {
ProController = 3,
Handheld = 4,
JoyconDual = 5,
JoyconLeft = 6,
JoyconRight = 7,
GameCube = 8,
Pokeball = 9,
MaxNpadType = 10,
};
enum class DeviceIndex : u8 {
Left = 0,
Right = 1,
@@ -80,28 +61,33 @@ public:
MaxDeviceIndex = 3,
};
// This is nn::hid::GyroscopeZeroDriftMode
enum class GyroscopeZeroDriftMode : u32 {
Loose = 0,
Standard = 1,
Tight = 2,
};
enum class NpadHoldType : u64 {
// This is nn::hid::NpadJoyHoldType
enum class NpadJoyHoldType : u64 {
Vertical = 0,
Horizontal = 1,
};
enum class NpadAssignments : u32 {
// This is nn::hid::NpadJoyAssignmentMode
enum class NpadJoyAssignmentMode : u32 {
Dual = 0,
Single = 1,
};
// This is nn::hid::NpadHandheldActivationMode
enum class NpadHandheldActivationMode : u64 {
Dual = 0,
Single = 1,
None = 2,
};
// This is nn::hid::NpadCommunicationMode
enum class NpadCommunicationMode : u64 {
Mode_5ms = 0,
Mode_10ms = 1,
@@ -110,33 +96,14 @@ public:
};
struct DeviceHandle {
NpadType npad_type;
Core::HID::NpadType npad_type;
u8 npad_id;
DeviceIndex device_index;
INSERT_PADDING_BYTES_NOINIT(1);
};
static_assert(sizeof(DeviceHandle) == 4, "DeviceHandle is an invalid size");
struct NpadStyleSet {
union {
u32_le raw{};
BitField<0, 1, u32> fullkey;
BitField<1, 1, u32> handheld;
BitField<2, 1, u32> joycon_dual;
BitField<3, 1, u32> joycon_left;
BitField<4, 1, u32> joycon_right;
BitField<5, 1, u32> gamecube;
BitField<6, 1, u32> palma;
BitField<7, 1, u32> lark;
BitField<8, 1, u32> handheld_lark;
BitField<9, 1, u32> lucia;
BitField<29, 1, u32> system_ext;
BitField<30, 1, u32> system;
};
};
static_assert(sizeof(NpadStyleSet) == 4, "NpadStyleSet is an invalid size");
// This is nn::hid::VibrationValue
struct VibrationValue {
f32 amp_low;
f32 freq_low;
@@ -152,31 +119,15 @@ public:
.freq_high = 320.0f,
};
struct LedPattern {
explicit LedPattern(u64 light1, u64 light2, u64 light3, u64 light4) {
position1.Assign(light1);
position2.Assign(light2);
position3.Assign(light3);
position4.Assign(light4);
}
union {
u64 raw{};
BitField<0, 1, u64> position1;
BitField<1, 1, u64> position2;
BitField<2, 1, u64> position3;
BitField<3, 1, u64> position4;
};
};
void SetSupportedStyleSet(NpadStyleSet style_set);
NpadStyleSet GetSupportedStyleSet() const;
void SetSupportedStyleSet(Core::HID::NpadStyleTag style_set);
Core::HID::NpadStyleTag GetSupportedStyleSet() const;
void SetSupportedNpadIdTypes(u8* data, std::size_t length);
void GetSupportedNpadIdTypes(u32* data, std::size_t max_length);
std::size_t GetSupportedNpadIdTypesSize() const;
void SetHoldType(NpadHoldType joy_hold_type);
NpadHoldType GetHoldType() const;
void SetHoldType(NpadJoyHoldType joy_hold_type);
NpadJoyHoldType GetHoldType() const;
void SetNpadHandheldActivationMode(NpadHandheldActivationMode activation_mode);
NpadHandheldActivationMode GetNpadHandheldActivationMode() const;
@@ -184,7 +135,7 @@ public:
void SetNpadCommunicationMode(NpadCommunicationMode communication_mode_);
NpadCommunicationMode GetNpadCommunicationMode() const;
void SetNpadMode(u32 npad_id, NpadAssignments assignment_mode);
void SetNpadMode(u32 npad_id, NpadJoyAssignmentMode assignment_mode);
bool VibrateControllerAtIndex(std::size_t npad_index, std::size_t device_index,
const VibrationValue& vibration_value);
@@ -209,9 +160,9 @@ public:
void SignalStyleSetChangedEvent(u32 npad_id) const;
// Adds a new controller at an index.
void AddNewControllerAt(NPadControllerType controller, std::size_t npad_index);
void AddNewControllerAt(Core::HID::NpadType controller, std::size_t npad_index);
// Adds a new controller at an index with connection status.
void UpdateControllerAt(NPadControllerType controller, std::size_t npad_index, bool connected);
void UpdateControllerAt(Core::HID::NpadType controller, std::size_t npad_index, bool connected);
void DisconnectNpad(u32 npad_id);
void DisconnectNpadAtIndex(std::size_t index);
@@ -223,7 +174,7 @@ public:
void SetSixAxisFusionParameters(f32 parameter1, f32 parameter2);
std::pair<f32, f32> GetSixAxisFusionParameters();
void ResetSixAxisFusionParameters();
LedPattern GetLedPattern(u32 npad_id);
Core::HID::LedPattern GetLedPattern(u32 npad_id);
bool IsUnintendedHomeButtonInputProtectionEnabled(u32 npad_id) const;
void SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled, u32 npad_id);
void SetAnalogStickUseCenterClamp(bool use_center_clamp);
@@ -241,105 +192,39 @@ public:
// Specifically for cheat engine and other features.
u32 GetAndResetPressState();
static Controller_NPad::NPadControllerType MapSettingsTypeToNPad(Settings::ControllerType type);
static Settings::ControllerType MapNPadToSettingsType(Controller_NPad::NPadControllerType type);
static std::size_t NPadIdToIndex(u32 npad_id);
static u32 IndexToNPad(std::size_t index);
static bool IsNpadIdValid(u32 npad_id);
static bool IsDeviceHandleValid(const DeviceHandle& device_handle);
private:
struct CommonHeader {
s64_le timestamp;
s64_le total_entry_count;
s64_le last_entry_index;
s64_le entry_count;
};
static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size");
enum class ColorAttributes : u32_le {
// This is nn::hid::detail::ColorAttribute
enum class ColorAttribute : u32 {
Ok = 0,
ReadError = 1,
NoController = 2,
};
static_assert(sizeof(ColorAttributes) == 4, "ColorAttributes is an invalid size");
static_assert(sizeof(ColorAttribute) == 4, "ColorAttribute is an invalid size");
struct ControllerColor {
u32_le body;
u32_le button;
// This is nn::hid::detail::NpadFullKeyColorState
struct NpadFullKeyColorState {
ColorAttribute attribute;
Core::HID::NpadControllerColor fullkey;
};
static_assert(sizeof(ControllerColor) == 8, "ControllerColor is an invalid size");
static_assert(sizeof(NpadFullKeyColorState) == 0xC, "NpadFullKeyColorState is an invalid size");
struct FullKeyColor {
ColorAttributes attribute;
ControllerColor fullkey;
// This is nn::hid::detail::NpadJoyColorState
struct NpadJoyColorState {
ColorAttribute attribute;
Core::HID::NpadControllerColor left;
Core::HID::NpadControllerColor right;
};
static_assert(sizeof(FullKeyColor) == 0xC, "FullKeyColor is an invalid size");
static_assert(sizeof(NpadJoyColorState) == 0x14, "NpadJoyColorState is an invalid size");
struct JoyconColor {
ColorAttributes attribute;
ControllerColor left;
ControllerColor right;
};
static_assert(sizeof(JoyconColor) == 0x14, "JoyconColor is an invalid size");
struct ControllerPadState {
// This is nn::hid::NpadAttribute
struct NpadAttribute {
union {
u64_le raw{};
// Button states
BitField<0, 1, u64> a;
BitField<1, 1, u64> b;
BitField<2, 1, u64> x;
BitField<3, 1, u64> y;
BitField<4, 1, u64> l_stick;
BitField<5, 1, u64> r_stick;
BitField<6, 1, u64> l;
BitField<7, 1, u64> r;
BitField<8, 1, u64> zl;
BitField<9, 1, u64> zr;
BitField<10, 1, u64> plus;
BitField<11, 1, u64> minus;
// D-Pad
BitField<12, 1, u64> d_left;
BitField<13, 1, u64> d_up;
BitField<14, 1, u64> d_right;
BitField<15, 1, u64> d_down;
// Left JoyStick
BitField<16, 1, u64> l_stick_left;
BitField<17, 1, u64> l_stick_up;
BitField<18, 1, u64> l_stick_right;
BitField<19, 1, u64> l_stick_down;
// Right JoyStick
BitField<20, 1, u64> r_stick_left;
BitField<21, 1, u64> r_stick_up;
BitField<22, 1, u64> r_stick_right;
BitField<23, 1, u64> r_stick_down;
// Not always active?
BitField<24, 1, u64> left_sl;
BitField<25, 1, u64> left_sr;
BitField<26, 1, u64> right_sl;
BitField<27, 1, u64> right_sr;
BitField<28, 1, u64> palma;
BitField<30, 1, u64> handheld_left_b;
};
};
static_assert(sizeof(ControllerPadState) == 8, "ControllerPadState is an invalid size");
struct AnalogPosition {
s32_le x;
s32_le y;
};
static_assert(sizeof(AnalogPosition) == 8, "AnalogPosition is an invalid size");
struct ConnectionState {
union {
u32_le raw{};
u32 raw{};
BitField<0, 1, u32> is_connected;
BitField<1, 1, u32> is_wired;
BitField<2, 1, u32> is_left_connected;
@@ -348,79 +233,60 @@ private:
BitField<5, 1, u32> is_right_wired;
};
};
static_assert(sizeof(ConnectionState) == 4, "ConnectionState is an invalid size");
static_assert(sizeof(NpadAttribute) == 4, "NpadAttribute is an invalid size");
struct ControllerPad {
ControllerPadState pad_states;
AnalogPosition l_stick;
AnalogPosition r_stick;
// This is nn::hid::NpadFullKeyState
// This is nn::hid::NpadHandheldState
// This is nn::hid::NpadJoyDualState
// This is nn::hid::NpadJoyLeftState
// This is nn::hid::NpadJoyRightState
// This is nn::hid::NpadPalmaState
// This is nn::hid::NpadSystemExtState
struct NPadGenericState {
s64_le sampling_number;
Core::HID::NpadButtonState npad_buttons;
Core::HID::AnalogStickState l_stick;
Core::HID::AnalogStickState r_stick;
NpadAttribute connection_status;
INSERT_PADDING_BYTES(4); // Reserved
};
static_assert(sizeof(ControllerPad) == 0x18, "ControllerPad is an invalid size");
static_assert(sizeof(NPadGenericState) == 0x28, "NPadGenericState is an invalid size");
struct GenericStates {
s64_le timestamp;
s64_le timestamp2;
ControllerPad pad;
ConnectionState connection_status;
};
static_assert(sizeof(GenericStates) == 0x30, "NPadGenericStates is an invalid size");
struct NPadGeneric {
CommonHeader common;
std::array<GenericStates, 17> npad;
};
static_assert(sizeof(NPadGeneric) == 0x350, "NPadGeneric is an invalid size");
struct SixAxisAttributes {
// This is nn::hid::SixAxisSensorAttribute
struct SixAxisSensorAttribute {
union {
u32_le raw{};
u32 raw{};
BitField<0, 1, u32> is_connected;
BitField<1, 1, u32> is_interpolated;
};
};
static_assert(sizeof(SixAxisAttributes) == 4, "SixAxisAttributes is an invalid size");
static_assert(sizeof(SixAxisSensorAttribute) == 4, "SixAxisSensorAttribute is an invalid size");
struct SixAxisStates {
s64_le timestamp{};
INSERT_PADDING_WORDS(2);
s64_le timestamp2{};
// This is nn::hid::SixAxisSensorState
struct SixAxisSensorState {
s64 delta_time{};
s64 sampling_number{};
Common::Vec3f accel{};
Common::Vec3f gyro{};
Common::Vec3f rotation{};
std::array<Common::Vec3f, 3> orientation{};
SixAxisAttributes attribute;
SixAxisSensorAttribute attribute;
INSERT_PADDING_BYTES(4); // Reserved
};
static_assert(sizeof(SixAxisStates) == 0x68, "SixAxisStates is an invalid size");
static_assert(sizeof(SixAxisSensorState) == 0x60, "SixAxisSensorState is an invalid size");
struct SixAxisGeneric {
CommonHeader common{};
std::array<SixAxisStates, 17> sixaxis{};
// This is nn::hid::server::NpadGcTriggerState
struct NpadGcTriggerState {
s64 sampling_number{};
s32 l_analog{};
s32 r_analog{};
};
static_assert(sizeof(SixAxisGeneric) == 0x708, "SixAxisGeneric is an invalid size");
struct TriggerState {
s64_le timestamp{};
s64_le timestamp2{};
s32_le l_analog{};
s32_le r_analog{};
};
static_assert(sizeof(TriggerState) == 0x18, "TriggerState is an invalid size");
struct TriggerGeneric {
INSERT_PADDING_BYTES(0x4);
s64_le timestamp;
INSERT_PADDING_BYTES(0x4);
s64_le total_entry_count;
s64_le last_entry_index;
s64_le entry_count;
std::array<TriggerState, 17> trigger{};
};
static_assert(sizeof(TriggerGeneric) == 0x1C8, "TriggerGeneric is an invalid size");
static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size");
// This is nn::hid::NpadSystemProperties
struct NPadSystemProperties {
union {
s64_le raw{};
s64 raw{};
BitField<0, 1, s64> is_charging_joy_dual;
BitField<1, 1, s64> is_charging_joy_left;
BitField<2, 1, s64> is_charging_joy_right;
@@ -438,17 +304,20 @@ private:
};
static_assert(sizeof(NPadSystemProperties) == 0x8, "NPadSystemProperties is an invalid size");
struct NPadButtonProperties {
// This is nn::hid::NpadSystemButtonProperties
struct NpadSystemButtonProperties {
union {
s32_le raw{};
s32 raw{};
BitField<0, 1, s32> is_home_button_protection_enabled;
};
};
static_assert(sizeof(NPadButtonProperties) == 0x4, "NPadButtonProperties is an invalid size");
static_assert(sizeof(NpadSystemButtonProperties) == 0x4,
"NPadButtonProperties is an invalid size");
struct NPadDevice {
// This is nn::hid::system::DeviceType
struct DeviceType {
union {
u32_le raw{};
u32 raw{};
BitField<0, 1, s32> fullkey;
BitField<1, 1, s32> debug_pad;
BitField<2, 1, s32> handheld_left;
@@ -465,26 +334,49 @@ private:
BitField<13, 1, s32> handheld_lark_nes_left;
BitField<14, 1, s32> handheld_lark_nes_right;
BitField<15, 1, s32> lucia;
BitField<16, 1, s32> lagon;
BitField<17, 1, s32> lager;
BitField<31, 1, s32> system;
};
};
struct MotionDevice {
Common::Vec3f accel;
Common::Vec3f gyro;
Common::Vec3f rotation;
std::array<Common::Vec3f, 3> orientation;
Common::Quaternion<f32> quaternion;
// This is nn::hid::detail::NfcXcdDeviceHandleStateImpl
struct NfcXcdDeviceHandleStateImpl {
u64 handle;
bool is_available;
bool is_activated;
INSERT_PADDING_BYTES(0x6); // Reserved
u64 sampling_number;
};
static_assert(sizeof(NfcXcdDeviceHandleStateImpl) == 0x18,
"NfcXcdDeviceHandleStateImpl is an invalid size");
struct NfcXcdHandle {
INSERT_PADDING_BYTES(0x60);
// nn::hid::detail::NfcXcdDeviceHandleStateImplAtomicStorage
struct NfcXcdDeviceHandleStateImplAtomicStorage {
u64 sampling_number;
NfcXcdDeviceHandleStateImpl nfc_xcd_device_handle_state;
};
static_assert(sizeof(NfcXcdDeviceHandleStateImplAtomicStorage) == 0x20,
"NfcXcdDeviceHandleStateImplAtomicStorage is an invalid size");
// This is nn::hid::detail::NfcXcdDeviceHandleState
struct NfcXcdDeviceHandleState {
// TODO(german77): Make this struct a ring lifo object
INSERT_PADDING_BYTES(0x8); // Unused
s64 total_buffer_count = max_buffer_size;
s64 buffer_tail{};
s64 buffer_count{};
std::array<NfcXcdDeviceHandleStateImplAtomicStorage, 2> nfc_xcd_device_handle_storage;
};
static_assert(sizeof(NfcXcdDeviceHandleState) == 0x60,
"NfcXcdDeviceHandleState is an invalid size");
// This is nn::hid::system::AppletFooterUiAttributesSet
struct AppletFooterUiAttributes {
INSERT_PADDING_BYTES(0x4);
};
// This is nn::hid::system::AppletFooterUiType
enum class AppletFooterUiType : u8 {
None = 0,
HandheldNone = 1,
@@ -510,95 +402,137 @@ private:
Lagon = 21,
};
struct NPadEntry {
NpadStyleSet style_set;
NpadAssignments assignment_mode;
FullKeyColor fullkey_color;
JoyconColor joycon_color;
struct AppletFooterUi {
AppletFooterUiAttributes attributes;
AppletFooterUiType type;
INSERT_PADDING_BYTES(0x5B); // Reserved
};
static_assert(sizeof(AppletFooterUi) == 0x60, "AppletFooterUi is an invalid size");
NPadGeneric fullkey_states;
NPadGeneric handheld_states;
NPadGeneric joy_dual_states;
NPadGeneric joy_left_states;
NPadGeneric joy_right_states;
NPadGeneric palma_states;
NPadGeneric system_ext_states;
SixAxisGeneric sixaxis_fullkey;
SixAxisGeneric sixaxis_handheld;
SixAxisGeneric sixaxis_dual_left;
SixAxisGeneric sixaxis_dual_right;
SixAxisGeneric sixaxis_left;
SixAxisGeneric sixaxis_right;
NPadDevice device_type;
INSERT_PADDING_BYTES(0x4); // reserved
// This is nn::hid::NpadLarkType
enum class NpadLarkType : u32 {
Invalid,
H1,
H2,
NL,
NR,
};
// This is nn::hid::NpadLuciaType
enum class NpadLuciaType : u32 {
Invalid,
J,
E,
U,
};
// This is nn::hid::NpadLagonType
enum class NpadLagonType : u32 {
Invalid,
};
// This is nn::hid::NpadLagerType
enum class NpadLagerType : u32 {
Invalid,
J,
E,
U,
};
// This is nn::hid::detail::NpadInternalState
struct NpadInternalState {
Core::HID::NpadStyleTag style_set;
NpadJoyAssignmentMode assignment_mode;
NpadFullKeyColorState fullkey_color;
NpadJoyColorState joycon_color;
Lifo<NPadGenericState> fullkey_lifo;
Lifo<NPadGenericState> handheld_lifo;
Lifo<NPadGenericState> joy_dual_lifo;
Lifo<NPadGenericState> joy_left_lifo;
Lifo<NPadGenericState> joy_right_lifo;
Lifo<NPadGenericState> palma_lifo;
Lifo<NPadGenericState> system_ext_lifo;
Lifo<SixAxisSensorState> sixaxis_fullkey_lifo;
Lifo<SixAxisSensorState> sixaxis_handheld_lifo;
Lifo<SixAxisSensorState> sixaxis_dual_left_lifo;
Lifo<SixAxisSensorState> sixaxis_dual_right_lifo;
Lifo<SixAxisSensorState> sixaxis_left_lifo;
Lifo<SixAxisSensorState> sixaxis_right_lifo;
DeviceType device_type;
INSERT_PADDING_BYTES(0x4); // Reserved
NPadSystemProperties system_properties;
NPadButtonProperties button_properties;
u32 battery_level_dual;
u32 battery_level_left;
u32 battery_level_right;
AppletFooterUiAttributes footer_attributes;
AppletFooterUiType footer_type;
// nfc_states needs to be checked switchbrew does not match with HW
NfcXcdHandle nfc_states;
INSERT_PADDING_BYTES(0x8); // Mutex
TriggerGeneric gc_trigger_states;
INSERT_PADDING_BYTES(0xc1f);
NpadSystemButtonProperties button_properties;
Core::HID::BatteryLevel battery_level_dual;
Core::HID::BatteryLevel battery_level_left;
Core::HID::BatteryLevel battery_level_right;
union {
NfcXcdDeviceHandleState nfc_xcd_device_handle;
AppletFooterUi applet_footer;
};
INSERT_PADDING_BYTES(0x20); // Unknown
Lifo<NpadGcTriggerState> gc_trigger_lifo;
NpadLarkType lark_type_l_and_main;
NpadLarkType lark_type_r;
NpadLuciaType lucia_type;
NpadLagonType lagon_type;
NpadLagerType lager_type;
INSERT_PADDING_BYTES(
0x4); // FW 13.x Investigate there is some sort of bitflag related to joycons
INSERT_PADDING_BYTES(0xc08); // Unknown
};
static_assert(sizeof(NPadEntry) == 0x5000, "NPadEntry is an invalid size");
static_assert(sizeof(NpadInternalState) == 0x5000, "NpadInternalState is an invalid size");
struct ControllerHolder {
NPadControllerType type;
bool is_connected;
struct VibrationData {
bool device_mounted{};
VibrationValue latest_vibration_value{};
std::chrono::steady_clock::time_point last_vibration_timepoint{};
};
struct ControllerData {
Core::HID::EmulatedController* device;
Kernel::KEvent* styleset_changed_event{};
NpadInternalState shared_memory_entry{};
std::array<VibrationData, 2> vibration{};
bool unintended_home_button_input_protection{};
bool is_connected{};
Core::HID::NpadType npad_type{Core::HID::NpadType::None};
// Current pad state
NPadGenericState npad_pad_state{};
NPadGenericState npad_libnx_state{};
NpadGcTriggerState npad_trigger_state{};
SixAxisSensorState sixaxis_fullkey_state{};
SixAxisSensorState sixaxis_handheld_state{};
SixAxisSensorState sixaxis_dual_left_state{};
SixAxisSensorState sixaxis_dual_right_state{};
SixAxisSensorState sixaxis_left_lifo_state{};
SixAxisSensorState sixaxis_right_lifo_state{};
int callback_key;
};
void ControllerUpdate(Core::HID::ControllerTriggerType type, std::size_t controller_idx);
void InitNewlyAddedController(std::size_t controller_idx);
bool IsControllerSupported(NPadControllerType controller) const;
bool IsControllerSupported(Core::HID::NpadType controller) const;
void RequestPadStateUpdate(u32 npad_id);
void WriteEmptyEntry(NpadInternalState& npad);
std::atomic<u32> press_state{};
NpadStyleSet style{};
std::array<NPadEntry, 10> shared_memory_entries{};
using ButtonArray = std::array<
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>,
10>;
using StickArray = std::array<
std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>,
10>;
using VibrationArray = std::array<std::array<std::unique_ptr<Input::VibrationDevice>,
Settings::NativeVibration::NUM_VIBRATIONS_HID>,
10>;
using MotionArray = std::array<
std::array<std::unique_ptr<Input::MotionDevice>, Settings::NativeMotion::NUM_MOTIONS_HID>,
10>;
std::array<ControllerData, 10> controller_data{};
KernelHelpers::ServiceContext& service_context;
std::mutex mutex;
ButtonArray buttons;
StickArray sticks;
VibrationArray vibrations;
MotionArray motions;
std::vector<u32> supported_npad_id_types{};
NpadHoldType hold_type{NpadHoldType::Vertical};
NpadJoyHoldType hold_type{NpadJoyHoldType::Vertical};
NpadHandheldActivationMode handheld_activation_mode{NpadHandheldActivationMode::Dual};
NpadCommunicationMode communication_mode{NpadCommunicationMode::Default};
// Each controller should have their own styleset changed event
std::array<Kernel::KEvent*, 10> styleset_changed_events{};
std::array<std::array<std::chrono::steady_clock::time_point, 2>, 10>
last_vibration_timepoints{};
std::array<std::array<VibrationValue, 2>, 10> latest_vibration_values{};
bool permit_vibration_session_enabled{false};
std::array<std::array<bool, 2>, 10> vibration_devices_mounted{};
std::array<ControllerHolder, 10> connected_controllers{};
std::array<bool, 10> unintended_home_button_input_protection{};
bool analog_stick_use_center_clamp{};
GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard};
bool sixaxis_sensors_enabled{true};
f32 sixaxis_fusion_parameter1{};
f32 sixaxis_fusion_parameter2{};
bool sixaxis_at_rest{true};
std::array<ControllerPad, 10> npad_pad_states{};
std::array<TriggerState, 10> npad_trigger_states{};
bool is_in_lr_assignment_mode{false};
};
} // namespace Service::HID

View File

@@ -31,10 +31,9 @@ void Controller_Stubbed::OnUpdate(const Core::Timing::CoreTiming& core_timing, u
std::memcpy(data + common_offset, &header, sizeof(CommonHeader));
}
void Controller_Stubbed::OnLoadInputDevices() {}
void Controller_Stubbed::SetCommonHeaderOffset(std::size_t off) {
common_offset = off;
smart_update = true;
}
} // namespace Service::HID

View File

@@ -22,12 +22,17 @@ public:
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
void SetCommonHeaderOffset(std::size_t off);
private:
struct CommonHeader {
s64 timestamp;
s64 total_entry_count;
s64 last_entry_index;
s64 entry_count;
};
static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size");
bool smart_update{};
std::size_t common_offset{};
};

View File

@@ -7,72 +7,79 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/frontend/emu_window.h"
#include "core/frontend/input.h"
#include "core/hle/service/hid/controllers/touchscreen.h"
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400;
Controller_Touchscreen::Controller_Touchscreen(Core::System& system_) : ControllerBase{system_} {}
Controller_Touchscreen::Controller_Touchscreen(Core::System& system_) : ControllerBase{system_} {
console = system.HIDCore().GetEmulatedConsole();
}
Controller_Touchscreen::~Controller_Touchscreen() = default;
void Controller_Touchscreen::OnInit() {
for (std::size_t id = 0; id < MAX_FINGERS; ++id) {
mouse_finger_id[id] = MAX_FINGERS;
keyboard_finger_id[id] = MAX_FINGERS;
udp_finger_id[id] = MAX_FINGERS;
}
}
void Controller_Touchscreen::OnInit() {}
void Controller_Touchscreen::OnRelease() {}
void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) {
shared_memory.header.timestamp = core_timing.GetCPUTicks();
shared_memory.header.total_entry_count = 17;
touch_screen_lifo.timestamp = core_timing.GetCPUTicks();
if (!IsControllerActivated()) {
shared_memory.header.entry_count = 0;
shared_memory.header.last_entry_index = 0;
touch_screen_lifo.buffer_count = 0;
touch_screen_lifo.buffer_tail = 0;
std::memcpy(data, &touch_screen_lifo, sizeof(touch_screen_lifo));
return;
}
shared_memory.header.entry_count = 16;
const auto& last_entry =
shared_memory.shared_memory_entries[shared_memory.header.last_entry_index];
shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
auto& cur_entry = shared_memory.shared_memory_entries[shared_memory.header.last_entry_index];
const auto touch_status = console->GetTouch();
for (std::size_t id = 0; id < MAX_FINGERS; id++) {
const auto& current_touch = touch_status[id];
auto& finger = fingers[id];
finger.position = current_touch.position;
finger.id = current_touch.id;
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
if (finger.attribute.start_touch) {
finger.attribute.raw = 0;
continue;
}
const Input::TouchStatus& mouse_status = touch_mouse_device->GetStatus();
const Input::TouchStatus& udp_status = touch_udp_device->GetStatus();
for (std::size_t id = 0; id < mouse_status.size(); ++id) {
mouse_finger_id[id] = UpdateTouchInputEvent(mouse_status[id], mouse_finger_id[id]);
udp_finger_id[id] = UpdateTouchInputEvent(udp_status[id], udp_finger_id[id]);
}
if (finger.attribute.end_touch) {
finger.attribute.raw = 0;
finger.pressed = false;
continue;
}
if (Settings::values.use_touch_from_button) {
const Input::TouchStatus& keyboard_status = touch_btn_device->GetStatus();
for (std::size_t id = 0; id < mouse_status.size(); ++id) {
keyboard_finger_id[id] =
UpdateTouchInputEvent(keyboard_status[id], keyboard_finger_id[id]);
if (!finger.pressed && current_touch.pressed) {
finger.attribute.start_touch.Assign(1);
finger.pressed = true;
continue;
}
if (finger.pressed && !current_touch.pressed) {
finger.attribute.raw = 0;
finger.attribute.end_touch.Assign(1);
}
}
std::array<Finger, 16> active_fingers;
std::array<Core::HID::TouchFinger, MAX_FINGERS> active_fingers;
const auto end_iter = std::copy_if(fingers.begin(), fingers.end(), active_fingers.begin(),
[](const auto& finger) { return finger.pressed; });
const auto active_fingers_count =
static_cast<std::size_t>(std::distance(active_fingers.begin(), end_iter));
const u64 tick = core_timing.GetCPUTicks();
cur_entry.entry_count = static_cast<s32_le>(active_fingers_count);
const auto& last_entry = touch_screen_lifo.ReadCurrentEntry().state;
next_state.sampling_number = last_entry.sampling_number + 1;
next_state.entry_count = static_cast<s32>(active_fingers_count);
for (std::size_t id = 0; id < MAX_FINGERS; ++id) {
auto& touch_entry = cur_entry.states[id];
auto& touch_entry = next_state.states[id];
if (id < active_fingers_count) {
const auto& [active_x, active_y] = active_fingers[id].position;
touch_entry.position = {
@@ -97,66 +104,9 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin
touch_entry.finger = 0;
}
}
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(TouchScreenSharedMemory));
}
void Controller_Touchscreen::OnLoadInputDevices() {
touch_mouse_device = Input::CreateDevice<Input::TouchDevice>("engine:emu_window");
touch_udp_device = Input::CreateDevice<Input::TouchDevice>("engine:cemuhookudp");
touch_btn_device = Input::CreateDevice<Input::TouchDevice>("engine:touch_from_button");
}
std::optional<std::size_t> Controller_Touchscreen::GetUnusedFingerID() const {
// Dont assign any touch input to a finger if disabled
if (!Settings::values.touchscreen.enabled) {
return std::nullopt;
}
std::size_t first_free_id = 0;
while (first_free_id < MAX_FINGERS) {
if (!fingers[first_free_id].pressed) {
return first_free_id;
} else {
first_free_id++;
}
}
return std::nullopt;
}
std::size_t Controller_Touchscreen::UpdateTouchInputEvent(
const std::tuple<float, float, bool>& touch_input, std::size_t finger_id) {
const auto& [x, y, pressed] = touch_input;
if (finger_id > MAX_FINGERS) {
LOG_ERROR(Service_HID, "Invalid finger id {}", finger_id);
return MAX_FINGERS;
}
if (pressed) {
Attributes attribute{};
if (finger_id == MAX_FINGERS) {
const auto first_free_id = GetUnusedFingerID();
if (!first_free_id) {
// Invalid finger id do nothing
return MAX_FINGERS;
}
finger_id = first_free_id.value();
fingers[finger_id].pressed = true;
fingers[finger_id].id = static_cast<u32_le>(finger_id);
attribute.start_touch.Assign(1);
}
fingers[finger_id].position = {x, y};
fingers[finger_id].attribute = attribute;
return finger_id;
}
if (finger_id != MAX_FINGERS) {
if (!fingers[finger_id].attribute.end_touch) {
fingers[finger_id].attribute.end_touch.Assign(1);
fingers[finger_id].attribute.start_touch.Assign(0);
return finger_id;
}
fingers[finger_id].pressed = false;
}
return MAX_FINGERS;
touch_screen_lifo.WriteNextEntry(next_state);
std::memcpy(data + SHARED_MEMORY_OFFSET, &touch_screen_lifo, sizeof(touch_screen_lifo));
}
} // namespace Service::HID

View File

@@ -9,18 +9,23 @@
#include "common/common_types.h"
#include "common/point.h"
#include "common/swap.h"
#include "core/frontend/input.h"
#include "core/hid/emulated_console.h"
#include "core/hid/hid_core.h"
#include "core/hid/hid_types.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/ring_lifo.h"
namespace Service::HID {
class Controller_Touchscreen final : public ControllerBase {
public:
// This is nn::hid::TouchScreenModeForNx
enum class TouchScreenModeForNx : u8 {
UseSystemSetting,
Finger,
Heat2,
};
// This is nn::hid::TouchScreenConfigurationForNx
struct TouchScreenConfigurationForNx {
TouchScreenModeForNx mode;
INSERT_PADDING_BYTES_NOINIT(0x7);
@@ -41,73 +46,24 @@ public:
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
private:
static constexpr std::size_t MAX_FINGERS = 16;
// Returns an unused finger id, if there is no fingers available std::nullopt will be returned
std::optional<std::size_t> GetUnusedFingerID() const;
// If the touch is new it tries to assing a new finger id, if there is no fingers avaliable no
// changes will be made. Updates the coordinates if the finger id it's already set. If the touch
// ends delays the output by one frame to set the end_touch flag before finally freeing the
// finger id
std::size_t UpdateTouchInputEvent(const std::tuple<float, float, bool>& touch_input,
std::size_t finger_id);
struct Attributes {
union {
u32 raw{};
BitField<0, 1, u32> start_touch;
BitField<1, 1, u32> end_touch;
};
// This is nn::hid::TouchScreenState
struct TouchScreenState {
s64 sampling_number;
s32 entry_count;
INSERT_PADDING_BYTES(4); // Reserved
std::array<Core::HID::TouchState, MAX_FINGERS> states;
};
static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size");
static_assert(sizeof(TouchScreenState) == 0x290, "TouchScreenState is an invalid size");
struct TouchState {
u64_le delta_time;
Attributes attribute;
u32_le finger;
Common::Point<u32_le> position;
u32_le diameter_x;
u32_le diameter_y;
u32_le rotation_angle;
};
static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size");
// This is nn::hid::detail::TouchScreenLifo
Lifo<TouchScreenState> touch_screen_lifo{};
static_assert(sizeof(touch_screen_lifo) == 0x2C38, "touch_screen_lifo is an invalid size");
TouchScreenState next_state{};
struct TouchScreenEntry {
s64_le sampling_number;
s64_le sampling_number2;
s32_le entry_count;
std::array<TouchState, MAX_FINGERS> states;
};
static_assert(sizeof(TouchScreenEntry) == 0x298, "TouchScreenEntry is an invalid size");
struct TouchScreenSharedMemory {
CommonHeader header;
std::array<TouchScreenEntry, 17> shared_memory_entries{};
INSERT_PADDING_BYTES(0x3c8);
};
static_assert(sizeof(TouchScreenSharedMemory) == 0x3000,
"TouchScreenSharedMemory is an invalid size");
struct Finger {
u64_le last_touch{};
Common::Point<float> position;
u32_le id{};
bool pressed{};
Attributes attribute;
};
TouchScreenSharedMemory shared_memory{};
std::unique_ptr<Input::TouchDevice> touch_mouse_device;
std::unique_ptr<Input::TouchDevice> touch_udp_device;
std::unique_ptr<Input::TouchDevice> touch_btn_device;
std::array<std::size_t, MAX_FINGERS> mouse_finger_id;
std::array<std::size_t, MAX_FINGERS> keyboard_finger_id;
std::array<std::size_t, MAX_FINGERS> udp_finger_id;
std::array<Finger, MAX_FINGERS> fingers;
std::array<Core::HID::TouchFinger, MAX_FINGERS> fingers;
Core::HID::EmulatedConsole* console;
};
} // namespace Service::HID

View File

@@ -19,28 +19,19 @@ void Controller_XPad::OnRelease() {}
void Controller_XPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) {
for (auto& xpad_entry : shared_memory.shared_memory_entries) {
xpad_entry.header.timestamp = core_timing.GetCPUTicks();
xpad_entry.header.total_entry_count = 17;
if (!IsControllerActivated()) {
xpad_entry.header.entry_count = 0;
xpad_entry.header.last_entry_index = 0;
return;
}
xpad_entry.header.entry_count = 16;
const auto& last_entry = xpad_entry.pad_states[xpad_entry.header.last_entry_index];
xpad_entry.header.last_entry_index = (xpad_entry.header.last_entry_index + 1) % 17;
auto& cur_entry = xpad_entry.pad_states[xpad_entry.header.last_entry_index];
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
if (!IsControllerActivated()) {
basic_xpad_lifo.buffer_count = 0;
basic_xpad_lifo.buffer_tail = 0;
std::memcpy(data + SHARED_MEMORY_OFFSET, &basic_xpad_lifo, sizeof(basic_xpad_lifo));
return;
}
const auto& last_entry = basic_xpad_lifo.ReadCurrentEntry().state;
next_state.sampling_number = last_entry.sampling_number + 1;
// TODO(ogniK): Update xpad states
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
basic_xpad_lifo.WriteNextEntry(next_state);
std::memcpy(data + SHARED_MEMORY_OFFSET, &basic_xpad_lifo, sizeof(basic_xpad_lifo));
}
void Controller_XPad::OnLoadInputDevices() {}
} // namespace Service::HID

View File

@@ -8,7 +8,9 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hid/hid_types.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/ring_lifo.h"
namespace Service::HID {
class Controller_XPad final : public ControllerBase {
@@ -25,13 +27,11 @@ public:
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override;
// Called when input devices should be loaded
void OnLoadInputDevices() override;
private:
struct Attributes {
// This is nn::hid::BasicXpadAttributeSet
struct BasicXpadAttributeSet {
union {
u32_le raw{};
u32 raw{};
BitField<0, 1, u32> is_connected;
BitField<1, 1, u32> is_wired;
BitField<2, 1, u32> is_left_connected;
@@ -40,11 +40,12 @@ private:
BitField<5, 1, u32> is_right_wired;
};
};
static_assert(sizeof(Attributes) == 4, "Attributes is an invalid size");
static_assert(sizeof(BasicXpadAttributeSet) == 4, "BasicXpadAttributeSet is an invalid size");
struct Buttons {
// This is nn::hid::BasicXpadButtonSet
struct BasicXpadButtonSet {
union {
u32_le raw{};
u32 raw{};
// Button states
BitField<0, 1, u32> a;
BitField<1, 1, u32> b;
@@ -88,35 +89,21 @@ private:
BitField<30, 1, u32> handheld_left_b;
};
};
static_assert(sizeof(Buttons) == 4, "Buttons is an invalid size");
static_assert(sizeof(BasicXpadButtonSet) == 4, "BasicXpadButtonSet is an invalid size");
struct AnalogStick {
s32_le x;
s32_le y;
// This is nn::hid::detail::BasicXpadState
struct BasicXpadState {
s64 sampling_number;
BasicXpadAttributeSet attributes;
BasicXpadButtonSet pad_states;
Core::HID::AnalogStickState l_stick;
Core::HID::AnalogStickState r_stick;
};
static_assert(sizeof(AnalogStick) == 0x8, "AnalogStick is an invalid size");
static_assert(sizeof(BasicXpadState) == 0x20, "BasicXpadState is an invalid size");
struct XPadState {
s64_le sampling_number;
s64_le sampling_number2;
Attributes attributes;
Buttons pad_states;
AnalogStick l_stick;
AnalogStick r_stick;
};
static_assert(sizeof(XPadState) == 0x28, "XPadState is an invalid size");
struct XPadEntry {
CommonHeader header;
std::array<XPadState, 17> pad_states{};
INSERT_PADDING_BYTES(0x138);
};
static_assert(sizeof(XPadEntry) == 0x400, "XPadEntry is an invalid size");
struct SharedMemory {
std::array<XPadEntry, 4> shared_memory_entries{};
};
static_assert(sizeof(SharedMemory) == 0x1000, "SharedMemory is an invalid size");
SharedMemory shared_memory{};
// This is nn::hid::detail::BasicXpadLifo
Lifo<BasicXpadState> basic_xpad_lifo{};
static_assert(sizeof(basic_xpad_lifo) == 0x2C8, "basic_xpad_lifo is an invalid size");
BasicXpadState next_state{};
};
} // namespace Service::HID

View File

@@ -9,7 +9,6 @@
#include "core/core.h"
#include "core/core_timing.h"
#include "core/frontend/emu_window.h"
#include "core/frontend/input.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/k_shared_memory.h"
@@ -36,10 +35,9 @@
namespace Service::HID {
// Updating period for each HID device.
// HID is polled every 15ms, this value was derived from
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering#joy-con-status-data-packet
constexpr auto pad_update_ns = std::chrono::nanoseconds{1000 * 1000}; // (1ms, 1000Hz)
constexpr auto motion_update_ns = std::chrono::nanoseconds{15 * 1000 * 1000}; // (15ms, 66.666Hz)
// Period time is obtained by measuring the number of samples in a second on HW using a homebrew
constexpr auto pad_update_ns = std::chrono::nanoseconds{4 * 1000 * 1000}; // (4ms, 250Hz)
constexpr auto motion_update_ns = std::chrono::nanoseconds{5 * 1000 * 1000}; // (5ms, 200Hz)
constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000;
IAppletResource::IAppletResource(Core::System& system_,
@@ -91,7 +89,7 @@ IAppletResource::IAppletResource(Core::System& system_,
system.CoreTiming().ScheduleEvent(pad_update_ns, pad_update_event);
system.CoreTiming().ScheduleEvent(motion_update_ns, motion_update_event);
ReloadInputDevices();
system.HIDCore().ReloadInputDevices();
}
void IAppletResource::ActivateController(HidController controller) {
@@ -119,11 +117,7 @@ void IAppletResource::UpdateControllers(std::uintptr_t user_data,
std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming();
const bool should_reload = Settings::values.is_device_reload_pending.exchange(false);
for (const auto& controller : controllers) {
if (should_reload) {
controller->OnLoadInputDevices();
}
controller->OnUpdate(core_timing, system.Kernel().GetHidSharedMem().GetPointer(),
SHARED_MEMORY_SIZE);
}
@@ -893,7 +887,7 @@ void Hid::ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) {
void Hid::SetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
const auto hold_type{rp.PopEnum<Controller_NPad::NpadHoldType>()};
const auto hold_type{rp.PopEnum<Controller_NPad::NpadJoyHoldType>()};
applet_resource->GetController<Controller_NPad>(HidController::NPad).SetHoldType(hold_type);
@@ -926,7 +920,7 @@ void Hid::SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx
const auto parameters{rp.PopRaw<Parameters>()};
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Single);
.SetNpadMode(parameters.npad_id, Controller_NPad::NpadJoyAssignmentMode::Single);
LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}",
parameters.npad_id, parameters.applet_resource_user_id);
@@ -948,7 +942,7 @@ void Hid::SetNpadJoyAssignmentModeSingle(Kernel::HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()};
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Single);
.SetNpadMode(parameters.npad_id, Controller_NPad::NpadJoyAssignmentMode::Single);
LOG_WARNING(Service_HID,
"(STUBBED) called, npad_id={}, applet_resource_user_id={}, npad_joy_device_type={}",
@@ -970,7 +964,7 @@ void Hid::SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()};
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Dual);
.SetNpadMode(parameters.npad_id, Controller_NPad::NpadJoyAssignmentMode::Dual);
LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}",
parameters.npad_id, parameters.applet_resource_user_id);
@@ -1136,36 +1130,36 @@ void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto vibration_device_handle{rp.PopRaw<Controller_NPad::DeviceHandle>()};
VibrationDeviceInfo vibration_device_info;
Core::HID::VibrationDeviceInfo vibration_device_info;
switch (vibration_device_handle.npad_type) {
case Controller_NPad::NpadType::ProController:
case Controller_NPad::NpadType::Handheld:
case Controller_NPad::NpadType::JoyconDual:
case Controller_NPad::NpadType::JoyconLeft:
case Controller_NPad::NpadType::JoyconRight:
case Core::HID::NpadType::ProController:
case Core::HID::NpadType::Handheld:
case Core::HID::NpadType::JoyconDual:
case Core::HID::NpadType::JoyconLeft:
case Core::HID::NpadType::JoyconRight:
default:
vibration_device_info.type = VibrationDeviceType::LinearResonantActuator;
vibration_device_info.type = Core::HID::VibrationDeviceType::LinearResonantActuator;
break;
case Controller_NPad::NpadType::GameCube:
vibration_device_info.type = VibrationDeviceType::GcErm;
case Core::HID::NpadType::GameCube:
vibration_device_info.type = Core::HID::VibrationDeviceType::GcErm;
break;
case Controller_NPad::NpadType::Pokeball:
vibration_device_info.type = VibrationDeviceType::Unknown;
case Core::HID::NpadType::Pokeball:
vibration_device_info.type = Core::HID::VibrationDeviceType::Unknown;
break;
}
switch (vibration_device_handle.device_index) {
case Controller_NPad::DeviceIndex::Left:
vibration_device_info.position = VibrationDevicePosition::Left;
vibration_device_info.position = Core::HID::VibrationDevicePosition::Left;
break;
case Controller_NPad::DeviceIndex::Right:
vibration_device_info.position = VibrationDevicePosition::Right;
vibration_device_info.position = Core::HID::VibrationDevicePosition::Right;
break;
case Controller_NPad::DeviceIndex::None:
default:
UNREACHABLE_MSG("DeviceIndex should never be None!");
vibration_device_info.position = VibrationDevicePosition::None;
vibration_device_info.position = Core::HID::VibrationDevicePosition::None;
break;
}
@@ -1280,7 +1274,7 @@ void Hid::SendVibrationGcErmCommand(Kernel::HLERequestContext& ctx) {
struct Parameters {
Controller_NPad::DeviceHandle vibration_device_handle;
u64 applet_resource_user_id;
VibrationGcErmCommand gc_erm_command;
Core::HID::VibrationGcErmCommand gc_erm_command;
};
static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
@@ -1294,21 +1288,21 @@ void Hid::SendVibrationGcErmCommand(Kernel::HLERequestContext& ctx) {
*/
const auto vibration_value = [parameters] {
switch (parameters.gc_erm_command) {
case VibrationGcErmCommand::Stop:
case Core::HID::VibrationGcErmCommand::Stop:
return Controller_NPad::VibrationValue{
.amp_low = 0.0f,
.freq_low = 160.0f,
.amp_high = 0.0f,
.freq_high = 320.0f,
};
case VibrationGcErmCommand::Start:
case Core::HID::VibrationGcErmCommand::Start:
return Controller_NPad::VibrationValue{
.amp_low = 1.0f,
.freq_low = 160.0f,
.amp_high = 1.0f,
.freq_high = 320.0f,
};
case VibrationGcErmCommand::StopHard:
case Core::HID::VibrationGcErmCommand::StopHard:
return Controller_NPad::VibrationValue{
.amp_low = 0.0f,
.freq_low = 0.0f,
@@ -1350,7 +1344,7 @@ void Hid::GetActualVibrationGcErmCommand(Kernel::HLERequestContext& ctx) {
const auto gc_erm_command = [last_vibration] {
if (last_vibration.amp_low != 0.0f || last_vibration.amp_high != 0.0f) {
return VibrationGcErmCommand::Start;
return Core::HID::VibrationGcErmCommand::Start;
}
/**
@@ -1360,10 +1354,10 @@ void Hid::GetActualVibrationGcErmCommand(Kernel::HLERequestContext& ctx) {
* This is done to reuse the controller vibration functions made for regular controllers.
*/
if (last_vibration.freq_low == 0.0f && last_vibration.freq_high == 0.0f) {
return VibrationGcErmCommand::StopHard;
return Core::HID::VibrationGcErmCommand::StopHard;
}
return VibrationGcErmCommand::Stop;
return Core::HID::VibrationGcErmCommand::Stop;
}();
LOG_DEBUG(Service_HID,
@@ -2039,10 +2033,6 @@ public:
}
};
void ReloadInputDevices() {
Settings::values.is_device_reload_pending.store(true);
}
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
std::make_shared<Hid>(system)->InstallAsService(service_manager);
std::make_shared<HidBus>(system)->InstallAsService(service_manager);

View File

@@ -161,38 +161,11 @@ private:
void GetNpadCommunicationMode(Kernel::HLERequestContext& ctx);
void SetTouchScreenConfiguration(Kernel::HLERequestContext& ctx);
enum class VibrationDeviceType : u32 {
Unknown = 0,
LinearResonantActuator = 1,
GcErm = 2,
};
enum class VibrationDevicePosition : u32 {
None = 0,
Left = 1,
Right = 2,
};
enum class VibrationGcErmCommand : u64 {
Stop = 0,
Start = 1,
StopHard = 2,
};
struct VibrationDeviceInfo {
VibrationDeviceType type{};
VibrationDevicePosition position{};
};
static_assert(sizeof(VibrationDeviceInfo) == 0x8, "VibrationDeviceInfo has incorrect size.");
std::shared_ptr<IAppletResource> applet_resource;
KernelHelpers::ServiceContext service_context;
};
/// Reload input devices. Used when input configuration changed
void ReloadInputDevices();
/// Registers all HID services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);

View File

@@ -0,0 +1,56 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#pragma once
#include <array>
#include "common/common_types.h"
#include "common/swap.h"
namespace Service::HID {
constexpr std::size_t max_buffer_size = 17;
template <typename State>
struct AtomicStorage {
s64 sampling_number;
State state;
};
template <typename State>
struct Lifo {
s64 timestamp{};
s64 total_buffer_count = max_buffer_size;
s64 buffer_tail{};
s64 buffer_count{};
std::array<AtomicStorage<State>, max_buffer_size> entries{};
const AtomicStorage<State>& ReadCurrentEntry() const {
return entries[buffer_tail];
}
const AtomicStorage<State>& ReadPreviousEntry() const {
return entries[GetPreviousEntryIndex()];
}
std::size_t GetPreviousEntryIndex() const {
return (buffer_tail + total_buffer_count - 1) % total_buffer_count;
}
std::size_t GetNextEntryIndex() const {
return (buffer_tail + 1) % total_buffer_count;
}
void WriteNextEntry(const State& new_state) {
if (buffer_count < total_buffer_count - 1) {
buffer_count++;
}
buffer_tail = GetNextEntryIndex();
const auto& previous_entry = ReadPreviousEntry();
entries[buffer_tail].sampling_number = previous_entry.sampling_number + 1;
entries[buffer_tail].state = new_state;
}
};
} // namespace Service::HID