mirror of
https://github.com/yuzu-emu/yuzu-mainline.git
synced 2024-12-25 15:25:30 +00:00
Morph review first wave
This commit is contained in:
parent
e2e5f1beaf
commit
b564f024f0
|
@ -289,7 +289,7 @@ public:
|
||||||
/// Provides a constant reference to the kernel instance.
|
/// Provides a constant reference to the kernel instance.
|
||||||
[[nodiscard]] const Kernel::KernelCore& Kernel() const;
|
[[nodiscard]] const Kernel::KernelCore& Kernel() const;
|
||||||
|
|
||||||
/// Gets a mutable reference to the HID interface
|
/// Gets a mutable reference to the HID interface.
|
||||||
[[nodiscard]] HID::HIDCore& HIDCore();
|
[[nodiscard]] HID::HIDCore& HIDCore();
|
||||||
|
|
||||||
/// Gets an immutable reference to the HID interface.
|
/// Gets an immutable reference to the HID interface.
|
||||||
|
|
|
@ -13,8 +13,7 @@ namespace Core::Frontend {
|
||||||
|
|
||||||
ControllerApplet::~ControllerApplet() = default;
|
ControllerApplet::~ControllerApplet() = default;
|
||||||
|
|
||||||
DefaultControllerApplet::DefaultControllerApplet(HID::HIDCore& hid_core_)
|
DefaultControllerApplet::DefaultControllerApplet(HID::HIDCore& hid_core_) : hid_core{hid_core_} {}
|
||||||
: hid_core{hid_core_} {}
|
|
||||||
|
|
||||||
DefaultControllerApplet::~DefaultControllerApplet() = default;
|
DefaultControllerApplet::~DefaultControllerApplet() = default;
|
||||||
|
|
||||||
|
@ -26,7 +25,7 @@ void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callb
|
||||||
parameters.enable_single_mode ? 1 : parameters.min_players;
|
parameters.enable_single_mode ? 1 : parameters.min_players;
|
||||||
|
|
||||||
// Disconnect Handheld first.
|
// Disconnect Handheld first.
|
||||||
auto* handheld =hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
|
auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
|
||||||
handheld->Disconnect();
|
handheld->Disconnect();
|
||||||
|
|
||||||
// Deduce the best configuration based on the input parameters.
|
// Deduce the best configuration based on the input parameters.
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
#include "core/hid/input_converter.h"
|
#include "core/hid/input_converter.h"
|
||||||
|
|
||||||
namespace Core::HID {
|
namespace Core::HID {
|
||||||
EmulatedConsole::EmulatedConsole() {}
|
EmulatedConsole::EmulatedConsole() = default;
|
||||||
|
|
||||||
EmulatedConsole::~EmulatedConsole() = default;
|
EmulatedConsole::~EmulatedConsole() = default;
|
||||||
|
|
||||||
|
@ -191,7 +191,7 @@ TouchFingerState EmulatedConsole::GetTouch() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmulatedConsole::TriggerOnChange(ConsoleTriggerType type) {
|
void EmulatedConsole::TriggerOnChange(ConsoleTriggerType type) {
|
||||||
for (const std::pair<int, ConsoleUpdateCallback> poller_pair : callback_list) {
|
for (const auto& poller_pair : callback_list) {
|
||||||
const ConsoleUpdateCallback& poller = poller_pair.second;
|
const ConsoleUpdateCallback& poller = poller_pair.second;
|
||||||
if (poller.on_change) {
|
if (poller.on_change) {
|
||||||
poller.on_change(type);
|
poller.on_change(type);
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
namespace Core::HID {
|
namespace Core::HID {
|
||||||
|
|
||||||
struct ConsoleMotionInfo {
|
struct ConsoleMotionInfo {
|
||||||
Input::MotionStatus raw_status;
|
Input::MotionStatus raw_status{};
|
||||||
MotionInput emulated{};
|
MotionInput emulated{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -34,21 +34,21 @@ using ConsoleMotionValues = ConsoleMotionInfo;
|
||||||
using TouchValues = std::array<Input::TouchStatus, 16>;
|
using TouchValues = std::array<Input::TouchStatus, 16>;
|
||||||
|
|
||||||
struct TouchFinger {
|
struct TouchFinger {
|
||||||
u64_le last_touch{};
|
u64 last_touch{};
|
||||||
Common::Point<float> position{};
|
Common::Point<float> position{};
|
||||||
u32_le id{};
|
u32 id{};
|
||||||
bool pressed{};
|
|
||||||
TouchAttribute attribute{};
|
TouchAttribute attribute{};
|
||||||
|
bool pressed{};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Contains all motion related data that is used on the services
|
// Contains all motion related data that is used on the services
|
||||||
struct ConsoleMotion {
|
struct ConsoleMotion {
|
||||||
bool is_at_rest{};
|
|
||||||
Common::Vec3f accel{};
|
Common::Vec3f accel{};
|
||||||
Common::Vec3f gyro{};
|
Common::Vec3f gyro{};
|
||||||
Common::Vec3f rotation{};
|
Common::Vec3f rotation{};
|
||||||
std::array<Common::Vec3f, 3> orientation{};
|
std::array<Common::Vec3f, 3> orientation{};
|
||||||
Common::Quaternion<f32> quaternion{};
|
Common::Quaternion<f32> quaternion{};
|
||||||
|
bool is_at_rest{};
|
||||||
};
|
};
|
||||||
|
|
||||||
using TouchFingerState = std::array<TouchFinger, 16>;
|
using TouchFingerState = std::array<TouchFinger, 16>;
|
||||||
|
|
|
@ -865,10 +865,10 @@ BatteryLevelState EmulatedController::GetBattery() const {
|
||||||
return controller.battery_state;
|
return controller.battery_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmulatedController::TriggerOnChange(ControllerTriggerType type, bool is_service_update) {
|
void EmulatedController::TriggerOnChange(ControllerTriggerType type, bool is_npad_service_update) {
|
||||||
for (const std::pair<int, ControllerUpdateCallback> poller_pair : callback_list) {
|
for (const auto& poller_pair : callback_list) {
|
||||||
const ControllerUpdateCallback& poller = poller_pair.second;
|
const ControllerUpdateCallback& poller = poller_pair.second;
|
||||||
if (!is_service_update && poller.is_service) {
|
if (!is_npad_service_update && poller.is_npad_service) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (poller.on_change) {
|
if (poller.on_change) {
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
namespace Core::HID {
|
namespace Core::HID {
|
||||||
|
|
||||||
struct ControllerMotionInfo {
|
struct ControllerMotionInfo {
|
||||||
Input::MotionStatus raw_status;
|
Input::MotionStatus raw_status{};
|
||||||
MotionInput emulated{};
|
MotionInput emulated{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -51,28 +51,28 @@ using BatteryValues = std::array<Input::BatteryStatus, 3>;
|
||||||
using VibrationValues = std::array<Input::VibrationStatus, 2>;
|
using VibrationValues = std::array<Input::VibrationStatus, 2>;
|
||||||
|
|
||||||
struct AnalogSticks {
|
struct AnalogSticks {
|
||||||
AnalogStickState left;
|
AnalogStickState left{};
|
||||||
AnalogStickState right;
|
AnalogStickState right{};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ControllerColors {
|
struct ControllerColors {
|
||||||
NpadControllerColor fullkey;
|
NpadControllerColor fullkey{};
|
||||||
NpadControllerColor left;
|
NpadControllerColor left{};
|
||||||
NpadControllerColor right;
|
NpadControllerColor right{};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BatteryLevelState {
|
struct BatteryLevelState {
|
||||||
NpadPowerInfo dual;
|
NpadPowerInfo dual{};
|
||||||
NpadPowerInfo left;
|
NpadPowerInfo left{};
|
||||||
NpadPowerInfo right;
|
NpadPowerInfo right{};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ControllerMotion {
|
struct ControllerMotion {
|
||||||
bool is_at_rest;
|
|
||||||
Common::Vec3f accel{};
|
Common::Vec3f accel{};
|
||||||
Common::Vec3f gyro{};
|
Common::Vec3f gyro{};
|
||||||
Common::Vec3f rotation{};
|
Common::Vec3f rotation{};
|
||||||
std::array<Common::Vec3f, 3> orientation{};
|
std::array<Common::Vec3f, 3> orientation{};
|
||||||
|
bool is_at_rest{};
|
||||||
};
|
};
|
||||||
|
|
||||||
using MotionState = std::array<ControllerMotion, 2>;
|
using MotionState = std::array<ControllerMotion, 2>;
|
||||||
|
@ -113,7 +113,7 @@ enum class ControllerTriggerType {
|
||||||
|
|
||||||
struct ControllerUpdateCallback {
|
struct ControllerUpdateCallback {
|
||||||
std::function<void(ControllerTriggerType)> on_change;
|
std::function<void(ControllerTriggerType)> on_change;
|
||||||
bool is_service;
|
bool is_npad_service;
|
||||||
};
|
};
|
||||||
|
|
||||||
class EmulatedController {
|
class EmulatedController {
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
namespace Core::HID {
|
namespace Core::HID {
|
||||||
|
|
||||||
EmulatedDevices::EmulatedDevices() {}
|
EmulatedDevices::EmulatedDevices() = default;
|
||||||
|
|
||||||
EmulatedDevices::~EmulatedDevices() = default;
|
EmulatedDevices::~EmulatedDevices() = default;
|
||||||
|
|
||||||
|
@ -332,7 +332,7 @@ MousePosition EmulatedDevices::GetMousePosition() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {
|
void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {
|
||||||
for (const std::pair<int, InterfaceUpdateCallback> poller_pair : callback_list) {
|
for (const auto& poller_pair : callback_list) {
|
||||||
const InterfaceUpdateCallback& poller = poller_pair.second;
|
const InterfaceUpdateCallback& poller = poller_pair.second;
|
||||||
if (poller.on_change) {
|
if (poller.on_change) {
|
||||||
poller.on_change(type);
|
poller.on_change(type);
|
||||||
|
|
|
@ -113,8 +113,8 @@ NpadStyleTag HIDCore::GetSupportedStyleTag() const {
|
||||||
|
|
||||||
s8 HIDCore::GetPlayerCount() const {
|
s8 HIDCore::GetPlayerCount() const {
|
||||||
s8 active_players = 0;
|
s8 active_players = 0;
|
||||||
for (std::size_t player_index = 0; player_index < available_controllers -2; player_index++) {
|
for (std::size_t player_index = 0; player_index < available_controllers - 2; ++player_index) {
|
||||||
const auto* controller = GetEmulatedControllerByIndex(player_index);
|
const auto* const controller = GetEmulatedControllerByIndex(player_index);
|
||||||
if (controller->IsConnected()) {
|
if (controller->IsConnected()) {
|
||||||
active_players++;
|
active_players++;
|
||||||
}
|
}
|
||||||
|
@ -123,8 +123,8 @@ s8 HIDCore::GetPlayerCount() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
NpadIdType HIDCore::GetFirstNpadId() const {
|
NpadIdType HIDCore::GetFirstNpadId() const {
|
||||||
for (std::size_t player_index = 0; player_index < available_controllers; player_index++) {
|
for (std::size_t player_index = 0; player_index < available_controllers; ++player_index) {
|
||||||
const auto* controller = GetEmulatedControllerByIndex(player_index);
|
const auto* const controller = GetEmulatedControllerByIndex(player_index);
|
||||||
if (controller->IsConnected()) {
|
if (controller->IsConnected()) {
|
||||||
return controller->GetNpadIdType();
|
return controller->GetNpadIdType();
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,7 +100,7 @@ enum class NpadType : u8 {
|
||||||
// This is nn::hid::NpadStyleTag
|
// This is nn::hid::NpadStyleTag
|
||||||
struct NpadStyleTag {
|
struct NpadStyleTag {
|
||||||
union {
|
union {
|
||||||
u32_le raw{};
|
u32 raw{};
|
||||||
|
|
||||||
BitField<0, 1, u32> fullkey;
|
BitField<0, 1, u32> fullkey;
|
||||||
BitField<1, 1, u32> handheld;
|
BitField<1, 1, u32> handheld;
|
||||||
|
@ -132,35 +132,35 @@ static_assert(sizeof(TouchAttribute) == 0x4, "TouchAttribute is an invalid size"
|
||||||
|
|
||||||
// This is nn::hid::TouchState
|
// This is nn::hid::TouchState
|
||||||
struct TouchState {
|
struct TouchState {
|
||||||
u64_le delta_time;
|
u64 delta_time;
|
||||||
TouchAttribute attribute;
|
TouchAttribute attribute;
|
||||||
u32_le finger;
|
u32 finger;
|
||||||
Common::Point<u32_le> position;
|
Common::Point<u32> position;
|
||||||
u32_le diameter_x;
|
u32 diameter_x;
|
||||||
u32_le diameter_y;
|
u32 diameter_y;
|
||||||
u32_le rotation_angle;
|
u32 rotation_angle;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size");
|
static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size");
|
||||||
|
|
||||||
// This is nn::hid::NpadControllerColor
|
// This is nn::hid::NpadControllerColor
|
||||||
struct NpadControllerColor {
|
struct NpadControllerColor {
|
||||||
u32_le body;
|
u32 body;
|
||||||
u32_le button;
|
u32 button;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(NpadControllerColor) == 8, "NpadControllerColor is an invalid size");
|
static_assert(sizeof(NpadControllerColor) == 8, "NpadControllerColor is an invalid size");
|
||||||
|
|
||||||
// This is nn::hid::AnalogStickState
|
// This is nn::hid::AnalogStickState
|
||||||
struct AnalogStickState {
|
struct AnalogStickState {
|
||||||
s32_le x;
|
s32 x;
|
||||||
s32_le y;
|
s32 y;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(AnalogStickState) == 8, "AnalogStickState is an invalid size");
|
static_assert(sizeof(AnalogStickState) == 8, "AnalogStickState is an invalid size");
|
||||||
|
|
||||||
// This is nn::hid::server::NpadGcTriggerState
|
// This is nn::hid::server::NpadGcTriggerState
|
||||||
struct NpadGcTriggerState {
|
struct NpadGcTriggerState {
|
||||||
s64_le sampling_number{};
|
s64 sampling_number{};
|
||||||
s32_le left{};
|
s32 left{};
|
||||||
s32_le right{};
|
s32 right{};
|
||||||
};
|
};
|
||||||
static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size");
|
static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size");
|
||||||
|
|
||||||
|
@ -286,7 +286,7 @@ static_assert(sizeof(NpadButtonState) == 0x8, "NpadButtonState has incorrect siz
|
||||||
// This is nn::hid::DebugPadButton
|
// This is nn::hid::DebugPadButton
|
||||||
struct DebugPadButton {
|
struct DebugPadButton {
|
||||||
union {
|
union {
|
||||||
u32_le raw{};
|
u32 raw{};
|
||||||
BitField<0, 1, u32> a;
|
BitField<0, 1, u32> a;
|
||||||
BitField<1, 1, u32> b;
|
BitField<1, 1, u32> b;
|
||||||
BitField<2, 1, u32> x;
|
BitField<2, 1, u32> x;
|
||||||
|
@ -345,7 +345,7 @@ static_assert(sizeof(VibrationDeviceInfo) == 0x8, "VibrationDeviceInfo has incor
|
||||||
// This is nn::hid::KeyboardModifier
|
// This is nn::hid::KeyboardModifier
|
||||||
struct KeyboardModifier {
|
struct KeyboardModifier {
|
||||||
union {
|
union {
|
||||||
u32_le raw{};
|
u32 raw{};
|
||||||
BitField<0, 1, u32> control;
|
BitField<0, 1, u32> control;
|
||||||
BitField<1, 1, u32> shift;
|
BitField<1, 1, u32> shift;
|
||||||
BitField<2, 1, u32> left_alt;
|
BitField<2, 1, u32> left_alt;
|
||||||
|
@ -383,7 +383,7 @@ static_assert(sizeof(MouseButton) == 0x4, "MouseButton is an invalid size");
|
||||||
// This is nn::hid::MouseAttribute
|
// This is nn::hid::MouseAttribute
|
||||||
struct MouseAttribute {
|
struct MouseAttribute {
|
||||||
union {
|
union {
|
||||||
u32_le raw{};
|
u32 raw{};
|
||||||
BitField<0, 1, u32> transferable;
|
BitField<0, 1, u32> transferable;
|
||||||
BitField<1, 1, u32> is_connected;
|
BitField<1, 1, u32> is_connected;
|
||||||
};
|
};
|
||||||
|
@ -392,13 +392,13 @@ static_assert(sizeof(MouseAttribute) == 0x4, "MouseAttribute is an invalid size"
|
||||||
|
|
||||||
// This is nn::hid::detail::MouseState
|
// This is nn::hid::detail::MouseState
|
||||||
struct MouseState {
|
struct MouseState {
|
||||||
s64_le sampling_number;
|
s64 sampling_number;
|
||||||
s32_le x;
|
s32 x;
|
||||||
s32_le y;
|
s32 y;
|
||||||
s32_le delta_x;
|
s32 delta_x;
|
||||||
s32_le delta_y;
|
s32 delta_y;
|
||||||
s32_le delta_wheel_x;
|
s32 delta_wheel_x;
|
||||||
s32_le delta_wheel_y;
|
s32 delta_wheel_y;
|
||||||
MouseButton button;
|
MouseButton button;
|
||||||
MouseAttribute attribute;
|
MouseAttribute attribute;
|
||||||
};
|
};
|
||||||
|
|
|
@ -142,8 +142,8 @@ Input::StickStatus TransformToStick(const Input::CallbackStatus& callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
SanitizeStick(status.x, status.y, true);
|
SanitizeStick(status.x, status.y, true);
|
||||||
const Input::AnalogProperties& properties_x = status.x.properties;
|
const auto& properties_x = status.x.properties;
|
||||||
const Input::AnalogProperties& properties_y = status.y.properties;
|
const auto& properties_y = status.y.properties;
|
||||||
const float x = status.x.value;
|
const float x = status.x.value;
|
||||||
const float y = status.y.value;
|
const float y = status.y.value;
|
||||||
|
|
||||||
|
@ -213,7 +213,7 @@ Input::TriggerStatus TransformToTrigger(const Input::CallbackStatus& callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
SanitizeAnalog(status.analog, true);
|
SanitizeAnalog(status.analog, true);
|
||||||
const Input::AnalogProperties& properties = status.analog.properties;
|
const auto& properties = status.analog.properties;
|
||||||
float& value = status.analog.value;
|
float& value = status.analog.value;
|
||||||
|
|
||||||
// Set button status
|
// Set button status
|
||||||
|
@ -231,7 +231,7 @@ Input::TriggerStatus TransformToTrigger(const Input::CallbackStatus& callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SanitizeAnalog(Input::AnalogStatus& analog, bool clamp_value) {
|
void SanitizeAnalog(Input::AnalogStatus& analog, bool clamp_value) {
|
||||||
const Input::AnalogProperties& properties = analog.properties;
|
const auto& properties = analog.properties;
|
||||||
float& raw_value = analog.raw_value;
|
float& raw_value = analog.raw_value;
|
||||||
float& value = analog.value;
|
float& value = analog.value;
|
||||||
|
|
||||||
|
@ -271,8 +271,8 @@ void SanitizeAnalog(Input::AnalogStatus& analog, bool clamp_value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SanitizeStick(Input::AnalogStatus& analog_x, Input::AnalogStatus& analog_y, bool clamp_value) {
|
void SanitizeStick(Input::AnalogStatus& analog_x, Input::AnalogStatus& analog_y, bool clamp_value) {
|
||||||
const Input::AnalogProperties& properties_x = analog_x.properties;
|
const auto& properties_x = analog_x.properties;
|
||||||
const Input::AnalogProperties& properties_y = analog_y.properties;
|
const auto& properties_y = analog_y.properties;
|
||||||
float& raw_x = analog_x.raw_value;
|
float& raw_x = analog_x.raw_value;
|
||||||
float& raw_y = analog_y.raw_value;
|
float& raw_y = analog_y.raw_value;
|
||||||
float& x = analog_x.value;
|
float& x = analog_x.value;
|
||||||
|
|
|
@ -35,8 +35,8 @@ public:
|
||||||
private:
|
private:
|
||||||
struct SevenSixAxisState {
|
struct SevenSixAxisState {
|
||||||
INSERT_PADDING_WORDS(4); // unused
|
INSERT_PADDING_WORDS(4); // unused
|
||||||
s64_le sampling_number{};
|
s64 sampling_number{};
|
||||||
s64_le sampling_number2{};
|
s64 sampling_number2{};
|
||||||
u64 unknown{};
|
u64 unknown{};
|
||||||
Common::Vec3f accel{};
|
Common::Vec3f accel{};
|
||||||
Common::Vec3f gyro{};
|
Common::Vec3f gyro{};
|
||||||
|
@ -45,10 +45,10 @@ private:
|
||||||
static_assert(sizeof(SevenSixAxisState) == 0x50, "SevenSixAxisState is an invalid size");
|
static_assert(sizeof(SevenSixAxisState) == 0x50, "SevenSixAxisState is an invalid size");
|
||||||
|
|
||||||
struct CommonHeader {
|
struct CommonHeader {
|
||||||
s64_le timestamp;
|
s64 timestamp;
|
||||||
s64_le total_entry_count;
|
s64 total_entry_count;
|
||||||
s64_le last_entry_index;
|
s64 last_entry_index;
|
||||||
s64_le entry_count;
|
s64 entry_count;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size");
|
static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size");
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ private:
|
||||||
|
|
||||||
// This is nn::hid::detail::ConsoleSixAxisSensorSharedMemoryFormat
|
// This is nn::hid::detail::ConsoleSixAxisSensorSharedMemoryFormat
|
||||||
struct ConsoleSharedMemory {
|
struct ConsoleSharedMemory {
|
||||||
u64_le sampling_number{};
|
u64 sampling_number{};
|
||||||
bool is_seven_six_axis_sensor_at_rest{};
|
bool is_seven_six_axis_sensor_at_rest{};
|
||||||
f32 verticalization_error{};
|
f32 verticalization_error{};
|
||||||
Common::Vec3f gyro_bias{};
|
Common::Vec3f gyro_bias{};
|
||||||
|
|
|
@ -38,7 +38,7 @@ private:
|
||||||
// This is nn::hid::DebugPadAttribute
|
// This is nn::hid::DebugPadAttribute
|
||||||
struct DebugPadAttribute {
|
struct DebugPadAttribute {
|
||||||
union {
|
union {
|
||||||
u32_le raw{};
|
u32 raw{};
|
||||||
BitField<0, 1, u32> connected;
|
BitField<0, 1, u32> connected;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -46,7 +46,7 @@ private:
|
||||||
|
|
||||||
// This is nn::hid::DebugPadState
|
// This is nn::hid::DebugPadState
|
||||||
struct DebugPadState {
|
struct DebugPadState {
|
||||||
s64_le sampling_number;
|
s64 sampling_number;
|
||||||
DebugPadAttribute attribute;
|
DebugPadAttribute attribute;
|
||||||
Core::HID::DebugPadButton pad_state;
|
Core::HID::DebugPadButton pad_state;
|
||||||
Core::HID::AnalogStickState r_stick;
|
Core::HID::AnalogStickState r_stick;
|
||||||
|
|
|
@ -65,10 +65,7 @@ void Controller_Gesture::OnUpdate(const Core::Timing::CoreTiming& core_timing, u
|
||||||
void Controller_Gesture::ReadTouchInput() {
|
void Controller_Gesture::ReadTouchInput() {
|
||||||
const auto touch_status = console->GetTouch();
|
const auto touch_status = console->GetTouch();
|
||||||
for (std::size_t id = 0; id < fingers.size(); ++id) {
|
for (std::size_t id = 0; id < fingers.size(); ++id) {
|
||||||
const Core::HID::TouchFinger& status = touch_status[id];
|
fingers[id] = touch_status[id];
|
||||||
Finger& finger = fingers[id];
|
|
||||||
finger.pos = status.position;
|
|
||||||
finger.pressed = status.pressed;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,14 +312,14 @@ const Controller_Gesture::GestureState& Controller_Gesture::GetLastGestureEntry(
|
||||||
|
|
||||||
Controller_Gesture::GestureProperties Controller_Gesture::GetGestureProperties() {
|
Controller_Gesture::GestureProperties Controller_Gesture::GetGestureProperties() {
|
||||||
GestureProperties gesture;
|
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 end_iter = std::copy_if(fingers.begin(), fingers.end(), active_fingers.begin(),
|
||||||
[](const auto& finger) { return finger.pressed; });
|
[](const auto& finger) { return finger.pressed; });
|
||||||
gesture.active_points =
|
gesture.active_points =
|
||||||
static_cast<std::size_t>(std::distance(active_fingers.begin(), end_iter));
|
static_cast<std::size_t>(std::distance(active_fingers.begin(), end_iter));
|
||||||
|
|
||||||
for (size_t id = 0; id < gesture.active_points; ++id) {
|
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] = {
|
gesture.points[id] = {
|
||||||
.x = static_cast<s32>(active_x * Layout::ScreenUndocked::Width),
|
.x = static_cast<s32>(active_x * Layout::ScreenUndocked::Width),
|
||||||
.y = static_cast<s32>(active_y * Layout::ScreenUndocked::Height),
|
.y = static_cast<s32>(active_y * Layout::ScreenUndocked::Height),
|
||||||
|
|
|
@ -60,7 +60,7 @@ private:
|
||||||
// This is nn::hid::GestureAttribute
|
// This is nn::hid::GestureAttribute
|
||||||
struct GestureAttribute {
|
struct GestureAttribute {
|
||||||
union {
|
union {
|
||||||
u32_le raw{};
|
u32 raw{};
|
||||||
|
|
||||||
BitField<4, 1, u32> is_new_touch;
|
BitField<4, 1, u32> is_new_touch;
|
||||||
BitField<8, 1, u32> is_double_tap;
|
BitField<8, 1, u32> is_double_tap;
|
||||||
|
@ -70,33 +70,28 @@ private:
|
||||||
|
|
||||||
// This is nn::hid::GestureState
|
// This is nn::hid::GestureState
|
||||||
struct GestureState {
|
struct GestureState {
|
||||||
s64_le sampling_number;
|
s64 sampling_number;
|
||||||
s64_le detection_count;
|
s64 detection_count;
|
||||||
GestureType type;
|
GestureType type;
|
||||||
GestureDirection direction;
|
GestureDirection direction;
|
||||||
Common::Point<s32_le> pos;
|
Common::Point<s32> pos;
|
||||||
Common::Point<s32_le> delta;
|
Common::Point<s32> delta;
|
||||||
f32 vel_x;
|
f32 vel_x;
|
||||||
f32 vel_y;
|
f32 vel_y;
|
||||||
GestureAttribute attributes;
|
GestureAttribute attributes;
|
||||||
f32 scale;
|
f32 scale;
|
||||||
f32 rotation_angle;
|
f32 rotation_angle;
|
||||||
s32_le point_count;
|
s32 point_count;
|
||||||
std::array<Common::Point<s32_le>, 4> points;
|
std::array<Common::Point<s32>, 4> points;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(GestureState) == 0x60, "GestureState is an invalid size");
|
static_assert(sizeof(GestureState) == 0x60, "GestureState is an invalid size");
|
||||||
|
|
||||||
struct Finger {
|
|
||||||
Common::Point<f32> pos{};
|
|
||||||
bool pressed{};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GestureProperties {
|
struct GestureProperties {
|
||||||
std::array<Common::Point<s32_le>, MAX_POINTS> points{};
|
std::array<Common::Point<s32>, MAX_POINTS> points{};
|
||||||
std::size_t active_points{};
|
std::size_t active_points{};
|
||||||
Common::Point<s32_le> mid_point{};
|
Common::Point<s32> mid_point{};
|
||||||
s64_le detection_count{};
|
s64 detection_count{};
|
||||||
u64_le delta_time{};
|
u64 delta_time{};
|
||||||
f32 average_distance{};
|
f32 average_distance{};
|
||||||
f32 angle{};
|
f32 angle{};
|
||||||
};
|
};
|
||||||
|
@ -150,10 +145,10 @@ private:
|
||||||
|
|
||||||
Core::HID::EmulatedConsole* console;
|
Core::HID::EmulatedConsole* console;
|
||||||
|
|
||||||
std::array<Finger, MAX_POINTS> fingers{};
|
std::array<Core::HID::TouchFinger, MAX_POINTS> fingers{};
|
||||||
GestureProperties last_gesture{};
|
GestureProperties last_gesture{};
|
||||||
s64_le last_update_timestamp{};
|
s64 last_update_timestamp{};
|
||||||
s64_le last_tap_timestamp{};
|
s64 last_tap_timestamp{};
|
||||||
f32 last_pan_time_difference{};
|
f32 last_pan_time_difference{};
|
||||||
bool force_update{false};
|
bool force_update{false};
|
||||||
bool enable_press_and_tap{false};
|
bool enable_press_and_tap{false};
|
||||||
|
|
|
@ -37,7 +37,7 @@ public:
|
||||||
private:
|
private:
|
||||||
// This is nn::hid::detail::KeyboardState
|
// This is nn::hid::detail::KeyboardState
|
||||||
struct KeyboardState {
|
struct KeyboardState {
|
||||||
s64_le sampling_number;
|
s64 sampling_number;
|
||||||
Core::HID::KeyboardModifier modifier;
|
Core::HID::KeyboardModifier modifier;
|
||||||
Core::HID::KeyboardKey key;
|
Core::HID::KeyboardKey key;
|
||||||
};
|
};
|
||||||
|
|
|
@ -106,7 +106,7 @@ Controller_NPad::Controller_NPad(Core::System& system_,
|
||||||
Core::HID::ControllerUpdateCallback engine_callback{
|
Core::HID::ControllerUpdateCallback engine_callback{
|
||||||
.on_change = [this,
|
.on_change = [this,
|
||||||
i](Core::HID::ControllerTriggerType type) { ControllerUpdate(type, i); },
|
i](Core::HID::ControllerTriggerType type) { ControllerUpdate(type, i); },
|
||||||
.is_service = true,
|
.is_npad_service = true,
|
||||||
};
|
};
|
||||||
controller.callback_key = controller.device->SetCallback(engine_callback);
|
controller.callback_key = controller.device->SetCallback(engine_callback);
|
||||||
}
|
}
|
||||||
|
@ -157,7 +157,6 @@ void Controller_NPad::ControllerUpdate(Core::HID::ControllerTriggerType type,
|
||||||
|
|
||||||
void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) {
|
void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) {
|
||||||
auto& controller = controller_data[controller_idx];
|
auto& controller = controller_data[controller_idx];
|
||||||
LOG_WARNING(Service_HID, "Connect {} {}", controller_idx, controller.is_connected);
|
|
||||||
const auto controller_type = controller.device->GetNpadType();
|
const auto controller_type = controller.device->GetNpadType();
|
||||||
auto& shared_memory = controller.shared_memory_entry;
|
auto& shared_memory = controller.shared_memory_entry;
|
||||||
if (controller_type == Core::HID::NpadType::None) {
|
if (controller_type == Core::HID::NpadType::None) {
|
||||||
|
@ -892,7 +891,6 @@ void Controller_NPad::DisconnectNpad(u32 npad_id) {
|
||||||
|
|
||||||
void Controller_NPad::DisconnectNpadAtIndex(std::size_t npad_index) {
|
void Controller_NPad::DisconnectNpadAtIndex(std::size_t npad_index) {
|
||||||
auto& controller = controller_data[npad_index];
|
auto& controller = controller_data[npad_index];
|
||||||
LOG_WARNING(Service_HID, "Disconnect {} {}", npad_index, controller.is_connected);
|
|
||||||
for (std::size_t device_idx = 0; device_idx < controller.vibration.size(); ++device_idx) {
|
for (std::size_t device_idx = 0; device_idx < controller.vibration.size(); ++device_idx) {
|
||||||
// Send an empty vibration to stop any vibrations.
|
// Send an empty vibration to stop any vibrations.
|
||||||
VibrateControllerAtIndex(npad_index, device_idx, {});
|
VibrateControllerAtIndex(npad_index, device_idx, {});
|
||||||
|
|
|
@ -195,7 +195,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// This is nn::hid::detail::ColorAttribute
|
// This is nn::hid::detail::ColorAttribute
|
||||||
enum class ColorAttribute : u32_le {
|
enum class ColorAttribute : u32 {
|
||||||
Ok = 0,
|
Ok = 0,
|
||||||
ReadError = 1,
|
ReadError = 1,
|
||||||
NoController = 2,
|
NoController = 2,
|
||||||
|
@ -220,7 +220,7 @@ private:
|
||||||
// This is nn::hid::NpadAttribute
|
// This is nn::hid::NpadAttribute
|
||||||
struct NpadAttribute {
|
struct NpadAttribute {
|
||||||
union {
|
union {
|
||||||
u32_le raw{};
|
u32 raw{};
|
||||||
BitField<0, 1, u32> is_connected;
|
BitField<0, 1, u32> is_connected;
|
||||||
BitField<1, 1, u32> is_wired;
|
BitField<1, 1, u32> is_wired;
|
||||||
BitField<2, 1, u32> is_left_connected;
|
BitField<2, 1, u32> is_left_connected;
|
||||||
|
@ -251,7 +251,7 @@ private:
|
||||||
// This is nn::hid::SixAxisSensorAttribute
|
// This is nn::hid::SixAxisSensorAttribute
|
||||||
struct SixAxisSensorAttribute {
|
struct SixAxisSensorAttribute {
|
||||||
union {
|
union {
|
||||||
u32_le raw{};
|
u32 raw{};
|
||||||
BitField<0, 1, u32> is_connected;
|
BitField<0, 1, u32> is_connected;
|
||||||
BitField<1, 1, u32> is_interpolated;
|
BitField<1, 1, u32> is_interpolated;
|
||||||
};
|
};
|
||||||
|
@ -260,8 +260,8 @@ private:
|
||||||
|
|
||||||
// This is nn::hid::SixAxisSensorState
|
// This is nn::hid::SixAxisSensorState
|
||||||
struct SixAxisSensorState {
|
struct SixAxisSensorState {
|
||||||
s64_le delta_time{};
|
s64 delta_time{};
|
||||||
s64_le sampling_number{};
|
s64 sampling_number{};
|
||||||
Common::Vec3f accel{};
|
Common::Vec3f accel{};
|
||||||
Common::Vec3f gyro{};
|
Common::Vec3f gyro{};
|
||||||
Common::Vec3f rotation{};
|
Common::Vec3f rotation{};
|
||||||
|
@ -273,16 +273,16 @@ private:
|
||||||
|
|
||||||
// This is nn::hid::server::NpadGcTriggerState
|
// This is nn::hid::server::NpadGcTriggerState
|
||||||
struct NpadGcTriggerState {
|
struct NpadGcTriggerState {
|
||||||
s64_le sampling_number{};
|
s64 sampling_number{};
|
||||||
s32_le l_analog{};
|
s32 l_analog{};
|
||||||
s32_le r_analog{};
|
s32 r_analog{};
|
||||||
};
|
};
|
||||||
static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size");
|
static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size");
|
||||||
|
|
||||||
// This is nn::hid::NpadSystemProperties
|
// This is nn::hid::NpadSystemProperties
|
||||||
struct NPadSystemProperties {
|
struct NPadSystemProperties {
|
||||||
union {
|
union {
|
||||||
s64_le raw{};
|
s64 raw{};
|
||||||
BitField<0, 1, s64> is_charging_joy_dual;
|
BitField<0, 1, s64> is_charging_joy_dual;
|
||||||
BitField<1, 1, s64> is_charging_joy_left;
|
BitField<1, 1, s64> is_charging_joy_left;
|
||||||
BitField<2, 1, s64> is_charging_joy_right;
|
BitField<2, 1, s64> is_charging_joy_right;
|
||||||
|
@ -303,7 +303,7 @@ private:
|
||||||
// This is nn::hid::NpadSystemButtonProperties
|
// This is nn::hid::NpadSystemButtonProperties
|
||||||
struct NpadSystemButtonProperties {
|
struct NpadSystemButtonProperties {
|
||||||
union {
|
union {
|
||||||
s32_le raw{};
|
s32 raw{};
|
||||||
BitField<0, 1, s32> is_home_button_protection_enabled;
|
BitField<0, 1, s32> is_home_button_protection_enabled;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -313,7 +313,7 @@ private:
|
||||||
// This is nn::hid::system::DeviceType
|
// This is nn::hid::system::DeviceType
|
||||||
struct DeviceType {
|
struct DeviceType {
|
||||||
union {
|
union {
|
||||||
u32_le raw{};
|
u32 raw{};
|
||||||
BitField<0, 1, s32> fullkey;
|
BitField<0, 1, s32> fullkey;
|
||||||
BitField<1, 1, s32> debug_pad;
|
BitField<1, 1, s32> debug_pad;
|
||||||
BitField<2, 1, s32> handheld_left;
|
BitField<2, 1, s32> handheld_left;
|
||||||
|
|
|
@ -26,10 +26,10 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct CommonHeader {
|
struct CommonHeader {
|
||||||
s64_le timestamp;
|
s64 timestamp;
|
||||||
s64_le total_entry_count;
|
s64 total_entry_count;
|
||||||
s64_le last_entry_index;
|
s64 last_entry_index;
|
||||||
s64_le entry_count;
|
s64 entry_count;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size");
|
static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size");
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::array<Finger, MAX_FINGERS> 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 end_iter = std::copy_if(fingers.begin(), fingers.end(), active_fingers.begin(),
|
||||||
[](const auto& finger) { return finger.pressed; });
|
[](const auto& finger) { return finger.pressed; });
|
||||||
const auto active_fingers_count =
|
const auto active_fingers_count =
|
||||||
|
@ -76,7 +76,7 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin
|
||||||
const auto& last_entry = touch_screen_lifo.ReadCurrentEntry().state;
|
const auto& last_entry = touch_screen_lifo.ReadCurrentEntry().state;
|
||||||
|
|
||||||
next_state.sampling_number = last_entry.sampling_number + 1;
|
next_state.sampling_number = last_entry.sampling_number + 1;
|
||||||
next_state.entry_count = static_cast<s32_le>(active_fingers_count);
|
next_state.entry_count = static_cast<s32>(active_fingers_count);
|
||||||
|
|
||||||
for (std::size_t id = 0; id < MAX_FINGERS; ++id) {
|
for (std::size_t id = 0; id < MAX_FINGERS; ++id) {
|
||||||
auto& touch_entry = next_state.states[id];
|
auto& touch_entry = next_state.states[id];
|
||||||
|
|
|
@ -50,27 +50,19 @@ private:
|
||||||
|
|
||||||
// This is nn::hid::TouchScreenState
|
// This is nn::hid::TouchScreenState
|
||||||
struct TouchScreenState {
|
struct TouchScreenState {
|
||||||
s64_le sampling_number;
|
s64 sampling_number;
|
||||||
s32_le entry_count;
|
s32 entry_count;
|
||||||
INSERT_PADDING_BYTES(4); // Reserved
|
INSERT_PADDING_BYTES(4); // Reserved
|
||||||
std::array<Core::HID::TouchState, MAX_FINGERS> states;
|
std::array<Core::HID::TouchState, MAX_FINGERS> states;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(TouchScreenState) == 0x290, "TouchScreenState is an invalid size");
|
static_assert(sizeof(TouchScreenState) == 0x290, "TouchScreenState is an invalid size");
|
||||||
|
|
||||||
struct Finger {
|
|
||||||
u64_le last_touch{};
|
|
||||||
Common::Point<float> position;
|
|
||||||
u32_le id{};
|
|
||||||
bool pressed{};
|
|
||||||
Core::HID::TouchAttribute attribute;
|
|
||||||
};
|
|
||||||
|
|
||||||
// This is nn::hid::detail::TouchScreenLifo
|
// This is nn::hid::detail::TouchScreenLifo
|
||||||
Lifo<TouchScreenState> touch_screen_lifo{};
|
Lifo<TouchScreenState> touch_screen_lifo{};
|
||||||
static_assert(sizeof(touch_screen_lifo) == 0x2C38, "touch_screen_lifo is an invalid size");
|
static_assert(sizeof(touch_screen_lifo) == 0x2C38, "touch_screen_lifo is an invalid size");
|
||||||
TouchScreenState next_state{};
|
TouchScreenState next_state{};
|
||||||
|
|
||||||
std::array<Finger, MAX_FINGERS> fingers;
|
std::array<Core::HID::TouchFinger, MAX_FINGERS> fingers;
|
||||||
Core::HID::EmulatedConsole* console;
|
Core::HID::EmulatedConsole* console;
|
||||||
};
|
};
|
||||||
} // namespace Service::HID
|
} // namespace Service::HID
|
||||||
|
|
|
@ -31,7 +31,7 @@ private:
|
||||||
// This is nn::hid::BasicXpadAttributeSet
|
// This is nn::hid::BasicXpadAttributeSet
|
||||||
struct BasicXpadAttributeSet {
|
struct BasicXpadAttributeSet {
|
||||||
union {
|
union {
|
||||||
u32_le raw{};
|
u32 raw{};
|
||||||
BitField<0, 1, u32> is_connected;
|
BitField<0, 1, u32> is_connected;
|
||||||
BitField<1, 1, u32> is_wired;
|
BitField<1, 1, u32> is_wired;
|
||||||
BitField<2, 1, u32> is_left_connected;
|
BitField<2, 1, u32> is_left_connected;
|
||||||
|
@ -45,7 +45,7 @@ private:
|
||||||
// This is nn::hid::BasicXpadButtonSet
|
// This is nn::hid::BasicXpadButtonSet
|
||||||
struct BasicXpadButtonSet {
|
struct BasicXpadButtonSet {
|
||||||
union {
|
union {
|
||||||
u32_le raw{};
|
u32 raw{};
|
||||||
// Button states
|
// Button states
|
||||||
BitField<0, 1, u32> a;
|
BitField<0, 1, u32> a;
|
||||||
BitField<1, 1, u32> b;
|
BitField<1, 1, u32> b;
|
||||||
|
@ -93,7 +93,7 @@ private:
|
||||||
|
|
||||||
// This is nn::hid::detail::BasicXpadState
|
// This is nn::hid::detail::BasicXpadState
|
||||||
struct BasicXpadState {
|
struct BasicXpadState {
|
||||||
s64_le sampling_number;
|
s64 sampling_number;
|
||||||
BasicXpadAttributeSet attributes;
|
BasicXpadAttributeSet attributes;
|
||||||
BasicXpadButtonSet pad_states;
|
BasicXpadButtonSet pad_states;
|
||||||
Core::HID::AnalogStickState l_stick;
|
Core::HID::AnalogStickState l_stick;
|
||||||
|
|
|
@ -12,16 +12,16 @@ constexpr std::size_t max_entry_size = 17;
|
||||||
|
|
||||||
template <typename State>
|
template <typename State>
|
||||||
struct AtomicStorage {
|
struct AtomicStorage {
|
||||||
s64_le sampling_number;
|
s64 sampling_number;
|
||||||
State state;
|
State state;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename State>
|
template <typename State>
|
||||||
struct Lifo {
|
struct Lifo {
|
||||||
s64_le timestamp{};
|
s64 timestamp{};
|
||||||
s64_le total_entry_count = max_entry_size;
|
s64 total_entry_count = max_entry_size;
|
||||||
s64_le last_entry_index{};
|
s64 last_entry_index{};
|
||||||
s64_le entry_count{};
|
s64 entry_count{};
|
||||||
std::array<AtomicStorage<State>, max_entry_size> entries{};
|
std::array<AtomicStorage<State>, max_entry_size> entries{};
|
||||||
|
|
||||||
const AtomicStorage<State>& ReadCurrentEntry() const {
|
const AtomicStorage<State>& ReadCurrentEntry() const {
|
||||||
|
|
|
@ -28,7 +28,7 @@ void PlayerControlPreview::SetController(Core::HID::EmulatedController* controll
|
||||||
controller = controller_;
|
controller = controller_;
|
||||||
Core::HID::ControllerUpdateCallback engine_callback{
|
Core::HID::ControllerUpdateCallback engine_callback{
|
||||||
.on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdate(type); },
|
.on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdate(type); },
|
||||||
.is_service = false,
|
.is_npad_service = false,
|
||||||
};
|
};
|
||||||
callback_key = controller->SetCallback(engine_callback);
|
callback_key = controller->SetCallback(engine_callback);
|
||||||
ControllerUpdate(Core::HID::ControllerTriggerType::All);
|
ControllerUpdate(Core::HID::ControllerTriggerType::All);
|
||||||
|
|
Loading…
Reference in a new issue