early-access version 2177
This commit is contained in:
parent
2b3decfff3
commit
0d005353e8
115 changed files with 13315 additions and 3835 deletions
|
@ -1,7 +1,7 @@
|
|||
yuzu emulator early access
|
||||
=============
|
||||
|
||||
This is the source code for early-access 2176.
|
||||
This is the source code for early-access 2177.
|
||||
|
||||
## Legal Notice
|
||||
|
||||
|
|
|
@ -72,6 +72,7 @@ add_library(common STATIC
|
|||
hex_util.h
|
||||
host_memory.cpp
|
||||
host_memory.h
|
||||
input.h
|
||||
intrusive_red_black_tree.h
|
||||
literals.h
|
||||
logging/backend.cpp
|
||||
|
|
304
src/common/input.h
Executable file
304
src/common/input.h
Executable file
|
@ -0,0 +1,304 @@
|
|||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/param_package.h"
|
||||
|
||||
namespace Common::Input {
|
||||
|
||||
enum class InputType {
|
||||
None,
|
||||
Battery,
|
||||
Button,
|
||||
Stick,
|
||||
Analog,
|
||||
Trigger,
|
||||
Motion,
|
||||
Touch,
|
||||
Color,
|
||||
Vibration,
|
||||
Nfc,
|
||||
Ir,
|
||||
};
|
||||
|
||||
enum class BatteryLevel : u32 {
|
||||
None,
|
||||
Empty,
|
||||
Critical,
|
||||
Low,
|
||||
Medium,
|
||||
Full,
|
||||
Charging,
|
||||
};
|
||||
|
||||
enum class PollingMode {
|
||||
Active,
|
||||
Pasive,
|
||||
Camera,
|
||||
NCF,
|
||||
IR,
|
||||
};
|
||||
|
||||
enum class VibrationError {
|
||||
None,
|
||||
NotSupported,
|
||||
Disabled,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
enum class PollingError {
|
||||
None,
|
||||
NotSupported,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
// Hint for amplification curve to be used
|
||||
enum class VibrationAmplificationType {
|
||||
Linear,
|
||||
Exponential,
|
||||
};
|
||||
|
||||
struct AnalogProperties {
|
||||
float deadzone{};
|
||||
float range{1.0f};
|
||||
float threshold{0.5f};
|
||||
float offset{};
|
||||
bool inverted{};
|
||||
};
|
||||
|
||||
struct AnalogStatus {
|
||||
float value{};
|
||||
float raw_value{};
|
||||
AnalogProperties properties{};
|
||||
};
|
||||
|
||||
struct ButtonStatus {
|
||||
bool value{};
|
||||
bool inverted{};
|
||||
bool toggle{};
|
||||
bool locked{};
|
||||
};
|
||||
|
||||
using BatteryStatus = BatteryLevel;
|
||||
|
||||
struct StickStatus {
|
||||
AnalogStatus x{};
|
||||
AnalogStatus y{};
|
||||
bool left{};
|
||||
bool right{};
|
||||
bool up{};
|
||||
bool down{};
|
||||
};
|
||||
|
||||
struct TriggerStatus {
|
||||
AnalogStatus analog{};
|
||||
ButtonStatus pressed{};
|
||||
};
|
||||
|
||||
struct MotionSensor {
|
||||
AnalogStatus x{};
|
||||
AnalogStatus y{};
|
||||
AnalogStatus z{};
|
||||
};
|
||||
|
||||
struct MotionStatus {
|
||||
// Gyroscope vector measurement in radians/s.
|
||||
MotionSensor gyro{};
|
||||
// Acceleration vector measurement in G force
|
||||
MotionSensor accel{};
|
||||
// Time since last measurement in microseconds
|
||||
u64 delta_timestamp{};
|
||||
// Request to update after reading the value
|
||||
bool force_update{};
|
||||
};
|
||||
|
||||
struct TouchStatus {
|
||||
ButtonStatus pressed{};
|
||||
AnalogStatus x{};
|
||||
AnalogStatus y{};
|
||||
int id{};
|
||||
};
|
||||
|
||||
struct BodyColorStatus {
|
||||
u32 body{};
|
||||
u32 buttons{};
|
||||
};
|
||||
|
||||
struct VibrationStatus {
|
||||
f32 low_amplitude{};
|
||||
f32 low_frequency{};
|
||||
f32 high_amplitude{};
|
||||
f32 high_frequency{};
|
||||
VibrationAmplificationType type;
|
||||
};
|
||||
|
||||
struct LedStatus {
|
||||
bool led_1{};
|
||||
bool led_2{};
|
||||
bool led_3{};
|
||||
bool led_4{};
|
||||
};
|
||||
|
||||
struct CallbackStatus {
|
||||
InputType type{InputType::None};
|
||||
ButtonStatus button_status{};
|
||||
StickStatus stick_status{};
|
||||
AnalogStatus analog_status{};
|
||||
TriggerStatus trigger_status{};
|
||||
MotionStatus motion_status{};
|
||||
TouchStatus touch_status{};
|
||||
BodyColorStatus color_status{};
|
||||
BatteryStatus battery_status{};
|
||||
VibrationStatus vibration_status{};
|
||||
};
|
||||
|
||||
struct InputCallback {
|
||||
std::function<void(CallbackStatus)> on_change;
|
||||
};
|
||||
|
||||
/// An abstract class template for an input device (a button, an analog input, etc.).
|
||||
class InputDevice {
|
||||
public:
|
||||
virtual ~InputDevice() = default;
|
||||
|
||||
// Request input device to update if necessary
|
||||
virtual void SoftUpdate() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Force input device to update data regarless of the current state
|
||||
virtual void ForceUpdate() {
|
||||
return;
|
||||
}
|
||||
|
||||
void SetCallback(InputCallback callback_) {
|
||||
callback = std::move(callback_);
|
||||
}
|
||||
|
||||
void TriggerOnChange(CallbackStatus status) {
|
||||
if (callback.on_change) {
|
||||
callback.on_change(status);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
InputCallback callback;
|
||||
};
|
||||
|
||||
/// An abstract class template for an output device (rumble, LED pattern, polling mode).
|
||||
class OutputDevice {
|
||||
public:
|
||||
virtual ~OutputDevice() = default;
|
||||
|
||||
virtual void SetLED([[maybe_unused]] LedStatus led_status) {
|
||||
return;
|
||||
}
|
||||
|
||||
virtual VibrationError SetVibration([[maybe_unused]] VibrationStatus vibration_status) {
|
||||
return VibrationError::NotSupported;
|
||||
}
|
||||
|
||||
virtual PollingError SetPollingMode([[maybe_unused]] PollingMode polling_mode) {
|
||||
return PollingError::NotSupported;
|
||||
}
|
||||
};
|
||||
|
||||
/// An abstract class template for a factory that can create input devices.
|
||||
template <typename InputDeviceType>
|
||||
class Factory {
|
||||
public:
|
||||
virtual ~Factory() = default;
|
||||
virtual std::unique_ptr<InputDeviceType> Create(const Common::ParamPackage&) = 0;
|
||||
};
|
||||
|
||||
namespace Impl {
|
||||
|
||||
template <typename InputDeviceType>
|
||||
using FactoryListType = std::unordered_map<std::string, std::shared_ptr<Factory<InputDeviceType>>>;
|
||||
|
||||
template <typename InputDeviceType>
|
||||
struct FactoryList {
|
||||
static FactoryListType<InputDeviceType> list;
|
||||
};
|
||||
|
||||
template <typename InputDeviceType>
|
||||
FactoryListType<InputDeviceType> FactoryList<InputDeviceType>::list;
|
||||
|
||||
} // namespace Impl
|
||||
|
||||
/**
|
||||
* Registers an input device factory.
|
||||
* @tparam InputDeviceType the type of input devices the factory can create
|
||||
* @param name the name of the factory. Will be used to match the "engine" parameter when creating
|
||||
* a device
|
||||
* @param factory the factory object to register
|
||||
*/
|
||||
template <typename InputDeviceType>
|
||||
void RegisterFactory(const std::string& name, std::shared_ptr<Factory<InputDeviceType>> factory) {
|
||||
auto pair = std::make_pair(name, std::move(factory));
|
||||
if (!Impl::FactoryList<InputDeviceType>::list.insert(std::move(pair)).second) {
|
||||
LOG_ERROR(Input, "Factory '{}' already registered", name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters an input device factory.
|
||||
* @tparam InputDeviceType the type of input devices the factory can create
|
||||
* @param name the name of the factory to unregister
|
||||
*/
|
||||
template <typename InputDeviceType>
|
||||
void UnregisterFactory(const std::string& name) {
|
||||
if (Impl::FactoryList<InputDeviceType>::list.erase(name) == 0) {
|
||||
LOG_ERROR(Input, "Factory '{}' not registered", name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an input device from given paramters.
|
||||
* @tparam InputDeviceType the type of input devices to create
|
||||
* @param params a serialized ParamPackage string that contains all parameters for creating the
|
||||
* device
|
||||
*/
|
||||
template <typename InputDeviceType>
|
||||
std::unique_ptr<InputDeviceType> CreateDeviceFromString(const std::string& params) {
|
||||
const Common::ParamPackage package(params);
|
||||
const std::string engine = package.Get("engine", "null");
|
||||
const auto& factory_list = Impl::FactoryList<InputDeviceType>::list;
|
||||
const auto pair = factory_list.find(engine);
|
||||
if (pair == factory_list.end()) {
|
||||
if (engine != "null") {
|
||||
LOG_ERROR(Input, "Unknown engine name: {}", engine);
|
||||
}
|
||||
return std::make_unique<InputDeviceType>();
|
||||
}
|
||||
return pair->second->Create(package);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an input device from given paramters.
|
||||
* @tparam InputDeviceType the type of input devices to create
|
||||
* @param A ParamPackage that contains all parameters for creating the device
|
||||
*/
|
||||
template <typename InputDeviceType>
|
||||
std::unique_ptr<InputDeviceType> CreateDevice(const Common::ParamPackage package) {
|
||||
const std::string engine = package.Get("engine", "null");
|
||||
const auto& factory_list = Impl::FactoryList<InputDeviceType>::list;
|
||||
const auto pair = factory_list.find(engine);
|
||||
if (pair == factory_list.end()) {
|
||||
if (engine != "null") {
|
||||
LOG_ERROR(Input, "Unknown engine name: {}", engine);
|
||||
}
|
||||
return std::make_unique<InputDeviceType>();
|
||||
}
|
||||
return pair->second->Create(package);
|
||||
}
|
||||
|
||||
} // namespace Common::Input
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
@ -567,12 +566,10 @@ struct Values {
|
|||
BasicSetting<bool> pause_tas_on_load{true, "pause_tas_on_load"};
|
||||
BasicSetting<bool> tas_enable{false, "tas_enable"};
|
||||
BasicSetting<bool> tas_loop{false, "tas_loop"};
|
||||
BasicSetting<bool> tas_swap_controllers{true, "tas_swap_controllers"};
|
||||
|
||||
BasicSetting<bool> mouse_panning{false, "mouse_panning"};
|
||||
BasicRangedSetting<u8> mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"};
|
||||
BasicSetting<bool> mouse_enabled{false, "mouse_enabled"};
|
||||
std::string mouse_device;
|
||||
MouseButtonsRaw mouse_buttons;
|
||||
|
||||
BasicSetting<bool> emulate_analog_keyboard{false, "emulate_analog_keyboard"};
|
||||
|
@ -592,8 +589,6 @@ struct Values {
|
|||
BasicSetting<int> touch_from_button_map_index{0, "touch_from_button_map"};
|
||||
std::vector<TouchFromButtonMap> touch_from_button_maps;
|
||||
|
||||
std::atomic_bool is_device_reload_pending{true};
|
||||
|
||||
// Data Storage
|
||||
BasicSetting<bool> use_virtual_sd{true, "use_virtual_sd"};
|
||||
BasicSetting<bool> gamecard_inserted{false, "gamecard_inserted"};
|
||||
|
|
|
@ -62,11 +62,22 @@ enum Values : int {
|
|||
|
||||
constexpr int STICK_HID_BEGIN = LStick;
|
||||
constexpr int STICK_HID_END = NumAnalogs;
|
||||
constexpr int NUM_STICKS_HID = NumAnalogs;
|
||||
|
||||
extern const std::array<const char*, NumAnalogs> mapping;
|
||||
} // namespace NativeAnalog
|
||||
|
||||
namespace NativeTrigger {
|
||||
enum Values : int {
|
||||
LTrigger,
|
||||
RTrigger,
|
||||
|
||||
NumTriggers,
|
||||
};
|
||||
|
||||
constexpr int TRIGGER_HID_BEGIN = LTrigger;
|
||||
constexpr int TRIGGER_HID_END = NumTriggers;
|
||||
} // namespace NativeTrigger
|
||||
|
||||
namespace NativeVibration {
|
||||
enum Values : int {
|
||||
LeftVibrationDevice,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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!");
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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
219
src/core/hid/emulated_console.cpp
Executable 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
189
src/core/hid/emulated_console.h
Executable 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
|
1012
src/core/hid/emulated_controller.cpp
Executable file
1012
src/core/hid/emulated_controller.cpp
Executable file
File diff suppressed because it is too large
Load diff
392
src/core/hid/emulated_controller.h
Executable file
392
src/core/hid/emulated_controller.h
Executable 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
372
src/core/hid/emulated_devices.cpp
Executable 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
196
src/core/hid/emulated_devices.h
Executable 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
168
src/core/hid/hid_core.cpp
Executable 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
73
src/core/hid/hid_core.h
Executable 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
416
src/core/hid/hid_types.h
Executable 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
355
src/core/hid/input_converter.cpp
Executable 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
86
src/core/hid/input_converter.h
Executable 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
|
61
src/core/hid/input_interpreter.cpp
Executable file
61
src/core/hid/input_interpreter.cpp
Executable 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
112
src/core/hid/input_interpreter.h
Executable 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
280
src/core/hid/motion_input.cpp
Executable 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
87
src/core/hid/motion_input.h
Executable 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
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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{};
|
||||
|
|
|
@ -11,7 +11,7 @@ ControllerBase::~ControllerBase() = default;
|
|||
|
||||
void ControllerBase::ActivateController() {
|
||||
if (is_activated) {
|
||||
OnRelease();
|
||||
return;
|
||||
}
|
||||
is_activated = true;
|
||||
OnInit();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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();
|
||||
|
||||
next_state.key = keyboard_state;
|
||||
next_state.modifier = keyboard_modifier_state;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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;
|
||||
};
|
||||
static_assert(sizeof(NPadEntry) == 0x5000, "NPadEntry is an invalid size");
|
||||
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(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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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{};
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
||||
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.start_touch) {
|
||||
finger.attribute.raw = 0;
|
||||
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.attribute.end_touch) {
|
||||
finger.attribute.raw = 0;
|
||||
finger.pressed = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
56
src/core/hle/service/hid/ring_lifo.h
Executable file
56
src/core/hle/service/hid/ring_lifo.h
Executable 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
|
|
@ -1,36 +1,32 @@
|
|||
add_library(input_common STATIC
|
||||
analog_from_button.cpp
|
||||
analog_from_button.h
|
||||
keyboard.cpp
|
||||
keyboard.h
|
||||
drivers/gc_adapter.cpp
|
||||
drivers/gc_adapter.h
|
||||
drivers/keyboard.cpp
|
||||
drivers/keyboard.h
|
||||
drivers/mouse.cpp
|
||||
drivers/mouse.h
|
||||
drivers/sdl_driver.cpp
|
||||
drivers/sdl_driver.h
|
||||
drivers/tas_input.cpp
|
||||
drivers/tas_input.h
|
||||
drivers/touch_screen.cpp
|
||||
drivers/touch_screen.h
|
||||
drivers/udp_client.cpp
|
||||
drivers/udp_client.h
|
||||
helpers/stick_from_buttons.cpp
|
||||
helpers/stick_from_buttons.h
|
||||
helpers/touch_from_buttons.cpp
|
||||
helpers/touch_from_buttons.h
|
||||
helpers/udp_protocol.cpp
|
||||
helpers/udp_protocol.h
|
||||
input_engine.cpp
|
||||
input_engine.h
|
||||
input_mapping.cpp
|
||||
input_mapping.h
|
||||
input_poller.cpp
|
||||
input_poller.h
|
||||
main.cpp
|
||||
main.h
|
||||
motion_from_button.cpp
|
||||
motion_from_button.h
|
||||
motion_input.cpp
|
||||
motion_input.h
|
||||
touch_from_button.cpp
|
||||
touch_from_button.h
|
||||
gcadapter/gc_adapter.cpp
|
||||
gcadapter/gc_adapter.h
|
||||
gcadapter/gc_poller.cpp
|
||||
gcadapter/gc_poller.h
|
||||
mouse/mouse_input.cpp
|
||||
mouse/mouse_input.h
|
||||
mouse/mouse_poller.cpp
|
||||
mouse/mouse_poller.h
|
||||
sdl/sdl.cpp
|
||||
sdl/sdl.h
|
||||
tas/tas_input.cpp
|
||||
tas/tas_input.h
|
||||
tas/tas_poller.cpp
|
||||
tas/tas_poller.h
|
||||
udp/client.cpp
|
||||
udp/client.h
|
||||
udp/protocol.cpp
|
||||
udp/protocol.h
|
||||
udp/udp.cpp
|
||||
udp/udp.h
|
||||
)
|
||||
|
||||
if (MSVC)
|
||||
|
@ -57,8 +53,8 @@ endif()
|
|||
|
||||
if (ENABLE_SDL2)
|
||||
target_sources(input_common PRIVATE
|
||||
sdl/sdl_impl.cpp
|
||||
sdl/sdl_impl.h
|
||||
drivers/sdl_driver.cpp
|
||||
drivers/sdl_driver.h
|
||||
)
|
||||
target_link_libraries(input_common PRIVATE SDL2)
|
||||
target_compile_definitions(input_common PRIVATE HAVE_SDL2)
|
||||
|
|
539
src/input_common/drivers/gc_adapter.cpp
Executable file
539
src/input_common/drivers/gc_adapter.cpp
Executable file
|
@ -0,0 +1,539 @@
|
|||
// Copyright 2014 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <libusb.h>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/param_package.h"
|
||||
#include "common/settings_input.h"
|
||||
#include "common/thread.h"
|
||||
#include "input_common/drivers/gc_adapter.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
class LibUSBContext {
|
||||
public:
|
||||
explicit LibUSBContext() {
|
||||
init_result = libusb_init(&ctx);
|
||||
}
|
||||
|
||||
~LibUSBContext() {
|
||||
libusb_exit(ctx);
|
||||
}
|
||||
|
||||
LibUSBContext& operator=(const LibUSBContext&) = delete;
|
||||
LibUSBContext(const LibUSBContext&) = delete;
|
||||
|
||||
LibUSBContext& operator=(LibUSBContext&&) noexcept = delete;
|
||||
LibUSBContext(LibUSBContext&&) noexcept = delete;
|
||||
|
||||
[[nodiscard]] int InitResult() const noexcept {
|
||||
return init_result;
|
||||
}
|
||||
|
||||
[[nodiscard]] libusb_context* get() noexcept {
|
||||
return ctx;
|
||||
}
|
||||
|
||||
private:
|
||||
libusb_context* ctx;
|
||||
int init_result{};
|
||||
};
|
||||
|
||||
class LibUSBDeviceHandle {
|
||||
public:
|
||||
explicit LibUSBDeviceHandle(libusb_context* ctx, uint16_t vid, uint16_t pid) noexcept {
|
||||
handle = libusb_open_device_with_vid_pid(ctx, vid, pid);
|
||||
}
|
||||
|
||||
~LibUSBDeviceHandle() noexcept {
|
||||
if (handle) {
|
||||
libusb_release_interface(handle, 1);
|
||||
libusb_close(handle);
|
||||
}
|
||||
}
|
||||
|
||||
LibUSBDeviceHandle& operator=(const LibUSBDeviceHandle&) = delete;
|
||||
LibUSBDeviceHandle(const LibUSBDeviceHandle&) = delete;
|
||||
|
||||
LibUSBDeviceHandle& operator=(LibUSBDeviceHandle&&) noexcept = delete;
|
||||
LibUSBDeviceHandle(LibUSBDeviceHandle&&) noexcept = delete;
|
||||
|
||||
[[nodiscard]] libusb_device_handle* get() noexcept {
|
||||
return handle;
|
||||
}
|
||||
|
||||
private:
|
||||
libusb_device_handle* handle{};
|
||||
};
|
||||
|
||||
GCAdapter::GCAdapter(const std::string input_engine_) : InputEngine(input_engine_) {
|
||||
if (usb_adapter_handle) {
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG(Input, "Initialization started");
|
||||
|
||||
libusb_ctx = std::make_unique<LibUSBContext>();
|
||||
const int init_res = libusb_ctx->InitResult();
|
||||
if (init_res == LIBUSB_SUCCESS) {
|
||||
adapter_scan_thread =
|
||||
std::jthread([this](std::stop_token stop_token) { AdapterScanThread(stop_token); });
|
||||
} else {
|
||||
LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res);
|
||||
}
|
||||
}
|
||||
|
||||
GCAdapter::~GCAdapter() {
|
||||
Reset();
|
||||
}
|
||||
|
||||
void GCAdapter::AdapterInputThread(std::stop_token stop_token) {
|
||||
LOG_DEBUG(Input, "Input thread started");
|
||||
Common::SetCurrentThreadName("yuzu:input:GCAdapter");
|
||||
s32 payload_size{};
|
||||
AdapterPayload adapter_payload{};
|
||||
|
||||
adapter_scan_thread = {};
|
||||
|
||||
while (!stop_token.stop_requested()) {
|
||||
libusb_interrupt_transfer(usb_adapter_handle->get(), input_endpoint, adapter_payload.data(),
|
||||
static_cast<s32>(adapter_payload.size()), &payload_size, 16);
|
||||
if (IsPayloadCorrect(adapter_payload, payload_size)) {
|
||||
UpdateControllers(adapter_payload);
|
||||
UpdateVibrations();
|
||||
}
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
if (restart_scan_thread) {
|
||||
adapter_scan_thread =
|
||||
std::jthread([this](std::stop_token token) { AdapterScanThread(token); });
|
||||
restart_scan_thread = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool GCAdapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) {
|
||||
if (payload_size != static_cast<s32>(adapter_payload.size()) ||
|
||||
adapter_payload[0] != LIBUSB_DT_HID) {
|
||||
LOG_DEBUG(Input, "Error reading payload (size: {}, type: {:02x})", payload_size,
|
||||
adapter_payload[0]);
|
||||
if (input_error_counter++ > 20) {
|
||||
LOG_ERROR(Input, "Timeout, Is the adapter connected?");
|
||||
adapter_input_thread.request_stop();
|
||||
restart_scan_thread = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
input_error_counter = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GCAdapter::UpdateControllers(const AdapterPayload& adapter_payload) {
|
||||
for (std::size_t port = 0; port < pads.size(); ++port) {
|
||||
const std::size_t offset = 1 + (9 * port);
|
||||
const auto type = static_cast<ControllerTypes>(adapter_payload[offset] >> 4);
|
||||
UpdatePadType(port, type);
|
||||
if (DeviceConnected(port)) {
|
||||
const u8 b1 = adapter_payload[offset + 1];
|
||||
const u8 b2 = adapter_payload[offset + 2];
|
||||
UpdateStateButtons(port, b1, b2);
|
||||
UpdateStateAxes(port, adapter_payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GCAdapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) {
|
||||
if (pads[port].type == pad_type) {
|
||||
return;
|
||||
}
|
||||
// Device changed reset device and set new type
|
||||
pads[port].axis_origin = {};
|
||||
pads[port].reset_origin_counter = {};
|
||||
pads[port].enable_vibration = {};
|
||||
pads[port].rumble_amplitude = {};
|
||||
pads[port].type = pad_type;
|
||||
}
|
||||
|
||||
void GCAdapter::UpdateStateButtons(std::size_t port, [[maybe_unused]] u8 b1,
|
||||
[[maybe_unused]] u8 b2) {
|
||||
if (port >= pads.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
static constexpr std::array<PadButton, 8> b1_buttons{
|
||||
PadButton::ButtonA, PadButton::ButtonB, PadButton::ButtonX, PadButton::ButtonY,
|
||||
PadButton::ButtonLeft, PadButton::ButtonRight, PadButton::ButtonDown, PadButton::ButtonUp,
|
||||
};
|
||||
|
||||
static constexpr std::array<PadButton, 4> b2_buttons{
|
||||
PadButton::ButtonStart,
|
||||
PadButton::TriggerZ,
|
||||
PadButton::TriggerR,
|
||||
PadButton::TriggerL,
|
||||
};
|
||||
|
||||
for (std::size_t i = 0; i < b1_buttons.size(); ++i) {
|
||||
const bool button_status = (b1 & (1U << i)) != 0;
|
||||
const int button = static_cast<int>(b1_buttons[i]);
|
||||
SetButton(pads[port].identifier, button, button_status);
|
||||
}
|
||||
|
||||
for (std::size_t j = 0; j < b2_buttons.size(); ++j) {
|
||||
const bool button_status = (b2 & (1U << j)) != 0;
|
||||
const int button = static_cast<int>(b2_buttons[j]);
|
||||
SetButton(pads[port].identifier, button, button_status);
|
||||
}
|
||||
}
|
||||
|
||||
void GCAdapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) {
|
||||
if (port >= pads.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::size_t offset = 1 + (9 * port);
|
||||
static constexpr std::array<PadAxes, 6> axes{
|
||||
PadAxes::StickX, PadAxes::StickY, PadAxes::SubstickX,
|
||||
PadAxes::SubstickY, PadAxes::TriggerLeft, PadAxes::TriggerRight,
|
||||
};
|
||||
|
||||
for (const PadAxes axis : axes) {
|
||||
const auto index = static_cast<std::size_t>(axis);
|
||||
const u8 axis_value = adapter_payload[offset + 3 + index];
|
||||
if (pads[port].reset_origin_counter <= 18) {
|
||||
if (pads[port].axis_origin[index] != axis_value) {
|
||||
pads[port].reset_origin_counter = 0;
|
||||
}
|
||||
pads[port].axis_origin[index] = axis_value;
|
||||
pads[port].reset_origin_counter++;
|
||||
}
|
||||
const f32 axis_status = (axis_value - pads[port].axis_origin[index]) / 100.0f;
|
||||
SetAxis(pads[port].identifier, static_cast<int>(index), axis_status);
|
||||
}
|
||||
}
|
||||
|
||||
void GCAdapter::AdapterScanThread(std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("yuzu:input:ScanGCAdapter");
|
||||
usb_adapter_handle = nullptr;
|
||||
pads = {};
|
||||
while (!stop_token.stop_requested() && !Setup()) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
}
|
||||
}
|
||||
|
||||
bool GCAdapter::Setup() {
|
||||
constexpr u16 nintendo_vid = 0x057e;
|
||||
constexpr u16 gc_adapter_pid = 0x0337;
|
||||
usb_adapter_handle =
|
||||
std::make_unique<LibUSBDeviceHandle>(libusb_ctx->get(), nintendo_vid, gc_adapter_pid);
|
||||
if (!usb_adapter_handle->get()) {
|
||||
return false;
|
||||
}
|
||||
if (!CheckDeviceAccess()) {
|
||||
usb_adapter_handle = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
libusb_device* const device = libusb_get_device(usb_adapter_handle->get());
|
||||
|
||||
LOG_INFO(Input, "GC adapter is now connected");
|
||||
// GC Adapter found and accessible, registering it
|
||||
if (GetGCEndpoint(device)) {
|
||||
rumble_enabled = true;
|
||||
input_error_counter = 0;
|
||||
output_error_counter = 0;
|
||||
|
||||
std::size_t port = 0;
|
||||
for (GCController& pad : pads) {
|
||||
pad.identifier = {
|
||||
.guid = Common::UUID{Common::INVALID_UUID},
|
||||
.port = port++,
|
||||
.pad = 0,
|
||||
};
|
||||
PreSetController(pad.identifier);
|
||||
}
|
||||
|
||||
adapter_input_thread =
|
||||
std::jthread([this](std::stop_token stop_token) { AdapterInputThread(stop_token); });
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GCAdapter::CheckDeviceAccess() {
|
||||
s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle->get(), 0);
|
||||
if (kernel_driver_error == 1) {
|
||||
kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle->get(), 0);
|
||||
if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) {
|
||||
LOG_ERROR(Input, "libusb_detach_kernel_driver failed with error = {}",
|
||||
kernel_driver_error);
|
||||
}
|
||||
}
|
||||
|
||||
// This fixes payload problems from offbrand GCAdapters
|
||||
const s32 control_transfer_error =
|
||||
libusb_control_transfer(usb_adapter_handle->get(), 0x21, 11, 0x0001, 0, nullptr, 0, 1000);
|
||||
if (control_transfer_error < 0) {
|
||||
LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error);
|
||||
}
|
||||
|
||||
if (kernel_driver_error && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) {
|
||||
usb_adapter_handle = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
const int interface_claim_error = libusb_claim_interface(usb_adapter_handle->get(), 0);
|
||||
if (interface_claim_error) {
|
||||
LOG_ERROR(Input, "libusb_claim_interface failed with error = {}", interface_claim_error);
|
||||
usb_adapter_handle = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GCAdapter::GetGCEndpoint(libusb_device* device) {
|
||||
libusb_config_descriptor* config = nullptr;
|
||||
const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config);
|
||||
if (config_descriptor_return != LIBUSB_SUCCESS) {
|
||||
LOG_ERROR(Input, "libusb_get_config_descriptor failed with error = {}",
|
||||
config_descriptor_return);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (u8 ic = 0; ic < config->bNumInterfaces; ic++) {
|
||||
const libusb_interface* interfaceContainer = &config->interface[ic];
|
||||
for (int i = 0; i < interfaceContainer->num_altsetting; i++) {
|
||||
const libusb_interface_descriptor* interface = &interfaceContainer->altsetting[i];
|
||||
for (u8 e = 0; e < interface->bNumEndpoints; e++) {
|
||||
const libusb_endpoint_descriptor* endpoint = &interface->endpoint[e];
|
||||
if ((endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) != 0) {
|
||||
input_endpoint = endpoint->bEndpointAddress;
|
||||
} else {
|
||||
output_endpoint = endpoint->bEndpointAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// This transfer seems to be responsible for clearing the state of the adapter
|
||||
// Used to clear the "busy" state of when the device is unexpectedly unplugged
|
||||
unsigned char clear_payload = 0x13;
|
||||
libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, &clear_payload,
|
||||
sizeof(clear_payload), nullptr, 16);
|
||||
return true;
|
||||
}
|
||||
|
||||
Common::Input::VibrationError GCAdapter::SetRumble(const PadIdentifier& identifier,
|
||||
const Common::Input::VibrationStatus vibration) {
|
||||
const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f;
|
||||
const auto processed_amplitude =
|
||||
static_cast<u8>((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8);
|
||||
|
||||
pads[identifier.port].rumble_amplitude = processed_amplitude;
|
||||
|
||||
if (!rumble_enabled) {
|
||||
return Common::Input::VibrationError::Disabled;
|
||||
}
|
||||
return Common::Input::VibrationError::None;
|
||||
}
|
||||
|
||||
void GCAdapter::UpdateVibrations() {
|
||||
// Use 8 states to keep the switching between on/off fast enough for
|
||||
// a human to feel different vibration strenght
|
||||
// More states == more rumble strengths == slower update time
|
||||
constexpr u8 vibration_states = 8;
|
||||
|
||||
vibration_counter = (vibration_counter + 1) % vibration_states;
|
||||
|
||||
for (GCController& pad : pads) {
|
||||
const bool vibrate = pad.rumble_amplitude > vibration_counter;
|
||||
vibration_changed |= vibrate != pad.enable_vibration;
|
||||
pad.enable_vibration = vibrate;
|
||||
}
|
||||
SendVibrations();
|
||||
}
|
||||
|
||||
void GCAdapter::SendVibrations() {
|
||||
if (!rumble_enabled || !vibration_changed) {
|
||||
return;
|
||||
}
|
||||
s32 size{};
|
||||
constexpr u8 rumble_command = 0x11;
|
||||
const u8 p1 = pads[0].enable_vibration;
|
||||
const u8 p2 = pads[1].enable_vibration;
|
||||
const u8 p3 = pads[2].enable_vibration;
|
||||
const u8 p4 = pads[3].enable_vibration;
|
||||
std::array<u8, 5> payload = {rumble_command, p1, p2, p3, p4};
|
||||
const int err =
|
||||
libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, payload.data(),
|
||||
static_cast<s32>(payload.size()), &size, 16);
|
||||
if (err) {
|
||||
LOG_DEBUG(Input, "Libusb write failed: {}", libusb_error_name(err));
|
||||
if (output_error_counter++ > 5) {
|
||||
LOG_ERROR(Input, "Output timeout, Rumble disabled");
|
||||
rumble_enabled = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
output_error_counter = 0;
|
||||
vibration_changed = false;
|
||||
}
|
||||
|
||||
bool GCAdapter::DeviceConnected(std::size_t port) const {
|
||||
return pads[port].type != ControllerTypes::None;
|
||||
}
|
||||
|
||||
void GCAdapter::Reset() {
|
||||
adapter_scan_thread = {};
|
||||
adapter_input_thread = {};
|
||||
usb_adapter_handle = nullptr;
|
||||
pads = {};
|
||||
libusb_ctx = nullptr;
|
||||
}
|
||||
|
||||
std::vector<Common::ParamPackage> GCAdapter::GetInputDevices() const {
|
||||
std::vector<Common::ParamPackage> devices;
|
||||
for (std::size_t port = 0; port < pads.size(); ++port) {
|
||||
if (!DeviceConnected(port)) {
|
||||
continue;
|
||||
}
|
||||
Common::ParamPackage identifier{};
|
||||
identifier.Set("engine", GetEngineName());
|
||||
identifier.Set("display", fmt::format("Gamecube Controller {}", port + 1));
|
||||
identifier.Set("port", static_cast<int>(port));
|
||||
devices.emplace_back(identifier);
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
|
||||
ButtonMapping GCAdapter::GetButtonMappingForDevice(const Common::ParamPackage& params) {
|
||||
// This list is missing ZL/ZR since those are not considered buttons.
|
||||
// We will add those afterwards
|
||||
// This list also excludes any button that can't be really mapped
|
||||
static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 12>
|
||||
switch_to_gcadapter_button = {
|
||||
std::pair{Settings::NativeButton::A, PadButton::ButtonA},
|
||||
{Settings::NativeButton::B, PadButton::ButtonB},
|
||||
{Settings::NativeButton::X, PadButton::ButtonX},
|
||||
{Settings::NativeButton::Y, PadButton::ButtonY},
|
||||
{Settings::NativeButton::Plus, PadButton::ButtonStart},
|
||||
{Settings::NativeButton::DLeft, PadButton::ButtonLeft},
|
||||
{Settings::NativeButton::DUp, PadButton::ButtonUp},
|
||||
{Settings::NativeButton::DRight, PadButton::ButtonRight},
|
||||
{Settings::NativeButton::DDown, PadButton::ButtonDown},
|
||||
{Settings::NativeButton::SL, PadButton::TriggerL},
|
||||
{Settings::NativeButton::SR, PadButton::TriggerR},
|
||||
{Settings::NativeButton::R, PadButton::TriggerZ},
|
||||
};
|
||||
if (!params.Has("port")) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ButtonMapping mapping{};
|
||||
for (const auto& [switch_button, gcadapter_button] : switch_to_gcadapter_button) {
|
||||
Common::ParamPackage button_params{};
|
||||
button_params.Set("engine", GetEngineName());
|
||||
button_params.Set("port", params.Get("port", 0));
|
||||
button_params.Set("button", static_cast<int>(gcadapter_button));
|
||||
mapping.insert_or_assign(switch_button, std::move(button_params));
|
||||
}
|
||||
|
||||
// Add the missing bindings for ZL/ZR
|
||||
static constexpr std::array<std::tuple<Settings::NativeButton::Values, PadButton, PadAxes>, 2>
|
||||
switch_to_gcadapter_axis = {
|
||||
std::tuple{Settings::NativeButton::ZL, PadButton::TriggerL, PadAxes::TriggerLeft},
|
||||
{Settings::NativeButton::ZR, PadButton::TriggerR, PadAxes::TriggerRight},
|
||||
};
|
||||
for (const auto& [switch_button, gcadapter_buton, gcadapter_axis] : switch_to_gcadapter_axis) {
|
||||
Common::ParamPackage button_params{};
|
||||
button_params.Set("engine", GetEngineName());
|
||||
button_params.Set("port", params.Get("port", 0));
|
||||
button_params.Set("button", static_cast<s32>(gcadapter_buton));
|
||||
button_params.Set("axis", static_cast<s32>(gcadapter_axis));
|
||||
button_params.Set("threshold", 0.5f);
|
||||
button_params.Set("range", 1.9f);
|
||||
button_params.Set("direction", "+");
|
||||
mapping.insert_or_assign(switch_button, std::move(button_params));
|
||||
}
|
||||
return mapping;
|
||||
}
|
||||
|
||||
AnalogMapping GCAdapter::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
|
||||
if (!params.Has("port")) {
|
||||
return {};
|
||||
}
|
||||
|
||||
AnalogMapping mapping = {};
|
||||
Common::ParamPackage left_analog_params;
|
||||
left_analog_params.Set("engine", GetEngineName());
|
||||
left_analog_params.Set("port", params.Get("port", 0));
|
||||
left_analog_params.Set("axis_x", static_cast<int>(PadAxes::StickX));
|
||||
left_analog_params.Set("axis_y", static_cast<int>(PadAxes::StickY));
|
||||
mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
|
||||
Common::ParamPackage right_analog_params;
|
||||
right_analog_params.Set("engine", GetEngineName());
|
||||
right_analog_params.Set("port", params.Get("port", 0));
|
||||
right_analog_params.Set("axis_x", static_cast<int>(PadAxes::SubstickX));
|
||||
right_analog_params.Set("axis_y", static_cast<int>(PadAxes::SubstickY));
|
||||
mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
|
||||
return mapping;
|
||||
}
|
||||
|
||||
std::string GCAdapter::GetUIButtonName(const Common::ParamPackage& params) const {
|
||||
PadButton button = static_cast<PadButton>(params.Get("button", 0));
|
||||
switch (button) {
|
||||
case PadButton::ButtonLeft:
|
||||
return "left";
|
||||
break;
|
||||
case PadButton::ButtonRight:
|
||||
return "right";
|
||||
break;
|
||||
case PadButton::ButtonDown:
|
||||
return "down";
|
||||
break;
|
||||
case PadButton::ButtonUp:
|
||||
return "up";
|
||||
break;
|
||||
case PadButton::TriggerZ:
|
||||
return "Z";
|
||||
break;
|
||||
case PadButton::TriggerR:
|
||||
return "R";
|
||||
break;
|
||||
case PadButton::TriggerL:
|
||||
return "L";
|
||||
break;
|
||||
case PadButton::ButtonA:
|
||||
return "A";
|
||||
break;
|
||||
case PadButton::ButtonB:
|
||||
return "B";
|
||||
break;
|
||||
case PadButton::ButtonX:
|
||||
return "X";
|
||||
break;
|
||||
case PadButton::ButtonY:
|
||||
return "Y";
|
||||
break;
|
||||
case PadButton::ButtonStart:
|
||||
return "start";
|
||||
break;
|
||||
default:
|
||||
return "Unkown GC";
|
||||
}
|
||||
}
|
||||
|
||||
std::string GCAdapter::GetUIName(const Common::ParamPackage& params) const {
|
||||
if (params.Has("button")) {
|
||||
return fmt::format("Button {}", GetUIButtonName(params));
|
||||
}
|
||||
if (params.Has("axis")) {
|
||||
return fmt::format("Axis {}", params.Get("axis", 0));
|
||||
}
|
||||
|
||||
return "Bad GC Adapter";
|
||||
}
|
||||
|
||||
} // namespace InputCommon
|
135
src/input_common/drivers/gc_adapter.h
Executable file
135
src/input_common/drivers/gc_adapter.h
Executable file
|
@ -0,0 +1,135 @@
|
|||
// Copyright 2014 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <stop_token>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "input_common/input_engine.h"
|
||||
|
||||
struct libusb_context;
|
||||
struct libusb_device;
|
||||
struct libusb_device_handle;
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
class LibUSBContext;
|
||||
class LibUSBDeviceHandle;
|
||||
|
||||
class GCAdapter : public InputCommon::InputEngine {
|
||||
public:
|
||||
explicit GCAdapter(const std::string input_engine_);
|
||||
~GCAdapter();
|
||||
|
||||
Common::Input::VibrationError SetRumble(
|
||||
const PadIdentifier& identifier, const Common::Input::VibrationStatus vibration) override;
|
||||
|
||||
/// Used for automapping features
|
||||
std::vector<Common::ParamPackage> GetInputDevices() const override;
|
||||
ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
|
||||
AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
|
||||
std::string GetUIName(const Common::ParamPackage& params) const override;
|
||||
|
||||
private:
|
||||
enum class PadButton {
|
||||
Undefined = 0x0000,
|
||||
ButtonLeft = 0x0001,
|
||||
ButtonRight = 0x0002,
|
||||
ButtonDown = 0x0004,
|
||||
ButtonUp = 0x0008,
|
||||
TriggerZ = 0x0010,
|
||||
TriggerR = 0x0020,
|
||||
TriggerL = 0x0040,
|
||||
ButtonA = 0x0100,
|
||||
ButtonB = 0x0200,
|
||||
ButtonX = 0x0400,
|
||||
ButtonY = 0x0800,
|
||||
ButtonStart = 0x1000,
|
||||
};
|
||||
|
||||
enum class PadAxes : u8 {
|
||||
StickX,
|
||||
StickY,
|
||||
SubstickX,
|
||||
SubstickY,
|
||||
TriggerLeft,
|
||||
TriggerRight,
|
||||
Undefined,
|
||||
};
|
||||
|
||||
enum class ControllerTypes {
|
||||
None,
|
||||
Wired,
|
||||
Wireless,
|
||||
};
|
||||
|
||||
struct GCController {
|
||||
ControllerTypes type = ControllerTypes::None;
|
||||
PadIdentifier identifier{};
|
||||
bool enable_vibration = false;
|
||||
u8 rumble_amplitude{};
|
||||
std::array<u8, 6> axis_origin{};
|
||||
u8 reset_origin_counter{};
|
||||
};
|
||||
|
||||
using AdapterPayload = std::array<u8, 37>;
|
||||
|
||||
void UpdatePadType(std::size_t port, ControllerTypes pad_type);
|
||||
void UpdateControllers(const AdapterPayload& adapter_payload);
|
||||
void UpdateStateButtons(std::size_t port, u8 b1, u8 b2);
|
||||
void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload);
|
||||
|
||||
void AdapterInputThread(std::stop_token stop_token);
|
||||
|
||||
void AdapterScanThread(std::stop_token stop_token);
|
||||
|
||||
bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size);
|
||||
|
||||
/// For use in initialization, querying devices to find the adapter
|
||||
bool Setup();
|
||||
|
||||
/// Returns true if we successfully gain access to GC Adapter
|
||||
bool CheckDeviceAccess();
|
||||
|
||||
/// Captures GC Adapter endpoint address
|
||||
/// Returns true if the endpoint was set correctly
|
||||
bool GetGCEndpoint(libusb_device* device);
|
||||
|
||||
/// Returns true if there is a device connected to port
|
||||
bool DeviceConnected(std::size_t port) const;
|
||||
|
||||
/// For shutting down, clear all data, join all threads, release usb
|
||||
void Reset();
|
||||
|
||||
void UpdateVibrations();
|
||||
|
||||
/// Updates vibration state of all controllers
|
||||
void SendVibrations();
|
||||
|
||||
std::string GetUIButtonName(const Common::ParamPackage& params) const;
|
||||
|
||||
std::unique_ptr<LibUSBDeviceHandle> usb_adapter_handle;
|
||||
std::array<GCController, 4> pads;
|
||||
|
||||
std::jthread adapter_input_thread;
|
||||
std::jthread adapter_scan_thread;
|
||||
bool restart_scan_thread{};
|
||||
|
||||
std::unique_ptr<LibUSBContext> libusb_ctx;
|
||||
|
||||
u8 input_endpoint{0};
|
||||
u8 output_endpoint{0};
|
||||
u8 input_error_counter{0};
|
||||
u8 output_error_counter{0};
|
||||
int vibration_counter{0};
|
||||
|
||||
bool rumble_enabled{true};
|
||||
bool vibration_changed{true};
|
||||
};
|
||||
} // namespace InputCommon
|
41
src/input_common/drivers/keyboard.cpp
Executable file
41
src/input_common/drivers/keyboard.cpp
Executable file
|
@ -0,0 +1,41 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included
|
||||
|
||||
#include "common/param_package.h"
|
||||
#include "input_common/drivers/keyboard.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
constexpr PadIdentifier identifier = {
|
||||
.guid = Common::UUID{Common::INVALID_UUID},
|
||||
.port = 0,
|
||||
.pad = 0,
|
||||
};
|
||||
|
||||
Keyboard::Keyboard(const std::string& input_engine_) : InputEngine(input_engine_) {
|
||||
PreSetController(identifier);
|
||||
}
|
||||
|
||||
void Keyboard::PressKey(int key_code) {
|
||||
SetButton(identifier, key_code, true);
|
||||
}
|
||||
|
||||
void Keyboard::ReleaseKey(int key_code) {
|
||||
SetButton(identifier, key_code, false);
|
||||
}
|
||||
|
||||
void Keyboard::ReleaseAllKeys() {
|
||||
ResetButtonState();
|
||||
}
|
||||
|
||||
std::vector<Common::ParamPackage> Keyboard::GetInputDevices() const {
|
||||
std::vector<Common::ParamPackage> devices;
|
||||
devices.emplace_back(Common::ParamPackage{
|
||||
{"engine", GetEngineName()},
|
||||
{"display", "Keyboard Only"},
|
||||
});
|
||||
return devices;
|
||||
}
|
||||
|
||||
} // namespace InputCommon
|
37
src/input_common/drivers/keyboard.h
Executable file
37
src/input_common/drivers/keyboard.h
Executable file
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "input_common/input_engine.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
/**
|
||||
* A button device factory representing a keyboard. It receives keyboard events and forward them
|
||||
* to all button devices it created.
|
||||
*/
|
||||
class Keyboard final : public InputCommon::InputEngine {
|
||||
public:
|
||||
explicit Keyboard(const std::string& input_engine_);
|
||||
|
||||
/**
|
||||
* Sets the status of all buttons bound with the key to pressed
|
||||
* @param key_code the code of the key to press
|
||||
*/
|
||||
void PressKey(int key_code);
|
||||
|
||||
/**
|
||||
* Sets the status of all buttons bound with the key to released
|
||||
* @param key_code the code of the key to release
|
||||
*/
|
||||
void ReleaseKey(int key_code);
|
||||
|
||||
void ReleaseAllKeys();
|
||||
|
||||
/// Used for automapping features
|
||||
std::vector<Common::ParamPackage> GetInputDevices() const override;
|
||||
};
|
||||
|
||||
} // namespace InputCommon
|
158
src/input_common/drivers/mouse.cpp
Executable file
158
src/input_common/drivers/mouse.cpp
Executable file
|
@ -0,0 +1,158 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included
|
||||
|
||||
#include <stop_token>
|
||||
#include <thread>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/param_package.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/thread.h"
|
||||
#include "input_common/drivers/mouse.h"
|
||||
|
||||
namespace InputCommon {
|
||||
constexpr int touch_axis_x = 10;
|
||||
constexpr int touch_axis_y = 11;
|
||||
constexpr PadIdentifier identifier = {
|
||||
.guid = Common::UUID{Common::INVALID_UUID},
|
||||
.port = 0,
|
||||
.pad = 0,
|
||||
};
|
||||
|
||||
Mouse::Mouse(const std::string input_engine_) : InputEngine(input_engine_) {
|
||||
PreSetController(identifier);
|
||||
update_thread = std::jthread([this](std::stop_token stop_token) { UpdateThread(stop_token); });
|
||||
}
|
||||
|
||||
void Mouse::UpdateThread(std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("yuzu:input:Mouse");
|
||||
constexpr int update_time = 10;
|
||||
while (!stop_token.stop_requested()) {
|
||||
if (Settings::values.mouse_panning) {
|
||||
// Slow movement by 4%
|
||||
last_mouse_change *= 0.96f;
|
||||
const float sensitivity =
|
||||
Settings::values.mouse_panning_sensitivity.GetValue() * 0.022f;
|
||||
SetAxis(identifier, 0, last_mouse_change.x * sensitivity);
|
||||
SetAxis(identifier, 1, -last_mouse_change.y * sensitivity);
|
||||
}
|
||||
|
||||
if (mouse_panning_timout++ > 20) {
|
||||
StopPanning();
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(update_time));
|
||||
}
|
||||
}
|
||||
|
||||
void Mouse::MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y) {
|
||||
SetAxis(identifier, touch_axis_x, touch_x);
|
||||
SetAxis(identifier, touch_axis_y, touch_y);
|
||||
|
||||
if (Settings::values.mouse_panning) {
|
||||
auto mouse_change =
|
||||
(Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast<float>();
|
||||
mouse_panning_timout = 0;
|
||||
|
||||
const auto move_distance = mouse_change.Length();
|
||||
if (move_distance == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make slow movements at least 3 units on lenght
|
||||
if (move_distance < 3.0f) {
|
||||
// Normalize value
|
||||
mouse_change /= move_distance;
|
||||
mouse_change *= 3.0f;
|
||||
}
|
||||
|
||||
// Average mouse movements
|
||||
last_mouse_change = (last_mouse_change * 0.91f) + (mouse_change * 0.09f);
|
||||
|
||||
const auto last_move_distance = last_mouse_change.Length();
|
||||
|
||||
// Make fast movements clamp to 8 units on lenght
|
||||
if (last_move_distance > 8.0f) {
|
||||
// Normalize value
|
||||
last_mouse_change /= last_move_distance;
|
||||
last_mouse_change *= 8.0f;
|
||||
}
|
||||
|
||||
// Ignore average if it's less than 1 unit and use current movement value
|
||||
if (last_move_distance < 1.0f) {
|
||||
last_mouse_change = mouse_change / mouse_change.Length();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (button_pressed) {
|
||||
const auto mouse_move = Common::MakeVec<int>(x, y) - mouse_origin;
|
||||
const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue() * 0.0012f;
|
||||
SetAxis(identifier, 0, static_cast<float>(mouse_move.x) * sensitivity);
|
||||
SetAxis(identifier, 1, static_cast<float>(-mouse_move.y) * sensitivity);
|
||||
}
|
||||
}
|
||||
|
||||
void Mouse::PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button) {
|
||||
SetAxis(identifier, touch_axis_x, touch_x);
|
||||
SetAxis(identifier, touch_axis_y, touch_y);
|
||||
SetButton(identifier, static_cast<int>(button), true);
|
||||
// Set initial analog parameters
|
||||
mouse_origin = {x, y};
|
||||
last_mouse_position = {x, y};
|
||||
button_pressed = true;
|
||||
}
|
||||
|
||||
void Mouse::ReleaseButton(MouseButton button) {
|
||||
SetButton(identifier, static_cast<int>(button), false);
|
||||
|
||||
if (!Settings::values.mouse_panning) {
|
||||
SetAxis(identifier, 0, 0);
|
||||
SetAxis(identifier, 1, 0);
|
||||
}
|
||||
button_pressed = false;
|
||||
}
|
||||
|
||||
void Mouse::ReleaseAllButtons() {
|
||||
ResetButtonState();
|
||||
button_pressed = false;
|
||||
}
|
||||
|
||||
void Mouse::StopPanning() {
|
||||
last_mouse_change = {};
|
||||
}
|
||||
|
||||
std::vector<Common::ParamPackage> Mouse::GetInputDevices() const {
|
||||
std::vector<Common::ParamPackage> devices;
|
||||
devices.emplace_back(Common::ParamPackage{
|
||||
{"engine", GetEngineName()},
|
||||
{"display", "Keyboard/Mouse"},
|
||||
});
|
||||
return devices;
|
||||
}
|
||||
|
||||
AnalogMapping Mouse::GetAnalogMappingForDevice(
|
||||
[[maybe_unused]] const Common::ParamPackage& params) {
|
||||
// Only overwrite different buttons from default
|
||||
AnalogMapping mapping = {};
|
||||
Common::ParamPackage right_analog_params;
|
||||
right_analog_params.Set("engine", GetEngineName());
|
||||
right_analog_params.Set("axis_x", 0);
|
||||
right_analog_params.Set("axis_y", 1);
|
||||
right_analog_params.Set("threshold", 0.5f);
|
||||
right_analog_params.Set("range", 1.0f);
|
||||
right_analog_params.Set("deadzone", 0.0f);
|
||||
mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
|
||||
return mapping;
|
||||
}
|
||||
|
||||
std::string Mouse::GetUIName(const Common::ParamPackage& params) const {
|
||||
if (params.Has("button")) {
|
||||
return fmt::format("Mouse {}", params.Get("button", 0));
|
||||
}
|
||||
|
||||
return "Bad Mouse";
|
||||
}
|
||||
|
||||
} // namespace InputCommon
|
73
src/input_common/drivers/mouse.h
Executable file
73
src/input_common/drivers/mouse.h
Executable 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 <stop_token>
|
||||
#include <thread>
|
||||
|
||||
#include "common/vector_math.h"
|
||||
#include "input_common/input_engine.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
enum class MouseButton {
|
||||
Left,
|
||||
Right,
|
||||
Wheel,
|
||||
Backward,
|
||||
Forward,
|
||||
Task,
|
||||
Extra,
|
||||
Undefined,
|
||||
};
|
||||
|
||||
/**
|
||||
* A button device factory representing a keyboard. It receives keyboard events and forward them
|
||||
* to all button devices it created.
|
||||
*/
|
||||
class Mouse final : public InputCommon::InputEngine {
|
||||
public:
|
||||
explicit Mouse(const std::string input_engine_);
|
||||
|
||||
/**
|
||||
* Signals that mouse has moved.
|
||||
* @param x the x-coordinate of the cursor
|
||||
* @param y the y-coordinate of the cursor
|
||||
* @param center_x the x-coordinate of the middle of the screen
|
||||
* @param center_y the y-coordinate of the middle of the screen
|
||||
*/
|
||||
void MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y);
|
||||
|
||||
/**
|
||||
* Sets the status of all buttons bound with the key to pressed
|
||||
* @param key_code the code of the key to press
|
||||
*/
|
||||
void PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button);
|
||||
|
||||
/**
|
||||
* Sets the status of all buttons bound with the key to released
|
||||
* @param key_code the code of the key to release
|
||||
*/
|
||||
void ReleaseButton(MouseButton button);
|
||||
|
||||
void ReleaseAllButtons();
|
||||
|
||||
std::vector<Common::ParamPackage> GetInputDevices() const override;
|
||||
AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
|
||||
std::string GetUIName(const Common::ParamPackage& params) const override;
|
||||
|
||||
private:
|
||||
void UpdateThread(std::stop_token stop_token);
|
||||
void StopPanning();
|
||||
|
||||
Common::Vec2<int> mouse_origin;
|
||||
Common::Vec2<int> last_mouse_position;
|
||||
Common::Vec2<float> last_mouse_change;
|
||||
bool button_pressed;
|
||||
int mouse_panning_timout{};
|
||||
std::jthread update_thread;
|
||||
};
|
||||
|
||||
} // namespace InputCommon
|
948
src/input_common/drivers/sdl_driver.cpp
Executable file
948
src/input_common/drivers/sdl_driver.cpp
Executable file
|
@ -0,0 +1,948 @@
|
|||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/math_util.h"
|
||||
#include "common/param_package.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/thread.h"
|
||||
#include "common/vector_math.h"
|
||||
#include "input_common/drivers/sdl_driver.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
namespace {
|
||||
std::string GetGUID(SDL_Joystick* joystick) {
|
||||
const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
|
||||
char guid_str[33];
|
||||
SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
|
||||
return guid_str;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
static int SDLEventWatcher(void* user_data, SDL_Event* event) {
|
||||
auto* const sdl_state = static_cast<SDLDriver*>(user_data);
|
||||
|
||||
sdl_state->HandleGameControllerEvent(*event);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
class SDLJoystick {
|
||||
public:
|
||||
SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick,
|
||||
SDL_GameController* game_controller)
|
||||
: guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose},
|
||||
sdl_controller{game_controller, &SDL_GameControllerClose} {
|
||||
EnableMotion();
|
||||
}
|
||||
|
||||
void EnableMotion() {
|
||||
if (sdl_controller) {
|
||||
SDL_GameController* controller = sdl_controller.get();
|
||||
if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) && !has_accel) {
|
||||
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE);
|
||||
has_accel = true;
|
||||
}
|
||||
if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) && !has_gyro) {
|
||||
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE);
|
||||
has_gyro = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HasGyro() const {
|
||||
return has_gyro;
|
||||
}
|
||||
|
||||
bool HasAccel() const {
|
||||
return has_accel;
|
||||
}
|
||||
|
||||
bool UpdateMotion(SDL_ControllerSensorEvent event) {
|
||||
constexpr float gravity_constant = 9.80665f;
|
||||
std::lock_guard lock{mutex};
|
||||
const u64 time_difference = event.timestamp - last_motion_update;
|
||||
last_motion_update = event.timestamp;
|
||||
switch (event.sensor) {
|
||||
case SDL_SENSOR_ACCEL: {
|
||||
motion.accel_x = -event.data[0] / gravity_constant;
|
||||
motion.accel_y = event.data[2] / gravity_constant;
|
||||
motion.accel_z = -event.data[1] / gravity_constant;
|
||||
break;
|
||||
}
|
||||
case SDL_SENSOR_GYRO: {
|
||||
motion.gyro_x = event.data[0] / (Common::PI * 2);
|
||||
motion.gyro_y = -event.data[2] / (Common::PI * 2);
|
||||
motion.gyro_z = event.data[1] / (Common::PI * 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore duplicated timestamps
|
||||
if (time_difference == 0) {
|
||||
return false;
|
||||
}
|
||||
motion.delta_timestamp = time_difference * 1000;
|
||||
return true;
|
||||
}
|
||||
|
||||
BasicMotion GetMotion() {
|
||||
return motion;
|
||||
}
|
||||
|
||||
bool RumblePlay(const Common::Input::VibrationStatus vibration) {
|
||||
constexpr u32 rumble_max_duration_ms = 1000;
|
||||
if (sdl_controller) {
|
||||
return SDL_GameControllerRumble(
|
||||
sdl_controller.get(), static_cast<u16>(vibration.low_amplitude),
|
||||
static_cast<u16>(vibration.high_amplitude), rumble_max_duration_ms) != -1;
|
||||
} else if (sdl_joystick) {
|
||||
return SDL_JoystickRumble(sdl_joystick.get(), static_cast<u16>(vibration.low_amplitude),
|
||||
static_cast<u16>(vibration.high_amplitude),
|
||||
rumble_max_duration_ms) != -1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HasHDRumble() const {
|
||||
if (sdl_controller) {
|
||||
return (SDL_GameControllerGetType(sdl_controller.get()) ==
|
||||
SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* The Pad identifier of the joystick
|
||||
*/
|
||||
const PadIdentifier GetPadIdentifier() const {
|
||||
return {
|
||||
.guid = Common::UUID{guid},
|
||||
.port = static_cast<std::size_t>(port),
|
||||
.pad = 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The guid of the joystick
|
||||
*/
|
||||
const std::string& GetGUID() const {
|
||||
return guid;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of joystick from the same type that were connected before this joystick
|
||||
*/
|
||||
int GetPort() const {
|
||||
return port;
|
||||
}
|
||||
|
||||
SDL_Joystick* GetSDLJoystick() const {
|
||||
return sdl_joystick.get();
|
||||
}
|
||||
|
||||
SDL_GameController* GetSDLGameController() const {
|
||||
return sdl_controller.get();
|
||||
}
|
||||
|
||||
void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) {
|
||||
sdl_joystick.reset(joystick);
|
||||
sdl_controller.reset(controller);
|
||||
}
|
||||
|
||||
bool IsJoyconLeft() const {
|
||||
const std::string controller_name = GetControllerName();
|
||||
if (std::strstr(controller_name.c_str(), "Joy-Con Left") != nullptr) {
|
||||
return true;
|
||||
}
|
||||
if (std::strstr(controller_name.c_str(), "Joy-Con (L)") != nullptr) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsJoyconRight() const {
|
||||
const std::string controller_name = GetControllerName();
|
||||
if (std::strstr(controller_name.c_str(), "Joy-Con Right") != nullptr) {
|
||||
return true;
|
||||
}
|
||||
if (std::strstr(controller_name.c_str(), "Joy-Con (R)") != nullptr) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
BatteryLevel GetBatteryLevel() {
|
||||
const auto level = SDL_JoystickCurrentPowerLevel(sdl_joystick.get());
|
||||
switch (level) {
|
||||
case SDL_JOYSTICK_POWER_EMPTY:
|
||||
return BatteryLevel::Empty;
|
||||
case SDL_JOYSTICK_POWER_LOW:
|
||||
return BatteryLevel::Critical;
|
||||
case SDL_JOYSTICK_POWER_MEDIUM:
|
||||
return BatteryLevel::Low;
|
||||
case SDL_JOYSTICK_POWER_FULL:
|
||||
return BatteryLevel::Medium;
|
||||
case SDL_JOYSTICK_POWER_MAX:
|
||||
return BatteryLevel::Full;
|
||||
case SDL_JOYSTICK_POWER_UNKNOWN:
|
||||
case SDL_JOYSTICK_POWER_WIRED:
|
||||
default:
|
||||
return BatteryLevel::Charging;
|
||||
}
|
||||
}
|
||||
|
||||
std::string GetControllerName() const {
|
||||
if (sdl_controller) {
|
||||
switch (SDL_GameControllerGetType(sdl_controller.get())) {
|
||||
case SDL_CONTROLLER_TYPE_XBOX360:
|
||||
return "XBox 360 Controller";
|
||||
case SDL_CONTROLLER_TYPE_XBOXONE:
|
||||
return "XBox One Controller";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
const auto name = SDL_GameControllerName(sdl_controller.get());
|
||||
if (name) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
if (sdl_joystick) {
|
||||
const auto name = SDL_JoystickName(sdl_joystick.get());
|
||||
if (name) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
bool IsYAxis(u8 index) {
|
||||
if (!sdl_controller) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& binding_left_y =
|
||||
SDL_GameControllerGetBindForAxis(sdl_controller.get(), SDL_CONTROLLER_AXIS_LEFTY);
|
||||
const auto& binding_right_y =
|
||||
SDL_GameControllerGetBindForAxis(sdl_controller.get(), SDL_CONTROLLER_AXIS_RIGHTY);
|
||||
if (index == binding_left_y.value.axis) {
|
||||
return true;
|
||||
}
|
||||
if (index == binding_right_y.value.axis) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string guid;
|
||||
int port;
|
||||
std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
|
||||
std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller;
|
||||
mutable std::mutex mutex;
|
||||
|
||||
u64 last_motion_update{};
|
||||
bool has_gyro{false};
|
||||
bool has_accel{false};
|
||||
BasicMotion motion;
|
||||
};
|
||||
|
||||
std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickByGUID(const std::string& guid, int port) {
|
||||
std::lock_guard lock{joystick_map_mutex};
|
||||
const auto it = joystick_map.find(guid);
|
||||
|
||||
if (it != joystick_map.end()) {
|
||||
while (it->second.size() <= static_cast<std::size_t>(port)) {
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()),
|
||||
nullptr, nullptr);
|
||||
it->second.emplace_back(std::move(joystick));
|
||||
}
|
||||
|
||||
return it->second[static_cast<std::size_t>(port)];
|
||||
}
|
||||
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr);
|
||||
|
||||
return joystick_map[guid].emplace_back(std::move(joystick));
|
||||
}
|
||||
|
||||
std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {
|
||||
auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
|
||||
const std::string guid = GetGUID(sdl_joystick);
|
||||
|
||||
std::lock_guard lock{joystick_map_mutex};
|
||||
const auto map_it = joystick_map.find(guid);
|
||||
|
||||
if (map_it == joystick_map.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(),
|
||||
[&sdl_joystick](const auto& joystick) {
|
||||
return joystick->GetSDLJoystick() == sdl_joystick;
|
||||
});
|
||||
|
||||
if (vec_it == map_it->second.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return *vec_it;
|
||||
}
|
||||
|
||||
void SDLDriver::InitJoystick(int joystick_index) {
|
||||
SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
|
||||
SDL_GameController* sdl_gamecontroller = nullptr;
|
||||
|
||||
if (SDL_IsGameController(joystick_index)) {
|
||||
sdl_gamecontroller = SDL_GameControllerOpen(joystick_index);
|
||||
}
|
||||
|
||||
if (!sdl_joystick) {
|
||||
LOG_ERROR(Input, "Failed to open joystick {}", joystick_index);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string guid = GetGUID(sdl_joystick);
|
||||
|
||||
std::lock_guard lock{joystick_map_mutex};
|
||||
if (joystick_map.find(guid) == joystick_map.end()) {
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);
|
||||
PreSetController(joystick->GetPadIdentifier());
|
||||
SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel());
|
||||
joystick_map[guid].emplace_back(std::move(joystick));
|
||||
return;
|
||||
}
|
||||
|
||||
auto& joystick_guid_list = joystick_map[guid];
|
||||
const auto joystick_it =
|
||||
std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
|
||||
[](const auto& joystick) { return !joystick->GetSDLJoystick(); });
|
||||
|
||||
if (joystick_it != joystick_guid_list.end()) {
|
||||
(*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller);
|
||||
return;
|
||||
}
|
||||
|
||||
const int port = static_cast<int>(joystick_guid_list.size());
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller);
|
||||
PreSetController(joystick->GetPadIdentifier());
|
||||
SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel());
|
||||
joystick_guid_list.emplace_back(std::move(joystick));
|
||||
}
|
||||
|
||||
void SDLDriver::CloseJoystick(SDL_Joystick* sdl_joystick) {
|
||||
const std::string guid = GetGUID(sdl_joystick);
|
||||
|
||||
std::lock_guard lock{joystick_map_mutex};
|
||||
// This call to guid is safe since the joystick is guaranteed to be in the map
|
||||
const auto& joystick_guid_list = joystick_map[guid];
|
||||
const auto joystick_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
|
||||
[&sdl_joystick](const auto& joystick) {
|
||||
return joystick->GetSDLJoystick() == sdl_joystick;
|
||||
});
|
||||
|
||||
if (joystick_it != joystick_guid_list.end()) {
|
||||
(*joystick_it)->SetSDLJoystick(nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) {
|
||||
switch (event.type) {
|
||||
case SDL_JOYBUTTONUP: {
|
||||
if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
|
||||
const PadIdentifier identifier = joystick->GetPadIdentifier();
|
||||
SetButton(identifier, event.jbutton.button, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_JOYBUTTONDOWN: {
|
||||
if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
|
||||
const PadIdentifier identifier = joystick->GetPadIdentifier();
|
||||
SetButton(identifier, event.jbutton.button, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_JOYHATMOTION: {
|
||||
if (const auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) {
|
||||
const PadIdentifier identifier = joystick->GetPadIdentifier();
|
||||
SetHatButton(identifier, event.jhat.hat, event.jhat.value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_JOYAXISMOTION: {
|
||||
if (const auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) {
|
||||
const PadIdentifier identifier = joystick->GetPadIdentifier();
|
||||
// Vertical axis is inverted on nintendo compared to SDL
|
||||
if (joystick->IsYAxis(event.jaxis.axis)) {
|
||||
SetAxis(identifier, event.jaxis.axis, -event.jaxis.value / 32767.0f);
|
||||
break;
|
||||
}
|
||||
SetAxis(identifier, event.jaxis.axis, event.jaxis.value / 32767.0f);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_CONTROLLERSENSORUPDATE: {
|
||||
if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) {
|
||||
if (joystick->UpdateMotion(event.csensor)) {
|
||||
const PadIdentifier identifier = joystick->GetPadIdentifier();
|
||||
SetMotion(identifier, 0, joystick->GetMotion());
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_JOYDEVICEREMOVED:
|
||||
LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which);
|
||||
CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which));
|
||||
break;
|
||||
case SDL_JOYDEVICEADDED:
|
||||
LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which);
|
||||
InitJoystick(event.jdevice.which);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SDLDriver::CloseJoysticks() {
|
||||
std::lock_guard lock{joystick_map_mutex};
|
||||
joystick_map.clear();
|
||||
}
|
||||
|
||||
SDLDriver::SDLDriver(const std::string& input_engine_) : InputEngine(input_engine_) {
|
||||
Common::SetCurrentThreadName("yuzu:input:SDL");
|
||||
|
||||
if (!Settings::values.enable_raw_input) {
|
||||
// Disable raw input. When enabled this setting causes SDL to die when a web applet opens
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0");
|
||||
}
|
||||
|
||||
// Prevent SDL from adding undesired axis
|
||||
SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
|
||||
|
||||
// Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
||||
|
||||
// Use hidapi driver for joycons. This will allow joycons to be detected as a GameController and
|
||||
// not a generic one
|
||||
SDL_SetHint("SDL_JOYSTICK_HIDAPI_JOY_CONS", "1");
|
||||
|
||||
// Turn off Pro controller home led
|
||||
SDL_SetHint("SDL_JOYSTICK_HIDAPI_SWITCH_HOME_LED", "0");
|
||||
|
||||
// If the frontend is going to manage the event loop, then we don't start one here
|
||||
start_thread = SDL_WasInit(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) == 0;
|
||||
if (start_thread && SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) {
|
||||
LOG_CRITICAL(Input, "SDL_Init failed with: {}", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_AddEventWatch(&SDLEventWatcher, this);
|
||||
|
||||
initialized = true;
|
||||
if (start_thread) {
|
||||
poll_thread = std::thread([this] {
|
||||
using namespace std::chrono_literals;
|
||||
while (initialized) {
|
||||
SDL_PumpEvents();
|
||||
std::this_thread::sleep_for(1ms);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Because the events for joystick connection happens before we have our event watcher added, we
|
||||
// can just open all the joysticks right here
|
||||
for (int i = 0; i < SDL_NumJoysticks(); ++i) {
|
||||
InitJoystick(i);
|
||||
}
|
||||
}
|
||||
|
||||
SDLDriver::~SDLDriver() {
|
||||
CloseJoysticks();
|
||||
SDL_DelEventWatch(&SDLEventWatcher, this);
|
||||
|
||||
initialized = false;
|
||||
if (start_thread) {
|
||||
poll_thread.join();
|
||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const {
|
||||
std::vector<Common::ParamPackage> devices;
|
||||
std::unordered_map<int, std::shared_ptr<SDLJoystick>> joycon_pairs;
|
||||
for (const auto& [key, value] : joystick_map) {
|
||||
for (const auto& joystick : value) {
|
||||
if (!joystick->GetSDLJoystick()) {
|
||||
continue;
|
||||
}
|
||||
const std::string name =
|
||||
fmt::format("{} {}", joystick->GetControllerName(), joystick->GetPort());
|
||||
devices.emplace_back(Common::ParamPackage{
|
||||
{"engine", GetEngineName()},
|
||||
{"display", std::move(name)},
|
||||
{"guid", joystick->GetGUID()},
|
||||
{"port", std::to_string(joystick->GetPort())},
|
||||
});
|
||||
if (joystick->IsJoyconLeft()) {
|
||||
joycon_pairs.insert_or_assign(joystick->GetPort(), joystick);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add dual controllers
|
||||
for (const auto& [key, value] : joystick_map) {
|
||||
for (const auto& joystick : value) {
|
||||
if (joystick->IsJoyconRight()) {
|
||||
if (!joycon_pairs.contains(joystick->GetPort())) {
|
||||
continue;
|
||||
}
|
||||
const auto joystick2 = joycon_pairs.at(joystick->GetPort());
|
||||
|
||||
const std::string name =
|
||||
fmt::format("{} {}", "Nintendo Dual Joy-Con", joystick->GetPort());
|
||||
devices.emplace_back(Common::ParamPackage{
|
||||
{"engine", GetEngineName()},
|
||||
{"display", std::move(name)},
|
||||
{"guid", joystick->GetGUID()},
|
||||
{"guid2", joystick2->GetGUID()},
|
||||
{"port", std::to_string(joystick->GetPort())},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
Common::Input::VibrationError SDLDriver::SetRumble(const PadIdentifier& identifier,
|
||||
const Common::Input::VibrationStatus vibration) {
|
||||
const auto joystick =
|
||||
GetSDLJoystickByGUID(identifier.guid.Format(), static_cast<int>(identifier.port));
|
||||
const auto process_amplitude_exp = [](f32 amplitude, f32 factor) {
|
||||
return (amplitude + std::pow(amplitude, factor)) * 0.5f * 0xFFFF;
|
||||
};
|
||||
|
||||
// Default exponential curve for rumble
|
||||
f32 factor = 0.35f;
|
||||
|
||||
// If vibration is set as a linear output use a flatter value
|
||||
if (vibration.type == Common::Input::VibrationAmplificationType::Linear) {
|
||||
factor = 0.5f;
|
||||
}
|
||||
|
||||
// Amplitude for HD rumble needs no modification
|
||||
if (joystick->HasHDRumble()) {
|
||||
factor = 1.0f;
|
||||
}
|
||||
|
||||
const Common::Input::VibrationStatus new_vibration{
|
||||
.low_amplitude = process_amplitude_exp(vibration.low_amplitude, factor),
|
||||
.low_frequency = vibration.low_frequency,
|
||||
.high_amplitude = process_amplitude_exp(vibration.high_amplitude, factor),
|
||||
.high_frequency = vibration.high_frequency,
|
||||
.type = Common::Input::VibrationAmplificationType::Exponential,
|
||||
};
|
||||
|
||||
if (!joystick->RumblePlay(new_vibration)) {
|
||||
return Common::Input::VibrationError::Unknown;
|
||||
}
|
||||
|
||||
return Common::Input::VibrationError::None;
|
||||
}
|
||||
Common::ParamPackage SDLDriver::BuildAnalogParamPackageForButton(int port, std::string guid,
|
||||
s32 axis, float value) const {
|
||||
Common::ParamPackage params{};
|
||||
params.Set("engine", GetEngineName());
|
||||
params.Set("port", port);
|
||||
params.Set("guid", std::move(guid));
|
||||
params.Set("axis", axis);
|
||||
params.Set("threshold", "0.5");
|
||||
params.Set("invert", value < 0 ? "-" : "+");
|
||||
return params;
|
||||
}
|
||||
|
||||
Common::ParamPackage SDLDriver::BuildButtonParamPackageForButton(int port, std::string guid,
|
||||
s32 button) const {
|
||||
Common::ParamPackage params{};
|
||||
params.Set("engine", GetEngineName());
|
||||
params.Set("port", port);
|
||||
params.Set("guid", std::move(guid));
|
||||
params.Set("button", button);
|
||||
return params;
|
||||
}
|
||||
|
||||
Common::ParamPackage SDLDriver::BuildHatParamPackageForButton(int port, std::string guid, s32 hat,
|
||||
u8 value) const {
|
||||
Common::ParamPackage params{};
|
||||
params.Set("engine", GetEngineName());
|
||||
params.Set("port", port);
|
||||
params.Set("guid", std::move(guid));
|
||||
params.Set("hat", hat);
|
||||
params.Set("direction", GetHatButtonName(value));
|
||||
return params;
|
||||
}
|
||||
|
||||
Common::ParamPackage SDLDriver::BuildMotionParam(int port, std::string guid) const {
|
||||
Common::ParamPackage params{};
|
||||
params.Set("engine", GetEngineName());
|
||||
params.Set("motion", 0);
|
||||
params.Set("port", port);
|
||||
params.Set("guid", std::move(guid));
|
||||
return params;
|
||||
}
|
||||
|
||||
Common::ParamPackage SDLDriver::BuildParamPackageForBinding(
|
||||
int port, const std::string& guid, const SDL_GameControllerButtonBind& binding) const {
|
||||
switch (binding.bindType) {
|
||||
case SDL_CONTROLLER_BINDTYPE_NONE:
|
||||
break;
|
||||
case SDL_CONTROLLER_BINDTYPE_AXIS:
|
||||
return BuildAnalogParamPackageForButton(port, guid, binding.value.axis);
|
||||
case SDL_CONTROLLER_BINDTYPE_BUTTON:
|
||||
return BuildButtonParamPackageForButton(port, guid, binding.value.button);
|
||||
case SDL_CONTROLLER_BINDTYPE_HAT:
|
||||
return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat,
|
||||
static_cast<u8>(binding.value.hat.hat_mask));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Common::ParamPackage SDLDriver::BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x,
|
||||
int axis_y, float offset_x,
|
||||
float offset_y) const {
|
||||
Common::ParamPackage params;
|
||||
params.Set("engine", GetEngineName());
|
||||
params.Set("port", static_cast<int>(identifier.port));
|
||||
params.Set("guid", identifier.guid.Format());
|
||||
params.Set("axis_x", axis_x);
|
||||
params.Set("axis_y", axis_y);
|
||||
params.Set("offset_x", offset_x);
|
||||
params.Set("offset_y", offset_y);
|
||||
params.Set("invert_x", "+");
|
||||
params.Set("invert_y", "+");
|
||||
return params;
|
||||
}
|
||||
|
||||
ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& params) {
|
||||
if (!params.Has("guid") || !params.Has("port")) {
|
||||
return {};
|
||||
}
|
||||
const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
|
||||
|
||||
auto* controller = joystick->GetSDLGameController();
|
||||
if (controller == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// This list is missing ZL/ZR since those are not considered buttons in SDL GameController.
|
||||
// We will add those afterwards
|
||||
// This list also excludes Screenshot since theres not really a mapping for that
|
||||
ButtonBindings switch_to_sdl_button;
|
||||
|
||||
if (SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) {
|
||||
switch_to_sdl_button = GetNintendoButtonBinding(joystick);
|
||||
} else {
|
||||
switch_to_sdl_button = GetDefaultButtonBinding();
|
||||
}
|
||||
|
||||
// Add the missing bindings for ZL/ZR
|
||||
static constexpr ZButtonBindings switch_to_sdl_axis{{
|
||||
{Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT},
|
||||
{Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
|
||||
}};
|
||||
|
||||
// Parameters contain two joysticks return dual
|
||||
if (params.Has("guid2")) {
|
||||
const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
|
||||
|
||||
if (joystick2->GetSDLGameController() != nullptr) {
|
||||
return GetDualControllerMapping(joystick, joystick2, switch_to_sdl_button,
|
||||
switch_to_sdl_axis);
|
||||
}
|
||||
}
|
||||
|
||||
return GetSingleControllerMapping(joystick, switch_to_sdl_button, switch_to_sdl_axis);
|
||||
}
|
||||
|
||||
ButtonBindings SDLDriver::GetDefaultButtonBinding() const {
|
||||
return {
|
||||
std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
|
||||
{Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A},
|
||||
{Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y},
|
||||
{Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X},
|
||||
{Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
|
||||
{Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
|
||||
{Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
|
||||
{Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
|
||||
{Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
|
||||
{Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
|
||||
{Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
|
||||
{Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
|
||||
{Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
|
||||
{Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
|
||||
{Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
|
||||
{Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
|
||||
{Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
|
||||
};
|
||||
}
|
||||
|
||||
ButtonBindings SDLDriver::GetNintendoButtonBinding(
|
||||
const std::shared_ptr<SDLJoystick>& joystick) const {
|
||||
// Default SL/SR mapping for pro controllers
|
||||
auto sl_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
|
||||
auto sr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
|
||||
|
||||
if (joystick->IsJoyconLeft()) {
|
||||
sl_button = SDL_CONTROLLER_BUTTON_PADDLE2;
|
||||
sr_button = SDL_CONTROLLER_BUTTON_PADDLE4;
|
||||
}
|
||||
if (joystick->IsJoyconRight()) {
|
||||
sl_button = SDL_CONTROLLER_BUTTON_PADDLE3;
|
||||
sr_button = SDL_CONTROLLER_BUTTON_PADDLE1;
|
||||
}
|
||||
|
||||
return {
|
||||
std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_A},
|
||||
{Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_B},
|
||||
{Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_X},
|
||||
{Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_Y},
|
||||
{Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
|
||||
{Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
|
||||
{Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
|
||||
{Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
|
||||
{Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
|
||||
{Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
|
||||
{Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
|
||||
{Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
|
||||
{Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
|
||||
{Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
|
||||
{Settings::NativeButton::SL, sl_button},
|
||||
{Settings::NativeButton::SR, sr_button},
|
||||
{Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
|
||||
};
|
||||
}
|
||||
|
||||
ButtonMapping SDLDriver::GetSingleControllerMapping(
|
||||
const std::shared_ptr<SDLJoystick>& joystick, const ButtonBindings& switch_to_sdl_button,
|
||||
const ZButtonBindings& switch_to_sdl_axis) const {
|
||||
ButtonMapping mapping;
|
||||
mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
|
||||
auto* controller = joystick->GetSDLGameController();
|
||||
|
||||
for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
|
||||
const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
|
||||
mapping.insert_or_assign(
|
||||
switch_button,
|
||||
BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
|
||||
}
|
||||
for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
|
||||
const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
|
||||
mapping.insert_or_assign(
|
||||
switch_button,
|
||||
BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
ButtonMapping SDLDriver::GetDualControllerMapping(const std::shared_ptr<SDLJoystick>& joystick,
|
||||
const std::shared_ptr<SDLJoystick>& joystick2,
|
||||
const ButtonBindings& switch_to_sdl_button,
|
||||
const ZButtonBindings& switch_to_sdl_axis) const {
|
||||
ButtonMapping mapping;
|
||||
mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
|
||||
auto* controller = joystick->GetSDLGameController();
|
||||
auto* controller2 = joystick2->GetSDLGameController();
|
||||
|
||||
for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
|
||||
if (IsButtonOnLeftSide(switch_button)) {
|
||||
const auto& binding = SDL_GameControllerGetBindForButton(controller2, sdl_button);
|
||||
mapping.insert_or_assign(
|
||||
switch_button,
|
||||
BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
|
||||
continue;
|
||||
}
|
||||
const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
|
||||
mapping.insert_or_assign(
|
||||
switch_button,
|
||||
BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
|
||||
}
|
||||
for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
|
||||
if (IsButtonOnLeftSide(switch_button)) {
|
||||
const auto& binding = SDL_GameControllerGetBindForAxis(controller2, sdl_axis);
|
||||
mapping.insert_or_assign(
|
||||
switch_button,
|
||||
BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
|
||||
continue;
|
||||
}
|
||||
const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
|
||||
mapping.insert_or_assign(
|
||||
switch_button,
|
||||
BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
bool SDLDriver::IsButtonOnLeftSide(Settings::NativeButton::Values button) const {
|
||||
switch (button) {
|
||||
case Settings::NativeButton::DDown:
|
||||
case Settings::NativeButton::DLeft:
|
||||
case Settings::NativeButton::DRight:
|
||||
case Settings::NativeButton::DUp:
|
||||
case Settings::NativeButton::L:
|
||||
case Settings::NativeButton::LStick:
|
||||
case Settings::NativeButton::Minus:
|
||||
case Settings::NativeButton::Screenshot:
|
||||
case Settings::NativeButton::ZL:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
AnalogMapping SDLDriver::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
|
||||
if (!params.Has("guid") || !params.Has("port")) {
|
||||
return {};
|
||||
}
|
||||
const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
|
||||
const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
|
||||
auto* controller = joystick->GetSDLGameController();
|
||||
if (controller == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
AnalogMapping mapping = {};
|
||||
const auto& binding_left_x =
|
||||
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
|
||||
const auto& binding_left_y =
|
||||
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
|
||||
if (params.Has("guid2")) {
|
||||
const auto identifier = joystick2->GetPadIdentifier();
|
||||
PreSetController(identifier);
|
||||
PreSetAxis(identifier, binding_left_x.value.axis);
|
||||
PreSetAxis(identifier, binding_left_y.value.axis);
|
||||
const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis);
|
||||
const auto left_offset_y = -GetAxis(identifier, binding_left_y.value.axis);
|
||||
mapping.insert_or_assign(Settings::NativeAnalog::LStick,
|
||||
BuildParamPackageForAnalog(identifier, binding_left_x.value.axis,
|
||||
binding_left_y.value.axis,
|
||||
left_offset_x, left_offset_y));
|
||||
} else {
|
||||
const auto identifier = joystick->GetPadIdentifier();
|
||||
PreSetController(identifier);
|
||||
PreSetAxis(identifier, binding_left_x.value.axis);
|
||||
PreSetAxis(identifier, binding_left_y.value.axis);
|
||||
const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis);
|
||||
const auto left_offset_y = -GetAxis(identifier, binding_left_y.value.axis);
|
||||
mapping.insert_or_assign(Settings::NativeAnalog::LStick,
|
||||
BuildParamPackageForAnalog(identifier, binding_left_x.value.axis,
|
||||
binding_left_y.value.axis,
|
||||
left_offset_x, left_offset_y));
|
||||
}
|
||||
const auto& binding_right_x =
|
||||
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
|
||||
const auto& binding_right_y =
|
||||
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
|
||||
const auto identifier = joystick->GetPadIdentifier();
|
||||
PreSetController(identifier);
|
||||
PreSetAxis(identifier, binding_right_x.value.axis);
|
||||
PreSetAxis(identifier, binding_right_y.value.axis);
|
||||
const auto right_offset_x = -GetAxis(identifier, binding_right_x.value.axis);
|
||||
const auto right_offset_y = -GetAxis(identifier, binding_right_y.value.axis);
|
||||
mapping.insert_or_assign(Settings::NativeAnalog::RStick,
|
||||
BuildParamPackageForAnalog(identifier, binding_right_x.value.axis,
|
||||
binding_right_y.value.axis, right_offset_x,
|
||||
right_offset_y));
|
||||
return mapping;
|
||||
}
|
||||
|
||||
MotionMapping SDLDriver::GetMotionMappingForDevice(const Common::ParamPackage& params) {
|
||||
if (!params.Has("guid") || !params.Has("port")) {
|
||||
return {};
|
||||
}
|
||||
const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
|
||||
const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
|
||||
auto* controller = joystick->GetSDLGameController();
|
||||
if (controller == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
MotionMapping mapping = {};
|
||||
joystick->EnableMotion();
|
||||
|
||||
if (joystick->HasGyro() || joystick->HasAccel()) {
|
||||
mapping.insert_or_assign(Settings::NativeMotion::MotionRight,
|
||||
BuildMotionParam(joystick->GetPort(), joystick->GetGUID()));
|
||||
}
|
||||
if (params.Has("guid2")) {
|
||||
joystick2->EnableMotion();
|
||||
if (joystick2->HasGyro() || joystick2->HasAccel()) {
|
||||
mapping.insert_or_assign(Settings::NativeMotion::MotionLeft,
|
||||
BuildMotionParam(joystick2->GetPort(), joystick2->GetGUID()));
|
||||
}
|
||||
} else {
|
||||
if (joystick->HasGyro() || joystick->HasAccel()) {
|
||||
mapping.insert_or_assign(Settings::NativeMotion::MotionLeft,
|
||||
BuildMotionParam(joystick->GetPort(), joystick->GetGUID()));
|
||||
}
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
std::string SDLDriver::GetUIName(const Common::ParamPackage& params) const {
|
||||
if (params.Has("button")) {
|
||||
// TODO(German77): Find how to substitue the values for real button names
|
||||
return fmt::format("Button {}", params.Get("button", 0));
|
||||
}
|
||||
if (params.Has("hat")) {
|
||||
return fmt::format("Hat {}", params.Get("direction", ""));
|
||||
}
|
||||
if (params.Has("axis")) {
|
||||
return fmt::format("Axis {}", params.Get("axis", ""));
|
||||
}
|
||||
if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) {
|
||||
return fmt::format("Axis {},{},{}", params.Get("axis_x", ""), params.Get("axis_y", ""),
|
||||
params.Get("axis_z", ""));
|
||||
}
|
||||
if (params.Has("motion")) {
|
||||
return "SDL motion";
|
||||
}
|
||||
|
||||
return "Bad SDL";
|
||||
}
|
||||
|
||||
std::string SDLDriver::GetHatButtonName(u8 direction_value) const {
|
||||
switch (direction_value) {
|
||||
case SDL_HAT_UP:
|
||||
return "up";
|
||||
case SDL_HAT_DOWN:
|
||||
return "down";
|
||||
case SDL_HAT_LEFT:
|
||||
return "left";
|
||||
case SDL_HAT_RIGHT:
|
||||
return "right";
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
u8 SDLDriver::GetHatButtonId(const std::string direction_name) const {
|
||||
Uint8 direction;
|
||||
if (direction_name == "up") {
|
||||
direction = SDL_HAT_UP;
|
||||
} else if (direction_name == "down") {
|
||||
direction = SDL_HAT_DOWN;
|
||||
} else if (direction_name == "left") {
|
||||
direction = SDL_HAT_LEFT;
|
||||
} else if (direction_name == "right") {
|
||||
direction = SDL_HAT_RIGHT;
|
||||
} else {
|
||||
direction = 0;
|
||||
}
|
||||
return direction;
|
||||
}
|
||||
|
||||
} // namespace InputCommon
|
117
src/input_common/drivers/sdl_driver.h
Executable file
117
src/input_common/drivers/sdl_driver.h
Executable file
|
@ -0,0 +1,117 @@
|
|||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "input_common/input_engine.h"
|
||||
|
||||
union SDL_Event;
|
||||
using SDL_GameController = struct _SDL_GameController;
|
||||
using SDL_Joystick = struct _SDL_Joystick;
|
||||
using SDL_JoystickID = s32;
|
||||
|
||||
using ButtonBindings =
|
||||
std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 17>;
|
||||
using ZButtonBindings =
|
||||
std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>;
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
class SDLJoystick;
|
||||
|
||||
class SDLDriver : public InputCommon::InputEngine {
|
||||
public:
|
||||
/// Initializes and registers SDL device factories
|
||||
SDLDriver(const std::string& input_engine_);
|
||||
|
||||
/// Unregisters SDL device factories and shut them down.
|
||||
~SDLDriver() override;
|
||||
|
||||
/// Handle SDL_Events for joysticks from SDL_PollEvent
|
||||
void HandleGameControllerEvent(const SDL_Event& event);
|
||||
|
||||
/// Get the nth joystick with the corresponding GUID
|
||||
std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id);
|
||||
|
||||
/**
|
||||
* Check how many identical joysticks (by guid) were connected before the one with sdl_id and so
|
||||
* tie it to a SDLJoystick with the same guid and that port
|
||||
*/
|
||||
std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port);
|
||||
|
||||
std::vector<Common::ParamPackage> GetInputDevices() const override;
|
||||
|
||||
ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
|
||||
AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
|
||||
MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override;
|
||||
std::string GetUIName(const Common::ParamPackage& params) const override;
|
||||
|
||||
std::string GetHatButtonName(u8 direction_value) const override;
|
||||
u8 GetHatButtonId(const std::string direction_name) const override;
|
||||
|
||||
Common::Input::VibrationError SetRumble(
|
||||
const PadIdentifier& identifier, const Common::Input::VibrationStatus vibration) override;
|
||||
|
||||
private:
|
||||
void InitJoystick(int joystick_index);
|
||||
void CloseJoystick(SDL_Joystick* sdl_joystick);
|
||||
|
||||
/// Needs to be called before SDL_QuitSubSystem.
|
||||
void CloseJoysticks();
|
||||
|
||||
Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, s32 axis,
|
||||
float value = 0.1f) const;
|
||||
Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid,
|
||||
s32 button) const;
|
||||
|
||||
Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, s32 hat,
|
||||
u8 value) const;
|
||||
|
||||
Common::ParamPackage BuildMotionParam(int port, std::string guid) const;
|
||||
|
||||
Common::ParamPackage BuildParamPackageForBinding(
|
||||
int port, const std::string& guid, const SDL_GameControllerButtonBind& binding) const;
|
||||
|
||||
Common::ParamPackage BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x,
|
||||
int axis_y, float offset_x,
|
||||
float offset_y) const;
|
||||
|
||||
/// Returns the default button bindings list for generic controllers
|
||||
ButtonBindings GetDefaultButtonBinding() const;
|
||||
|
||||
/// Returns the default button bindings list for nintendo controllers
|
||||
ButtonBindings GetNintendoButtonBinding(const std::shared_ptr<SDLJoystick>& joystick) const;
|
||||
|
||||
/// Returns the button mappings from a single controller
|
||||
ButtonMapping GetSingleControllerMapping(const std::shared_ptr<SDLJoystick>& joystick,
|
||||
const ButtonBindings& switch_to_sdl_button,
|
||||
const ZButtonBindings& switch_to_sdl_axis) const;
|
||||
|
||||
/// Returns the button mappings from two different controllers
|
||||
ButtonMapping GetDualControllerMapping(const std::shared_ptr<SDLJoystick>& joystick,
|
||||
const std::shared_ptr<SDLJoystick>& joystick2,
|
||||
const ButtonBindings& switch_to_sdl_button,
|
||||
const ZButtonBindings& switch_to_sdl_axis) const;
|
||||
|
||||
/// Returns true if the button is on the left joycon
|
||||
bool IsButtonOnLeftSide(Settings::NativeButton::Values button) const;
|
||||
|
||||
/// Map of GUID of a list of corresponding virtual Joysticks
|
||||
std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
|
||||
std::mutex joystick_map_mutex;
|
||||
|
||||
bool start_thread = false;
|
||||
std::atomic<bool> initialized = false;
|
||||
|
||||
std::thread poll_thread;
|
||||
};
|
||||
} // namespace InputCommon
|
321
src/input_common/drivers/tas_input.cpp
Executable file
321
src/input_common/drivers/tas_input.cpp
Executable file
|
@ -0,0 +1,321 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include <regex>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/fs_types.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "input_common/drivers/tas_input.h"
|
||||
|
||||
namespace InputCommon::TasInput {
|
||||
|
||||
enum TasAxes : u8 {
|
||||
StickX,
|
||||
StickY,
|
||||
SubstickX,
|
||||
SubstickY,
|
||||
Undefined,
|
||||
};
|
||||
|
||||
// Supported keywords and buttons from a TAS file
|
||||
constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = {
|
||||
std::pair{"KEY_A", TasButton::BUTTON_A},
|
||||
{"KEY_B", TasButton::BUTTON_B},
|
||||
{"KEY_X", TasButton::BUTTON_X},
|
||||
{"KEY_Y", TasButton::BUTTON_Y},
|
||||
{"KEY_LSTICK", TasButton::STICK_L},
|
||||
{"KEY_RSTICK", TasButton::STICK_R},
|
||||
{"KEY_L", TasButton::TRIGGER_L},
|
||||
{"KEY_R", TasButton::TRIGGER_R},
|
||||
{"KEY_PLUS", TasButton::BUTTON_PLUS},
|
||||
{"KEY_MINUS", TasButton::BUTTON_MINUS},
|
||||
{"KEY_DLEFT", TasButton::BUTTON_LEFT},
|
||||
{"KEY_DUP", TasButton::BUTTON_UP},
|
||||
{"KEY_DRIGHT", TasButton::BUTTON_RIGHT},
|
||||
{"KEY_DDOWN", TasButton::BUTTON_DOWN},
|
||||
{"KEY_SL", TasButton::BUTTON_SL},
|
||||
{"KEY_SR", TasButton::BUTTON_SR},
|
||||
{"KEY_CAPTURE", TasButton::BUTTON_CAPTURE},
|
||||
{"KEY_HOME", TasButton::BUTTON_HOME},
|
||||
{"KEY_ZL", TasButton::TRIGGER_ZL},
|
||||
{"KEY_ZR", TasButton::TRIGGER_ZR},
|
||||
};
|
||||
|
||||
Tas::Tas(const std::string input_engine_) : InputCommon::InputEngine(input_engine_) {
|
||||
for (size_t player_index = 0; player_index < PLAYER_NUMBER; player_index++) {
|
||||
PadIdentifier identifier{
|
||||
.guid = Common::UUID{},
|
||||
.port = player_index,
|
||||
.pad = 0,
|
||||
};
|
||||
PreSetController(identifier);
|
||||
}
|
||||
ClearInput();
|
||||
if (!Settings::values.tas_enable) {
|
||||
needs_reset = true;
|
||||
return;
|
||||
}
|
||||
LoadTasFiles();
|
||||
}
|
||||
|
||||
Tas::~Tas() {
|
||||
Stop();
|
||||
};
|
||||
|
||||
void Tas::LoadTasFiles() {
|
||||
script_length = 0;
|
||||
for (size_t i = 0; i < commands.size(); i++) {
|
||||
LoadTasFile(i);
|
||||
if (commands[i].size() > script_length) {
|
||||
script_length = commands[i].size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Tas::LoadTasFile(size_t player_index) {
|
||||
if (!commands[player_index].empty()) {
|
||||
commands[player_index].clear();
|
||||
}
|
||||
std::string file =
|
||||
Common::FS::ReadStringFromFile(Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) /
|
||||
fmt::format("script0-{}.txt", player_index + 1),
|
||||
Common::FS::FileType::BinaryFile);
|
||||
std::stringstream command_line(file);
|
||||
std::string line;
|
||||
int frame_no = 0;
|
||||
while (std::getline(command_line, line, '\n')) {
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
std::smatch m;
|
||||
|
||||
std::stringstream linestream(line);
|
||||
std::string segment;
|
||||
std::vector<std::string> seglist;
|
||||
|
||||
while (std::getline(linestream, segment, ' ')) {
|
||||
seglist.push_back(segment);
|
||||
}
|
||||
|
||||
if (seglist.size() < 4) {
|
||||
continue;
|
||||
}
|
||||
|
||||
while (frame_no < std::stoi(seglist.at(0))) {
|
||||
commands[player_index].push_back({});
|
||||
frame_no++;
|
||||
}
|
||||
|
||||
TASCommand command = {
|
||||
.buttons = ReadCommandButtons(seglist.at(1)),
|
||||
.l_axis = ReadCommandAxis(seglist.at(2)),
|
||||
.r_axis = ReadCommandAxis(seglist.at(3)),
|
||||
};
|
||||
commands[player_index].push_back(command);
|
||||
frame_no++;
|
||||
}
|
||||
LOG_INFO(Input, "TAS file loaded! {} frames", frame_no);
|
||||
}
|
||||
|
||||
void Tas::WriteTasFile(std::u8string file_name) {
|
||||
std::string output_text;
|
||||
for (size_t frame = 0; frame < record_commands.size(); frame++) {
|
||||
const TASCommand& line = record_commands[frame];
|
||||
output_text += fmt::format("{} {} {} {}\n", frame, WriteCommandButtons(line.buttons),
|
||||
WriteCommandAxis(line.l_axis), WriteCommandAxis(line.r_axis));
|
||||
}
|
||||
const auto bytes_written = Common::FS::WriteStringToFile(
|
||||
Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name,
|
||||
Common::FS::FileType::TextFile, output_text);
|
||||
if (bytes_written == output_text.size()) {
|
||||
LOG_INFO(Input, "TAS file written to file!");
|
||||
} else {
|
||||
LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written,
|
||||
output_text.size());
|
||||
}
|
||||
}
|
||||
|
||||
void Tas::RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis) {
|
||||
last_input = {
|
||||
.buttons = buttons,
|
||||
.l_axis = FlipAxisY(left_axis),
|
||||
.r_axis = FlipAxisY(right_axis),
|
||||
};
|
||||
}
|
||||
|
||||
TasAnalog Tas::FlipAxisY(TasAnalog old) {
|
||||
return {
|
||||
.x = old.x,
|
||||
.y = -old.y,
|
||||
};
|
||||
}
|
||||
|
||||
std::tuple<TasState, size_t, size_t> Tas::GetStatus() const {
|
||||
TasState state;
|
||||
if (is_recording) {
|
||||
return {TasState::Recording, 0, record_commands.size()};
|
||||
}
|
||||
|
||||
if (is_running) {
|
||||
state = TasState::Running;
|
||||
} else {
|
||||
state = TasState::Stopped;
|
||||
}
|
||||
|
||||
return {state, current_command, script_length};
|
||||
}
|
||||
|
||||
void Tas::UpdateThread() {
|
||||
if (!Settings::values.tas_enable) {
|
||||
if (is_running) {
|
||||
Stop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_recording) {
|
||||
record_commands.push_back(last_input);
|
||||
}
|
||||
if (needs_reset) {
|
||||
current_command = 0;
|
||||
needs_reset = false;
|
||||
LoadTasFiles();
|
||||
LOG_DEBUG(Input, "tas_reset done");
|
||||
}
|
||||
|
||||
if (!is_running) {
|
||||
ClearInput();
|
||||
return;
|
||||
}
|
||||
if (current_command < script_length) {
|
||||
LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length);
|
||||
const size_t frame = current_command++;
|
||||
for (size_t player_index = 0; player_index < commands.size(); player_index++) {
|
||||
TASCommand command{};
|
||||
if (frame < commands[player_index].size()) {
|
||||
command = commands[player_index][frame];
|
||||
}
|
||||
|
||||
PadIdentifier identifier{
|
||||
.guid = Common::UUID{},
|
||||
.port = player_index,
|
||||
.pad = 0,
|
||||
};
|
||||
for (std::size_t i = 0; i < sizeof(command.buttons) * 8; ++i) {
|
||||
const bool button_status = (command.buttons & (1LLU << i)) != 0;
|
||||
const int button = static_cast<int>(i);
|
||||
SetButton(identifier, button, button_status);
|
||||
}
|
||||
SetAxis(identifier, TasAxes::StickX, command.l_axis.x);
|
||||
SetAxis(identifier, TasAxes::StickY, command.l_axis.y);
|
||||
SetAxis(identifier, TasAxes::SubstickX, command.r_axis.x);
|
||||
SetAxis(identifier, TasAxes::SubstickY, command.r_axis.y);
|
||||
}
|
||||
} else {
|
||||
is_running = Settings::values.tas_loop.GetValue();
|
||||
current_command = 0;
|
||||
ClearInput();
|
||||
}
|
||||
}
|
||||
|
||||
void Tas::ClearInput() {
|
||||
ResetButtonState();
|
||||
ResetAnalogState();
|
||||
}
|
||||
|
||||
TasAnalog Tas::ReadCommandAxis(const std::string& line) const {
|
||||
std::stringstream linestream(line);
|
||||
std::string segment;
|
||||
std::vector<std::string> seglist;
|
||||
|
||||
while (std::getline(linestream, segment, ';')) {
|
||||
seglist.push_back(segment);
|
||||
}
|
||||
|
||||
const float x = std::stof(seglist.at(0)) / 32767.0f;
|
||||
const float y = std::stof(seglist.at(1)) / 32767.0f;
|
||||
|
||||
return {x, y};
|
||||
}
|
||||
|
||||
u64 Tas::ReadCommandButtons(const std::string& data) const {
|
||||
std::stringstream button_text(data);
|
||||
std::string line;
|
||||
u64 buttons = 0;
|
||||
while (std::getline(button_text, line, ';')) {
|
||||
for (auto [text, tas_button] : text_to_tas_button) {
|
||||
if (text == line) {
|
||||
buttons |= static_cast<u64>(tas_button);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return buttons;
|
||||
}
|
||||
|
||||
std::string Tas::WriteCommandButtons(u64 buttons) const {
|
||||
std::string returns = "";
|
||||
for (auto [text_button, tas_button] : text_to_tas_button) {
|
||||
if ((buttons & static_cast<u64>(tas_button)) != 0) {
|
||||
returns += fmt::format("{};", text_button);
|
||||
}
|
||||
}
|
||||
return returns.empty() ? "NONE" : returns;
|
||||
}
|
||||
|
||||
std::string Tas::WriteCommandAxis(TasAnalog analog) const {
|
||||
return fmt::format("{};{}", analog.x * 32767, analog.y * 32767);
|
||||
}
|
||||
|
||||
void Tas::StartStop() {
|
||||
if (!Settings::values.tas_enable) {
|
||||
return;
|
||||
}
|
||||
if (is_running) {
|
||||
Stop();
|
||||
} else {
|
||||
is_running = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Tas::Stop() {
|
||||
is_running = false;
|
||||
}
|
||||
|
||||
void Tas::Reset() {
|
||||
if (!Settings::values.tas_enable) {
|
||||
return;
|
||||
}
|
||||
needs_reset = true;
|
||||
}
|
||||
|
||||
bool Tas::Record() {
|
||||
if (!Settings::values.tas_enable) {
|
||||
return true;
|
||||
}
|
||||
is_recording = !is_recording;
|
||||
return is_recording;
|
||||
}
|
||||
|
||||
void Tas::SaveRecording(bool overwrite_file) {
|
||||
if (is_recording) {
|
||||
return;
|
||||
}
|
||||
if (record_commands.empty()) {
|
||||
return;
|
||||
}
|
||||
WriteTasFile(u8"record.txt");
|
||||
if (overwrite_file) {
|
||||
WriteTasFile(u8"script0-1.txt");
|
||||
}
|
||||
needs_reset = true;
|
||||
record_commands.clear();
|
||||
}
|
||||
|
||||
} // namespace InputCommon::TasInput
|
199
src/input_common/drivers/tas_input.h
Executable file
199
src/input_common/drivers/tas_input.h
Executable file
|
@ -0,0 +1,199 @@
|
|||
// 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"
|
||||
#include "common/settings_input.h"
|
||||
#include "input_common/input_engine.h"
|
||||
#include "input_common/main.h"
|
||||
|
||||
/*
|
||||
To play back TAS scripts on Yuzu, select the folder with scripts in the configuration menu below
|
||||
Tools -> Configure TAS. The file itself has normal text format and has to be called script0-1.txt
|
||||
for controller 1, script0-2.txt for controller 2 and so forth (with max. 8 players).
|
||||
|
||||
A script file has the same format as TAS-nx uses, so final files will look like this:
|
||||
|
||||
1 KEY_B 0;0 0;0
|
||||
6 KEY_ZL 0;0 0;0
|
||||
41 KEY_ZL;KEY_Y 0;0 0;0
|
||||
43 KEY_X;KEY_A 32767;0 0;0
|
||||
44 KEY_A 32767;0 0;0
|
||||
45 KEY_A 32767;0 0;0
|
||||
46 KEY_A 32767;0 0;0
|
||||
47 KEY_A 32767;0 0;0
|
||||
|
||||
After placing the file at the correct location, it can be read into Yuzu with the (default) hotkey
|
||||
CTRL+F6 (refresh). In the bottom left corner, it will display the amount of frames the script file
|
||||
has. Playback can be started or stopped using CTRL+F5.
|
||||
|
||||
However, for playback to actually work, the correct input device has to be selected: In the Controls
|
||||
menu, select TAS from the device list for the controller that the script should be played on.
|
||||
|
||||
Recording a new script file is really simple: Just make sure that the proper device (not TAS) is
|
||||
connected on P1, and press CTRL+F7 to start recording. When done, just press the same keystroke
|
||||
again (CTRL+F7). The new script will be saved at the location previously selected, as the filename
|
||||
record.txt.
|
||||
|
||||
For debugging purposes, the common controller debugger can be used (View -> Debugging -> Controller
|
||||
P1).
|
||||
*/
|
||||
|
||||
namespace InputCommon::TasInput {
|
||||
|
||||
constexpr size_t PLAYER_NUMBER = 10;
|
||||
|
||||
enum class TasButton : u64 {
|
||||
BUTTON_A = 1U << 0,
|
||||
BUTTON_B = 1U << 1,
|
||||
BUTTON_X = 1U << 2,
|
||||
BUTTON_Y = 1U << 3,
|
||||
STICK_L = 1U << 4,
|
||||
STICK_R = 1U << 5,
|
||||
TRIGGER_L = 1U << 6,
|
||||
TRIGGER_R = 1U << 7,
|
||||
TRIGGER_ZL = 1U << 8,
|
||||
TRIGGER_ZR = 1U << 9,
|
||||
BUTTON_PLUS = 1U << 10,
|
||||
BUTTON_MINUS = 1U << 11,
|
||||
BUTTON_LEFT = 1U << 12,
|
||||
BUTTON_UP = 1U << 13,
|
||||
BUTTON_RIGHT = 1U << 14,
|
||||
BUTTON_DOWN = 1U << 15,
|
||||
BUTTON_SL = 1U << 16,
|
||||
BUTTON_SR = 1U << 17,
|
||||
BUTTON_HOME = 1U << 18,
|
||||
BUTTON_CAPTURE = 1U << 19,
|
||||
};
|
||||
|
||||
struct TasAnalog {
|
||||
float x{};
|
||||
float y{};
|
||||
};
|
||||
|
||||
enum class TasState {
|
||||
Running,
|
||||
Recording,
|
||||
Stopped,
|
||||
};
|
||||
|
||||
class Tas final : public InputCommon::InputEngine {
|
||||
public:
|
||||
explicit Tas(const std::string input_engine_);
|
||||
~Tas();
|
||||
|
||||
/**
|
||||
* Changes the input status that will be stored in each frame
|
||||
* @param buttons: bitfield with the status of the buttons
|
||||
* @param left_axis: value of the left axis
|
||||
* @param right_axis: value of the right axis
|
||||
*/
|
||||
void RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis);
|
||||
|
||||
// Main loop that records or executes input
|
||||
void UpdateThread();
|
||||
|
||||
// Sets the flag to start or stop the TAS command excecution and swaps controllers profiles
|
||||
void StartStop();
|
||||
|
||||
// Stop the TAS and reverts any controller profile
|
||||
void Stop();
|
||||
|
||||
// Sets the flag to reload the file and start from the begining in the next update
|
||||
void Reset();
|
||||
|
||||
/**
|
||||
* Sets the flag to enable or disable recording of inputs
|
||||
* @return Returns true if the current recording status is enabled
|
||||
*/
|
||||
bool Record();
|
||||
|
||||
/**
|
||||
* Saves contents of record_commands on a file
|
||||
* @param overwrite_file: Indicates if player 1 should be overwritten
|
||||
*/
|
||||
void SaveRecording(bool overwrite_file);
|
||||
|
||||
/**
|
||||
* Returns the current status values of TAS playback/recording
|
||||
* @return Tuple of
|
||||
* TasState indicating the current state out of Running ;
|
||||
* Current playback progress ;
|
||||
* Total length of script file currently loaded or being recorded
|
||||
*/
|
||||
std::tuple<TasState, size_t, size_t> GetStatus() const;
|
||||
|
||||
private:
|
||||
struct TASCommand {
|
||||
u64 buttons{};
|
||||
TasAnalog l_axis{};
|
||||
TasAnalog r_axis{};
|
||||
};
|
||||
|
||||
/// Loads TAS files from all players
|
||||
void LoadTasFiles();
|
||||
|
||||
/** Loads TAS file from the specified player
|
||||
* @param player_index: player number where data is going to be stored
|
||||
*/
|
||||
void LoadTasFile(size_t player_index);
|
||||
|
||||
/** Writes a TAS file from the recorded commands
|
||||
* @param file_name: name of the file to be written
|
||||
*/
|
||||
void WriteTasFile(std::u8string file_name);
|
||||
|
||||
/** Inverts the Y axis polarity
|
||||
* @param old: value of the axis
|
||||
* @return new value of the axis
|
||||
*/
|
||||
TasAnalog FlipAxisY(TasAnalog old);
|
||||
|
||||
/**
|
||||
* Parses a string containing the axis values. X and Y have a range from -32767 to 32767
|
||||
* @param line: string containing axis values with the following format "x;y"
|
||||
* @return Returns a TAS analog object with axis values with range from -1.0 to 1.0
|
||||
*/
|
||||
TasAnalog ReadCommandAxis(const std::string& line) const;
|
||||
|
||||
/**
|
||||
* Parses a string containing the button values. Each button is represented by it's text format
|
||||
* specified in text_to_tas_button array
|
||||
* @param line: string containing button name with the following format "a;b;c;d..."
|
||||
* @return Returns a u64 with each bit representing the status of a button
|
||||
*/
|
||||
u64 ReadCommandButtons(const std::string& line) const;
|
||||
|
||||
/**
|
||||
* Reset state of all players
|
||||
*/
|
||||
void ClearInput();
|
||||
|
||||
/**
|
||||
* Converts an u64 containing the button status into the text equivalent
|
||||
* @param buttons: bitfield with the status of the buttons
|
||||
* @return Returns a string with the name of the buttons to be written to the file
|
||||
*/
|
||||
std::string WriteCommandButtons(u64 buttons) const;
|
||||
|
||||
/**
|
||||
* Converts an TAS analog object containing the axis status into the text equivalent
|
||||
* @param data: value of the axis
|
||||
* @return A string with the value of the axis to be written to the file
|
||||
*/
|
||||
std::string WriteCommandAxis(TasAnalog data) const;
|
||||
|
||||
size_t script_length{0};
|
||||
bool is_recording{false};
|
||||
bool is_running{false};
|
||||
bool needs_reset{false};
|
||||
std::array<std::vector<TASCommand>, PLAYER_NUMBER> commands{};
|
||||
std::vector<TASCommand> record_commands{};
|
||||
size_t current_command{0};
|
||||
TASCommand last_input{}; // only used for recording
|
||||
};
|
||||
} // namespace InputCommon::TasInput
|
53
src/input_common/drivers/touch_screen.cpp
Executable file
53
src/input_common/drivers/touch_screen.cpp
Executable file
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included
|
||||
|
||||
#include "common/param_package.h"
|
||||
#include "input_common/drivers/touch_screen.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
constexpr PadIdentifier identifier = {
|
||||
.guid = Common::UUID{Common::INVALID_UUID},
|
||||
.port = 0,
|
||||
.pad = 0,
|
||||
};
|
||||
|
||||
TouchScreen::TouchScreen(const std::string input_engine_) : InputEngine(input_engine_) {
|
||||
PreSetController(identifier);
|
||||
}
|
||||
|
||||
void TouchScreen::TouchMoved(float x, float y, std::size_t finger) {
|
||||
if (finger >= 16) {
|
||||
return;
|
||||
}
|
||||
TouchPressed(x, y, finger);
|
||||
}
|
||||
|
||||
void TouchScreen::TouchPressed(float x, float y, std::size_t finger) {
|
||||
if (finger >= 16) {
|
||||
return;
|
||||
}
|
||||
SetButton(identifier, static_cast<int>(finger), true);
|
||||
SetAxis(identifier, static_cast<int>(finger * 2), x);
|
||||
SetAxis(identifier, static_cast<int>(finger * 2 + 1), y);
|
||||
}
|
||||
|
||||
void TouchScreen::TouchReleased(std::size_t finger) {
|
||||
if (finger >= 16) {
|
||||
return;
|
||||
}
|
||||
SetButton(identifier, static_cast<int>(finger), false);
|
||||
SetAxis(identifier, static_cast<int>(finger * 2), 0.0f);
|
||||
SetAxis(identifier, static_cast<int>(finger * 2 + 1), 0.0f);
|
||||
}
|
||||
|
||||
void TouchScreen::ReleaseAllTouch() {
|
||||
for (int index = 0; index < 16; ++index) {
|
||||
SetButton(identifier, index, false);
|
||||
SetAxis(identifier, index * 2, 0.0f);
|
||||
SetAxis(identifier, index * 2 + 1, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace InputCommon
|
44
src/input_common/drivers/touch_screen.h
Executable file
44
src/input_common/drivers/touch_screen.h
Executable file
|
@ -0,0 +1,44 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "input_common/input_engine.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
/**
|
||||
* A button device factory representing a keyboard. It receives keyboard events and forward them
|
||||
* to all button devices it created.
|
||||
*/
|
||||
class TouchScreen final : public InputCommon::InputEngine {
|
||||
public:
|
||||
explicit TouchScreen(const std::string input_engine_);
|
||||
|
||||
/**
|
||||
* Signals that mouse has moved.
|
||||
* @param x the x-coordinate of the cursor
|
||||
* @param y the y-coordinate of the cursor
|
||||
* @param center_x the x-coordinate of the middle of the screen
|
||||
* @param center_y the y-coordinate of the middle of the screen
|
||||
*/
|
||||
void TouchMoved(float x, float y, std::size_t finger);
|
||||
|
||||
/**
|
||||
* Sets the status of all buttons bound with the key to pressed
|
||||
* @param key_code the code of the key to press
|
||||
*/
|
||||
void TouchPressed(float x, float y, std::size_t finger);
|
||||
|
||||
/**
|
||||
* Sets the status of all buttons bound with the key to released
|
||||
* @param key_code the code of the key to release
|
||||
*/
|
||||
void TouchReleased(std::size_t finger);
|
||||
|
||||
/// Resets all inputs to their initial value
|
||||
void ReleaseAllTouch();
|
||||
};
|
||||
|
||||
} // namespace InputCommon
|
401
src/input_common/drivers/udp_client.cpp
Executable file
401
src/input_common/drivers/udp_client.cpp
Executable file
|
@ -0,0 +1,401 @@
|
|||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <random>
|
||||
#include <boost/asio.hpp>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/param_package.h"
|
||||
#include "common/settings.h"
|
||||
#include "input_common/drivers/udp_client.h"
|
||||
#include "input_common/helpers/udp_protocol.h"
|
||||
|
||||
using boost::asio::ip::udp;
|
||||
|
||||
namespace InputCommon::CemuhookUDP {
|
||||
|
||||
struct SocketCallback {
|
||||
std::function<void(Response::Version)> version;
|
||||
std::function<void(Response::PortInfo)> port_info;
|
||||
std::function<void(Response::PadData)> pad_data;
|
||||
};
|
||||
|
||||
class Socket {
|
||||
public:
|
||||
using clock = std::chrono::system_clock;
|
||||
|
||||
explicit Socket(const std::string& host, u16 port, SocketCallback callback_)
|
||||
: callback(std::move(callback_)), timer(io_service),
|
||||
socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(GenerateRandomClientId()) {
|
||||
boost::system::error_code ec{};
|
||||
auto ipv4 = boost::asio::ip::make_address_v4(host, ec);
|
||||
if (ec.value() != boost::system::errc::success) {
|
||||
LOG_ERROR(Input, "Invalid IPv4 address \"{}\" provided to socket", host);
|
||||
ipv4 = boost::asio::ip::address_v4{};
|
||||
}
|
||||
|
||||
send_endpoint = {udp::endpoint(ipv4, port)};
|
||||
}
|
||||
|
||||
void Stop() {
|
||||
io_service.stop();
|
||||
}
|
||||
|
||||
void Loop() {
|
||||
io_service.run();
|
||||
}
|
||||
|
||||
void StartSend(const clock::time_point& from) {
|
||||
timer.expires_at(from + std::chrono::seconds(3));
|
||||
timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); });
|
||||
}
|
||||
|
||||
void StartReceive() {
|
||||
socket.async_receive_from(
|
||||
boost::asio::buffer(receive_buffer), receive_endpoint,
|
||||
[this](const boost::system::error_code& error, std::size_t bytes_transferred) {
|
||||
HandleReceive(error, bytes_transferred);
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
u32 GenerateRandomClientId() const {
|
||||
std::random_device device;
|
||||
return device();
|
||||
}
|
||||
|
||||
void HandleReceive(const boost::system::error_code&, std::size_t bytes_transferred) {
|
||||
if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) {
|
||||
switch (*type) {
|
||||
case Type::Version: {
|
||||
Response::Version version;
|
||||
std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version));
|
||||
callback.version(std::move(version));
|
||||
break;
|
||||
}
|
||||
case Type::PortInfo: {
|
||||
Response::PortInfo port_info;
|
||||
std::memcpy(&port_info, &receive_buffer[sizeof(Header)],
|
||||
sizeof(Response::PortInfo));
|
||||
callback.port_info(std::move(port_info));
|
||||
break;
|
||||
}
|
||||
case Type::PadData: {
|
||||
Response::PadData pad_data;
|
||||
std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData));
|
||||
callback.pad_data(std::move(pad_data));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
StartReceive();
|
||||
}
|
||||
|
||||
void HandleSend(const boost::system::error_code&) {
|
||||
boost::system::error_code _ignored{};
|
||||
// Send a request for getting port info for the pad
|
||||
const Request::PortInfo port_info{4, {0, 1, 2, 3}};
|
||||
const auto port_message = Request::Create(port_info, client_id);
|
||||
std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE);
|
||||
socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored);
|
||||
|
||||
// Send a request for getting pad data for the pad
|
||||
const Request::PadData pad_data{
|
||||
Request::PadData::Flags::AllPorts,
|
||||
0,
|
||||
EMPTY_MAC_ADDRESS,
|
||||
};
|
||||
const auto pad_message = Request::Create(pad_data, client_id);
|
||||
std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE);
|
||||
socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored);
|
||||
StartSend(timer.expiry());
|
||||
}
|
||||
|
||||
SocketCallback callback;
|
||||
boost::asio::io_service io_service;
|
||||
boost::asio::basic_waitable_timer<clock> timer;
|
||||
udp::socket socket;
|
||||
|
||||
const u32 client_id;
|
||||
|
||||
static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>);
|
||||
static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>);
|
||||
std::array<u8, PORT_INFO_SIZE> send_buffer1;
|
||||
std::array<u8, PAD_DATA_SIZE> send_buffer2;
|
||||
udp::endpoint send_endpoint;
|
||||
|
||||
std::array<u8, MAX_PACKET_SIZE> receive_buffer;
|
||||
udp::endpoint receive_endpoint;
|
||||
};
|
||||
|
||||
static void SocketLoop(Socket* socket) {
|
||||
socket->StartReceive();
|
||||
socket->StartSend(Socket::clock::now());
|
||||
socket->Loop();
|
||||
}
|
||||
|
||||
UDPClient::UDPClient(const std::string& input_engine_) : InputEngine(input_engine_) {
|
||||
LOG_INFO(Input, "Udp Initialization started");
|
||||
ReloadSockets();
|
||||
}
|
||||
|
||||
UDPClient::~UDPClient() {
|
||||
Reset();
|
||||
}
|
||||
|
||||
UDPClient::ClientConnection::ClientConnection() = default;
|
||||
|
||||
UDPClient::ClientConnection::~ClientConnection() = default;
|
||||
|
||||
void UDPClient::ReloadSockets() {
|
||||
Reset();
|
||||
|
||||
std::stringstream servers_ss(Settings::values.udp_input_servers.GetValue());
|
||||
std::string server_token;
|
||||
std::size_t client = 0;
|
||||
while (std::getline(servers_ss, server_token, ',')) {
|
||||
if (client == MAX_UDP_CLIENTS) {
|
||||
break;
|
||||
}
|
||||
std::stringstream server_ss(server_token);
|
||||
std::string token;
|
||||
std::getline(server_ss, token, ':');
|
||||
std::string udp_input_address = token;
|
||||
std::getline(server_ss, token, ':');
|
||||
char* temp;
|
||||
const u16 udp_input_port = static_cast<u16>(std::strtol(token.c_str(), &temp, 0));
|
||||
if (*temp != '\0') {
|
||||
LOG_ERROR(Input, "Port number is not valid {}", token);
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::size_t client_number = GetClientNumber(udp_input_address, udp_input_port);
|
||||
if (client_number != MAX_UDP_CLIENTS) {
|
||||
LOG_ERROR(Input, "Duplicated UDP servers found");
|
||||
continue;
|
||||
}
|
||||
StartCommunication(client++, udp_input_address, udp_input_port);
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t UDPClient::GetClientNumber(std::string_view host, u16 port) const {
|
||||
for (std::size_t client = 0; client < clients.size(); client++) {
|
||||
if (clients[client].active == -1) {
|
||||
continue;
|
||||
}
|
||||
if (clients[client].host == host && clients[client].port == port) {
|
||||
return client;
|
||||
}
|
||||
}
|
||||
return MAX_UDP_CLIENTS;
|
||||
}
|
||||
|
||||
void UDPClient::OnVersion([[maybe_unused]] Response::Version data) {
|
||||
LOG_TRACE(Input, "Version packet received: {}", data.version);
|
||||
}
|
||||
|
||||
void UDPClient::OnPortInfo([[maybe_unused]] Response::PortInfo data) {
|
||||
LOG_TRACE(Input, "PortInfo packet received: {}", data.model);
|
||||
}
|
||||
|
||||
void UDPClient::OnPadData(Response::PadData data, std::size_t client) {
|
||||
const std::size_t pad_index = (client * PADS_PER_CLIENT) + data.info.id;
|
||||
|
||||
if (pad_index >= pads.size()) {
|
||||
LOG_ERROR(Input, "Invalid pad id {}", data.info.id);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_TRACE(Input, "PadData packet received");
|
||||
if (data.packet_counter == pads[pad_index].packet_sequence) {
|
||||
LOG_WARNING(
|
||||
Input,
|
||||
"PadData packet dropped because its stale info. Current count: {} Packet count: {}",
|
||||
pads[pad_index].packet_sequence, data.packet_counter);
|
||||
pads[pad_index].connected = false;
|
||||
return;
|
||||
}
|
||||
|
||||
clients[client].active = 1;
|
||||
pads[pad_index].connected = true;
|
||||
pads[pad_index].packet_sequence = data.packet_counter;
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto time_difference = static_cast<u64>(
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(now - pads[pad_index].last_update)
|
||||
.count());
|
||||
pads[pad_index].last_update = now;
|
||||
|
||||
// Gyroscope values are not it the correct scale from better joy.
|
||||
// Dividing by 312 allows us to make one full turn = 1 turn
|
||||
// This must be a configurable valued called sensitivity
|
||||
const float gyro_scale = 1.0f / 312.0f;
|
||||
|
||||
const BasicMotion motion{
|
||||
.gyro_x = data.gyro.pitch * gyro_scale,
|
||||
.gyro_y = data.gyro.roll * gyro_scale,
|
||||
.gyro_z = -data.gyro.yaw * gyro_scale,
|
||||
.accel_x = data.accel.x,
|
||||
.accel_y = -data.accel.z,
|
||||
.accel_z = data.accel.y,
|
||||
.delta_timestamp = time_difference,
|
||||
};
|
||||
const PadIdentifier identifier = GetPadIdentifier(pad_index);
|
||||
SetMotion(identifier, 0, motion);
|
||||
|
||||
for (std::size_t id = 0; id < data.touch.size(); ++id) {
|
||||
const auto touch_pad = data.touch[id];
|
||||
const int touch_id = static_cast<int>(client * 2 + id);
|
||||
|
||||
// TODO: Use custom calibration per device
|
||||
const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue());
|
||||
const u16 min_x = static_cast<u16>(touch_param.Get("min_x", 100));
|
||||
const u16 min_y = static_cast<u16>(touch_param.Get("min_y", 50));
|
||||
const u16 max_x = static_cast<u16>(touch_param.Get("max_x", 1800));
|
||||
const u16 max_y = static_cast<u16>(touch_param.Get("max_y", 850));
|
||||
|
||||
const f32 x =
|
||||
static_cast<f32>(std::clamp(static_cast<u16>(touch_pad.x), min_x, max_x) - min_x) /
|
||||
static_cast<f32>(max_x - min_x);
|
||||
const f32 y =
|
||||
static_cast<f32>(std::clamp(static_cast<u16>(touch_pad.y), min_y, max_y) - min_y) /
|
||||
static_cast<f32>(max_y - min_y);
|
||||
|
||||
if (touch_pad.is_active) {
|
||||
SetAxis(identifier, touch_id * 2, x);
|
||||
SetAxis(identifier, touch_id * 2 + 1, y);
|
||||
SetButton(identifier, touch_id, true);
|
||||
continue;
|
||||
}
|
||||
SetAxis(identifier, touch_id * 2, 0);
|
||||
SetAxis(identifier, touch_id * 2 + 1, 0);
|
||||
SetButton(identifier, touch_id, false);
|
||||
}
|
||||
}
|
||||
|
||||
void UDPClient::StartCommunication(std::size_t client, const std::string& host, u16 port) {
|
||||
SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
|
||||
[this](Response::PortInfo info) { OnPortInfo(info); },
|
||||
[this, client](Response::PadData data) { OnPadData(data, client); }};
|
||||
LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
|
||||
clients[client].uuid = GetHostUUID(host);
|
||||
clients[client].host = host;
|
||||
clients[client].port = port;
|
||||
clients[client].active = 0;
|
||||
clients[client].socket = std::make_unique<Socket>(host, port, callback);
|
||||
clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()};
|
||||
for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) {
|
||||
const PadIdentifier identifier = GetPadIdentifier(client * PADS_PER_CLIENT + index);
|
||||
PreSetController(identifier);
|
||||
}
|
||||
}
|
||||
|
||||
const PadIdentifier UDPClient::GetPadIdentifier(std::size_t pad_index) const {
|
||||
const std::size_t client = pad_index / PADS_PER_CLIENT;
|
||||
return {
|
||||
.guid = clients[client].uuid,
|
||||
.port = static_cast<std::size_t>(clients[client].port),
|
||||
.pad = pad_index,
|
||||
};
|
||||
}
|
||||
|
||||
const Common::UUID UDPClient::GetHostUUID(const std::string host) const {
|
||||
const auto ip = boost::asio::ip::address_v4::from_string(host);
|
||||
const auto hex_host = fmt::format("{:06x}", ip.to_ulong());
|
||||
return Common::UUID{hex_host};
|
||||
}
|
||||
|
||||
void UDPClient::Reset() {
|
||||
for (auto& client : clients) {
|
||||
if (client.thread.joinable()) {
|
||||
client.active = -1;
|
||||
client.socket->Stop();
|
||||
client.thread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TestCommunication(const std::string& host, u16 port,
|
||||
const std::function<void()>& success_callback,
|
||||
const std::function<void()>& failure_callback) {
|
||||
std::thread([=] {
|
||||
Common::Event success_event;
|
||||
SocketCallback callback{
|
||||
.version = [](Response::Version) {},
|
||||
.port_info = [](Response::PortInfo) {},
|
||||
.pad_data = [&](Response::PadData) { success_event.Set(); },
|
||||
};
|
||||
Socket socket{host, port, std::move(callback)};
|
||||
std::thread worker_thread{SocketLoop, &socket};
|
||||
const bool result =
|
||||
success_event.WaitUntil(std::chrono::steady_clock::now() + std::chrono::seconds(10));
|
||||
socket.Stop();
|
||||
worker_thread.join();
|
||||
if (result) {
|
||||
success_callback();
|
||||
} else {
|
||||
failure_callback();
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
|
||||
CalibrationConfigurationJob::CalibrationConfigurationJob(
|
||||
const std::string& host, u16 port, std::function<void(Status)> status_callback,
|
||||
std::function<void(u16, u16, u16, u16)> data_callback) {
|
||||
|
||||
std::thread([=, this] {
|
||||
Status current_status{Status::Initialized};
|
||||
SocketCallback callback{
|
||||
[](Response::Version) {}, [](Response::PortInfo) {},
|
||||
[&](Response::PadData data) {
|
||||
static constexpr u16 CALIBRATION_THRESHOLD = 100;
|
||||
static constexpr u16 MAX_VALUE = UINT16_MAX;
|
||||
|
||||
if (current_status == Status::Initialized) {
|
||||
// Receiving data means the communication is ready now
|
||||
current_status = Status::Ready;
|
||||
status_callback(current_status);
|
||||
}
|
||||
const auto& touchpad_0 = data.touch[0];
|
||||
if (touchpad_0.is_active == 0) {
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG(Input, "Current touch: {} {}", touchpad_0.x, touchpad_0.y);
|
||||
const u16 min_x = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.x));
|
||||
const u16 min_y = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.y));
|
||||
if (current_status == Status::Ready) {
|
||||
// First touch - min data (min_x/min_y)
|
||||
current_status = Status::Stage1Completed;
|
||||
status_callback(current_status);
|
||||
}
|
||||
if (touchpad_0.x - min_x > CALIBRATION_THRESHOLD &&
|
||||
touchpad_0.y - min_y > CALIBRATION_THRESHOLD) {
|
||||
// Set the current position as max value and finishes configuration
|
||||
const u16 max_x = touchpad_0.x;
|
||||
const u16 max_y = touchpad_0.y;
|
||||
current_status = Status::Completed;
|
||||
data_callback(min_x, min_y, max_x, max_y);
|
||||
status_callback(current_status);
|
||||
|
||||
complete_event.Set();
|
||||
}
|
||||
}};
|
||||
Socket socket{host, port, std::move(callback)};
|
||||
std::thread worker_thread{SocketLoop, &socket};
|
||||
complete_event.Wait();
|
||||
socket.Stop();
|
||||
worker_thread.join();
|
||||
}).detach();
|
||||
}
|
||||
|
||||
CalibrationConfigurationJob::~CalibrationConfigurationJob() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
void CalibrationConfigurationJob::Stop() {
|
||||
complete_event.Set();
|
||||
}
|
||||
|
||||
} // namespace InputCommon::CemuhookUDP
|
129
src/input_common/drivers/udp_client.h
Executable file
129
src/input_common/drivers/udp_client.h
Executable file
|
@ -0,0 +1,129 @@
|
|||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/thread.h"
|
||||
#include "input_common/input_engine.h"
|
||||
|
||||
namespace InputCommon::CemuhookUDP {
|
||||
|
||||
class Socket;
|
||||
|
||||
namespace Response {
|
||||
struct PadData;
|
||||
struct PortInfo;
|
||||
struct TouchPad;
|
||||
struct Version;
|
||||
} // namespace Response
|
||||
|
||||
enum class PadTouch {
|
||||
Click,
|
||||
Undefined,
|
||||
};
|
||||
|
||||
struct UDPPadStatus {
|
||||
std::string host{"127.0.0.1"};
|
||||
u16 port{26760};
|
||||
std::size_t pad_index{};
|
||||
};
|
||||
|
||||
struct DeviceStatus {
|
||||
std::mutex update_mutex;
|
||||
|
||||
// calibration data for scaling the device's touch area to 3ds
|
||||
struct CalibrationData {
|
||||
u16 min_x{};
|
||||
u16 min_y{};
|
||||
u16 max_x{};
|
||||
u16 max_y{};
|
||||
};
|
||||
std::optional<CalibrationData> touch_calibration;
|
||||
};
|
||||
|
||||
/**
|
||||
* A button device factory representing a keyboard. It receives keyboard events and forward them
|
||||
* to all button devices it created.
|
||||
*/
|
||||
class UDPClient final : public InputCommon::InputEngine {
|
||||
public:
|
||||
explicit UDPClient(const std::string& input_engine_);
|
||||
~UDPClient();
|
||||
|
||||
void ReloadSockets();
|
||||
|
||||
private:
|
||||
struct PadData {
|
||||
std::size_t pad_index{};
|
||||
bool connected{};
|
||||
DeviceStatus status;
|
||||
u64 packet_sequence{};
|
||||
|
||||
std::chrono::time_point<std::chrono::steady_clock> last_update;
|
||||
};
|
||||
|
||||
struct ClientConnection {
|
||||
ClientConnection();
|
||||
~ClientConnection();
|
||||
Common::UUID uuid{"7F000001"};
|
||||
std::string host{"127.0.0.1"};
|
||||
u16 port{26760};
|
||||
s8 active{-1};
|
||||
std::unique_ptr<Socket> socket;
|
||||
std::thread thread;
|
||||
};
|
||||
|
||||
// For shutting down, clear all data, join all threads, release usb
|
||||
void Reset();
|
||||
|
||||
// Translates configuration to client number
|
||||
std::size_t GetClientNumber(std::string_view host, u16 port) const;
|
||||
|
||||
void OnVersion(Response::Version);
|
||||
void OnPortInfo(Response::PortInfo);
|
||||
void OnPadData(Response::PadData, std::size_t client);
|
||||
void StartCommunication(std::size_t client, const std::string& host, u16 port);
|
||||
const PadIdentifier GetPadIdentifier(std::size_t pad_index) const;
|
||||
const Common::UUID GetHostUUID(const std::string host) const;
|
||||
|
||||
// Allocate clients for 8 udp servers
|
||||
static constexpr std::size_t MAX_UDP_CLIENTS = 8;
|
||||
static constexpr std::size_t PADS_PER_CLIENT = 4;
|
||||
std::array<PadData, MAX_UDP_CLIENTS * PADS_PER_CLIENT> pads{};
|
||||
std::array<ClientConnection, MAX_UDP_CLIENTS> clients{};
|
||||
};
|
||||
|
||||
/// An async job allowing configuration of the touchpad calibration.
|
||||
class CalibrationConfigurationJob {
|
||||
public:
|
||||
enum class Status {
|
||||
Initialized,
|
||||
Ready,
|
||||
Stage1Completed,
|
||||
Completed,
|
||||
};
|
||||
/**
|
||||
* Constructs and starts the job with the specified parameter.
|
||||
*
|
||||
* @param status_callback Callback for job status updates
|
||||
* @param data_callback Called when calibration data is ready
|
||||
*/
|
||||
explicit CalibrationConfigurationJob(const std::string& host, u16 port,
|
||||
std::function<void(Status)> status_callback,
|
||||
std::function<void(u16, u16, u16, u16)> data_callback);
|
||||
~CalibrationConfigurationJob();
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
Common::Event complete_event;
|
||||
};
|
||||
|
||||
void TestCommunication(const std::string& host, u16 port,
|
||||
const std::function<void()>& success_callback,
|
||||
const std::function<void()>& failure_callback);
|
||||
|
||||
} // namespace InputCommon::CemuhookUDP
|
304
src/input_common/helpers/stick_from_buttons.cpp
Executable file
304
src/input_common/helpers/stick_from_buttons.cpp
Executable file
|
@ -0,0 +1,304 @@
|
|||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include "common/math_util.h"
|
||||
#include "common/settings.h"
|
||||
#include "input_common/helpers/stick_from_buttons.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
class Stick final : public Common::Input::InputDevice {
|
||||
public:
|
||||
using Button = std::unique_ptr<Common::Input::InputDevice>;
|
||||
|
||||
Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_,
|
||||
float modifier_scale_, float modifier_angle_)
|
||||
: up(std::move(up_)), down(std::move(down_)), left(std::move(left_)),
|
||||
right(std::move(right_)), modifier(std::move(modifier_)), modifier_scale(modifier_scale_),
|
||||
modifier_angle(modifier_angle_) {
|
||||
Common::Input::InputCallback button_up_callback{
|
||||
[this](Common::Input::CallbackStatus callback_) { UpdateUpButtonStatus(callback_); }};
|
||||
Common::Input::InputCallback button_down_callback{
|
||||
[this](Common::Input::CallbackStatus callback_) { UpdateDownButtonStatus(callback_); }};
|
||||
Common::Input::InputCallback button_left_callback{
|
||||
[this](Common::Input::CallbackStatus callback_) { UpdateLeftButtonStatus(callback_); }};
|
||||
Common::Input::InputCallback button_right_callback{
|
||||
[this](Common::Input::CallbackStatus callback_) {
|
||||
UpdateRightButtonStatus(callback_);
|
||||
}};
|
||||
Common::Input::InputCallback button_modifier_callback{
|
||||
[this](Common::Input::CallbackStatus callback_) { UpdateModButtonStatus(callback_); }};
|
||||
up->SetCallback(button_up_callback);
|
||||
down->SetCallback(button_down_callback);
|
||||
left->SetCallback(button_left_callback);
|
||||
right->SetCallback(button_right_callback);
|
||||
modifier->SetCallback(button_modifier_callback);
|
||||
last_x_axis_value = 0.0f;
|
||||
last_y_axis_value = 0.0f;
|
||||
}
|
||||
|
||||
bool IsAngleGreater(float old_angle, float new_angle) const {
|
||||
constexpr float TAU = Common::PI * 2.0f;
|
||||
// Use wider angle to ease the transition.
|
||||
constexpr float aperture = TAU * 0.15f;
|
||||
const float top_limit = new_angle + aperture;
|
||||
return (old_angle > new_angle && old_angle <= top_limit) ||
|
||||
(old_angle + TAU > new_angle && old_angle + TAU <= top_limit);
|
||||
}
|
||||
|
||||
bool IsAngleSmaller(float old_angle, float new_angle) const {
|
||||
constexpr float TAU = Common::PI * 2.0f;
|
||||
// Use wider angle to ease the transition.
|
||||
constexpr float aperture = TAU * 0.15f;
|
||||
const float bottom_limit = new_angle - aperture;
|
||||
return (old_angle >= bottom_limit && old_angle < new_angle) ||
|
||||
(old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle);
|
||||
}
|
||||
|
||||
float GetAngle(std::chrono::time_point<std::chrono::steady_clock> now) const {
|
||||
constexpr float TAU = Common::PI * 2.0f;
|
||||
float new_angle = angle;
|
||||
|
||||
auto time_difference = static_cast<float>(
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count());
|
||||
time_difference /= 1000.0f * 1000.0f;
|
||||
if (time_difference > 0.5f) {
|
||||
time_difference = 0.5f;
|
||||
}
|
||||
|
||||
if (IsAngleGreater(new_angle, goal_angle)) {
|
||||
new_angle -= modifier_angle * time_difference;
|
||||
if (new_angle < 0) {
|
||||
new_angle += TAU;
|
||||
}
|
||||
if (!IsAngleGreater(new_angle, goal_angle)) {
|
||||
return goal_angle;
|
||||
}
|
||||
} else if (IsAngleSmaller(new_angle, goal_angle)) {
|
||||
new_angle += modifier_angle * time_difference;
|
||||
if (new_angle >= TAU) {
|
||||
new_angle -= TAU;
|
||||
}
|
||||
if (!IsAngleSmaller(new_angle, goal_angle)) {
|
||||
return goal_angle;
|
||||
}
|
||||
} else {
|
||||
return goal_angle;
|
||||
}
|
||||
return new_angle;
|
||||
}
|
||||
|
||||
void SetGoalAngle(bool r, bool l, bool u, bool d) {
|
||||
// Move to the right
|
||||
if (r && !u && !d) {
|
||||
goal_angle = 0.0f;
|
||||
}
|
||||
|
||||
// Move to the upper right
|
||||
if (r && u && !d) {
|
||||
goal_angle = Common::PI * 0.25f;
|
||||
}
|
||||
|
||||
// Move up
|
||||
if (u && !l && !r) {
|
||||
goal_angle = Common::PI * 0.5f;
|
||||
}
|
||||
|
||||
// Move to the upper left
|
||||
if (l && u && !d) {
|
||||
goal_angle = Common::PI * 0.75f;
|
||||
}
|
||||
|
||||
// Move to the left
|
||||
if (l && !u && !d) {
|
||||
goal_angle = Common::PI;
|
||||
}
|
||||
|
||||
// Move to the bottom left
|
||||
if (l && !u && d) {
|
||||
goal_angle = Common::PI * 1.25f;
|
||||
}
|
||||
|
||||
// Move down
|
||||
if (d && !l && !r) {
|
||||
goal_angle = Common::PI * 1.5f;
|
||||
}
|
||||
|
||||
// Move to the bottom right
|
||||
if (r && !u && d) {
|
||||
goal_angle = Common::PI * 1.75f;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateUpButtonStatus(Common::Input::CallbackStatus button_callback) {
|
||||
up_status = button_callback.button_status.value;
|
||||
UpdateStatus();
|
||||
}
|
||||
|
||||
void UpdateDownButtonStatus(Common::Input::CallbackStatus button_callback) {
|
||||
down_status = button_callback.button_status.value;
|
||||
UpdateStatus();
|
||||
}
|
||||
|
||||
void UpdateLeftButtonStatus(Common::Input::CallbackStatus button_callback) {
|
||||
left_status = button_callback.button_status.value;
|
||||
UpdateStatus();
|
||||
}
|
||||
|
||||
void UpdateRightButtonStatus(Common::Input::CallbackStatus button_callback) {
|
||||
right_status = button_callback.button_status.value;
|
||||
UpdateStatus();
|
||||
}
|
||||
|
||||
void UpdateModButtonStatus(Common::Input::CallbackStatus button_callback) {
|
||||
modifier_status = button_callback.button_status.value;
|
||||
UpdateStatus();
|
||||
}
|
||||
|
||||
void UpdateStatus() {
|
||||
const float coef = modifier_status ? modifier_scale : 1.0f;
|
||||
|
||||
bool r = right_status;
|
||||
bool l = left_status;
|
||||
bool u = up_status;
|
||||
bool d = down_status;
|
||||
|
||||
// Eliminate contradictory movements
|
||||
if (r && l) {
|
||||
r = false;
|
||||
l = false;
|
||||
}
|
||||
if (u && d) {
|
||||
u = false;
|
||||
d = false;
|
||||
}
|
||||
|
||||
// Move if a key is pressed
|
||||
if (r || l || u || d) {
|
||||
amplitude = coef;
|
||||
} else {
|
||||
amplitude = 0;
|
||||
}
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto time_difference = static_cast<u64>(
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update).count());
|
||||
|
||||
if (time_difference < 10) {
|
||||
// Disable analog mode if inputs are too fast
|
||||
SetGoalAngle(r, l, u, d);
|
||||
angle = goal_angle;
|
||||
} else {
|
||||
angle = GetAngle(now);
|
||||
SetGoalAngle(r, l, u, d);
|
||||
}
|
||||
|
||||
last_update = now;
|
||||
Common::Input::CallbackStatus status{
|
||||
.type = Common::Input::InputType::Stick,
|
||||
.stick_status = GetStatus(),
|
||||
};
|
||||
last_x_axis_value = status.stick_status.x.raw_value;
|
||||
last_y_axis_value = status.stick_status.y.raw_value;
|
||||
TriggerOnChange(status);
|
||||
}
|
||||
|
||||
void ForceUpdate() override {
|
||||
up->ForceUpdate();
|
||||
down->ForceUpdate();
|
||||
left->ForceUpdate();
|
||||
right->ForceUpdate();
|
||||
modifier->ForceUpdate();
|
||||
}
|
||||
|
||||
void SoftUpdate() override {
|
||||
Common::Input::CallbackStatus status{
|
||||
.type = Common::Input::InputType::Stick,
|
||||
.stick_status = GetStatus(),
|
||||
};
|
||||
if (last_x_axis_value == status.stick_status.x.raw_value &&
|
||||
last_y_axis_value == status.stick_status.y.raw_value) {
|
||||
return;
|
||||
}
|
||||
last_x_axis_value = status.stick_status.x.raw_value;
|
||||
last_y_axis_value = status.stick_status.y.raw_value;
|
||||
TriggerOnChange(status);
|
||||
}
|
||||
|
||||
Common::Input::StickStatus GetStatus() const {
|
||||
Common::Input::StickStatus status{};
|
||||
status.x.properties = properties;
|
||||
status.y.properties = properties;
|
||||
if (Settings::values.emulate_analog_keyboard) {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
float angle_ = GetAngle(now);
|
||||
status.x.raw_value = std::cos(angle_) * amplitude;
|
||||
status.y.raw_value = std::sin(angle_) * amplitude;
|
||||
return status;
|
||||
}
|
||||
constexpr float SQRT_HALF = 0.707106781f;
|
||||
int x = 0, y = 0;
|
||||
if (right_status) {
|
||||
++x;
|
||||
}
|
||||
if (left_status) {
|
||||
--x;
|
||||
}
|
||||
if (up_status) {
|
||||
++y;
|
||||
}
|
||||
if (down_status) {
|
||||
--y;
|
||||
}
|
||||
const float coef = modifier_status ? modifier_scale : 1.0f;
|
||||
status.x.raw_value = static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF);
|
||||
status.y.raw_value = static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF);
|
||||
return status;
|
||||
}
|
||||
|
||||
private:
|
||||
Button up;
|
||||
Button down;
|
||||
Button left;
|
||||
Button right;
|
||||
Button modifier;
|
||||
float modifier_scale;
|
||||
float modifier_angle;
|
||||
float angle{};
|
||||
float goal_angle{};
|
||||
float amplitude{};
|
||||
bool up_status;
|
||||
bool down_status;
|
||||
bool left_status;
|
||||
bool right_status;
|
||||
bool modifier_status;
|
||||
float last_x_axis_value;
|
||||
float last_y_axis_value;
|
||||
const Common::Input::AnalogProperties properties{0.0f, 1.0f, 0.5f, 0.0f, false};
|
||||
std::chrono::time_point<std::chrono::steady_clock> last_update;
|
||||
};
|
||||
|
||||
std::unique_ptr<Common::Input::InputDevice> StickFromButton::Create(
|
||||
const Common::ParamPackage& params) {
|
||||
const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
|
||||
auto up = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
|
||||
params.Get("up", null_engine));
|
||||
auto down = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
|
||||
params.Get("down", null_engine));
|
||||
auto left = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
|
||||
params.Get("left", null_engine));
|
||||
auto right = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
|
||||
params.Get("right", null_engine));
|
||||
auto modifier = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
|
||||
params.Get("modifier", null_engine));
|
||||
auto modifier_scale = params.Get("modifier_scale", 0.5f);
|
||||
auto modifier_angle = params.Get("modifier_angle", 5.5f);
|
||||
return std::make_unique<Stick>(std::move(up), std::move(down), std::move(left),
|
||||
std::move(right), std::move(modifier), modifier_scale,
|
||||
modifier_angle);
|
||||
}
|
||||
|
||||
} // namespace InputCommon
|
30
src/input_common/helpers/stick_from_buttons.h
Executable file
30
src/input_common/helpers/stick_from_buttons.h
Executable file
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/input.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
/**
|
||||
* An analog device factory that takes direction button devices and combines them into a analog
|
||||
* device.
|
||||
*/
|
||||
class StickFromButton final : public Common::Input::Factory<Common::Input::InputDevice> {
|
||||
public:
|
||||
/**
|
||||
* Creates an analog device from direction button devices
|
||||
* @param params contains parameters for creating the device:
|
||||
* - "up": a serialized ParamPackage for creating a button device for up direction
|
||||
* - "down": a serialized ParamPackage for creating a button device for down direction
|
||||
* - "left": a serialized ParamPackage for creating a button device for left direction
|
||||
* - "right": a serialized ParamPackage for creating a button device for right direction
|
||||
* - "modifier": a serialized ParamPackage for creating a button device as the modifier
|
||||
* - "modifier_scale": a float for the multiplier the modifier gives to the position
|
||||
*/
|
||||
std::unique_ptr<Common::Input::InputDevice> Create(const Common::ParamPackage& params) override;
|
||||
};
|
||||
|
||||
} // namespace InputCommon
|
81
src/input_common/helpers/touch_from_buttons.cpp
Executable file
81
src/input_common/helpers/touch_from_buttons.cpp
Executable file
|
@ -0,0 +1,81 @@
|
|||
// Copyright 2020 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include "common/settings.h"
|
||||
#include "core/frontend/framebuffer_layout.h"
|
||||
#include "input_common/helpers/touch_from_buttons.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
class TouchFromButtonDevice final : public Common::Input::InputDevice {
|
||||
public:
|
||||
using Button = std::unique_ptr<Common::Input::InputDevice>;
|
||||
TouchFromButtonDevice(Button button_, int touch_id_, float x_, float y_)
|
||||
: button(std::move(button_)), touch_id(touch_id_), x(x_), y(y_) {
|
||||
Common::Input::InputCallback button_up_callback{
|
||||
[this](Common::Input::CallbackStatus callback_) { UpdateButtonStatus(callback_); }};
|
||||
last_button_value = false;
|
||||
button->SetCallback(button_up_callback);
|
||||
button->ForceUpdate();
|
||||
}
|
||||
|
||||
void ForceUpdate() override {
|
||||
button->ForceUpdate();
|
||||
}
|
||||
|
||||
Common::Input::TouchStatus GetStatus(bool pressed) const {
|
||||
const Common::Input::ButtonStatus button_status{
|
||||
.value = pressed,
|
||||
};
|
||||
Common::Input::TouchStatus status{
|
||||
.pressed = button_status,
|
||||
.x = {},
|
||||
.y = {},
|
||||
.id = touch_id,
|
||||
};
|
||||
status.x.properties = properties;
|
||||
status.y.properties = properties;
|
||||
|
||||
if (!pressed) {
|
||||
return status;
|
||||
}
|
||||
|
||||
status.x.raw_value = x;
|
||||
status.y.raw_value = y;
|
||||
return status;
|
||||
}
|
||||
|
||||
void UpdateButtonStatus(Common::Input::CallbackStatus button_callback) {
|
||||
const Common::Input::CallbackStatus status{
|
||||
.type = Common::Input::InputType::Touch,
|
||||
.touch_status = GetStatus(button_callback.button_status.value),
|
||||
};
|
||||
if (last_button_value != button_callback.button_status.value) {
|
||||
last_button_value = button_callback.button_status.value;
|
||||
TriggerOnChange(status);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Button button;
|
||||
bool last_button_value;
|
||||
const int touch_id;
|
||||
const float x;
|
||||
const float y;
|
||||
const Common::Input::AnalogProperties properties{0.0f, 1.0f, 0.5f, 0.0f, false};
|
||||
};
|
||||
|
||||
std::unique_ptr<Common::Input::InputDevice> TouchFromButton::Create(
|
||||
const Common::ParamPackage& params) {
|
||||
const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
|
||||
auto button = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
|
||||
params.Get("button", null_engine));
|
||||
const auto touch_id = params.Get("touch_id", 0);
|
||||
const float x = params.Get("x", 0.0f) / 1280.0f;
|
||||
const float y = params.Get("y", 0.0f) / 720.0f;
|
||||
return std::make_unique<TouchFromButtonDevice>(std::move(button), touch_id, x, y);
|
||||
}
|
||||
|
||||
} // namespace InputCommon
|
22
src/input_common/helpers/touch_from_buttons.h
Executable file
22
src/input_common/helpers/touch_from_buttons.h
Executable file
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2020 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/input.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
/**
|
||||
* A touch device factory that takes a list of button devices and combines them into a touch device.
|
||||
*/
|
||||
class TouchFromButton final : public Common::Input::Factory<Common::Input::InputDevice> {
|
||||
public:
|
||||
/**
|
||||
* Creates a touch device from a list of button devices
|
||||
*/
|
||||
std::unique_ptr<Common::Input::InputDevice> Create(const Common::ParamPackage& params) override;
|
||||
};
|
||||
|
||||
} // namespace InputCommon
|
78
src/input_common/helpers/udp_protocol.cpp
Executable file
78
src/input_common/helpers/udp_protocol.cpp
Executable file
|
@ -0,0 +1,78 @@
|
|||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include "common/logging/log.h"
|
||||
#include "input_common/helpers/udp_protocol.h"
|
||||
|
||||
namespace InputCommon::CemuhookUDP {
|
||||
|
||||
static constexpr std::size_t GetSizeOfResponseType(Type t) {
|
||||
switch (t) {
|
||||
case Type::Version:
|
||||
return sizeof(Response::Version);
|
||||
case Type::PortInfo:
|
||||
return sizeof(Response::PortInfo);
|
||||
case Type::PadData:
|
||||
return sizeof(Response::PadData);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace Response {
|
||||
|
||||
/**
|
||||
* Returns Type if the packet is valid, else none
|
||||
*
|
||||
* Note: Modifies the buffer to zero out the crc (since thats the easiest way to check without
|
||||
* copying the buffer)
|
||||
*/
|
||||
std::optional<Type> Validate(u8* data, std::size_t size) {
|
||||
if (size < sizeof(Header)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
Header header{};
|
||||
std::memcpy(&header, data, sizeof(Header));
|
||||
if (header.magic != SERVER_MAGIC) {
|
||||
LOG_ERROR(Input, "UDP Packet has an unexpected magic value");
|
||||
return std::nullopt;
|
||||
}
|
||||
if (header.protocol_version != PROTOCOL_VERSION) {
|
||||
LOG_ERROR(Input, "UDP Packet protocol mismatch");
|
||||
return std::nullopt;
|
||||
}
|
||||
if (header.type < Type::Version || header.type > Type::PadData) {
|
||||
LOG_ERROR(Input, "UDP Packet is an unknown type");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Packet size must equal sizeof(Header) + sizeof(Data)
|
||||
// and also verify that the packet info mentions the correct size. Since the spec includes the
|
||||
// type of the packet as part of the data, we need to include it in size calculations here
|
||||
// ie: payload_length == sizeof(T) + sizeof(Type)
|
||||
const std::size_t data_len = GetSizeOfResponseType(header.type);
|
||||
if (header.payload_length != data_len + sizeof(Type) || size < data_len + sizeof(Header)) {
|
||||
LOG_ERROR(
|
||||
Input,
|
||||
"UDP Packet payload length doesn't match. Received: {} PayloadLength: {} Expected: {}",
|
||||
size, header.payload_length, data_len + sizeof(Type));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const u32 crc32 = header.crc;
|
||||
boost::crc_32_type result;
|
||||
// zero out the crc in the buffer and then run the crc against it
|
||||
std::memset(&data[offsetof(Header, crc)], 0, sizeof(u32_le));
|
||||
|
||||
result.process_bytes(data, data_len + sizeof(Header));
|
||||
if (crc32 != result.checksum()) {
|
||||
LOG_ERROR(Input, "UDP Packet CRC check failed. Offset: {}", offsetof(Header, crc));
|
||||
return std::nullopt;
|
||||
}
|
||||
return header.type;
|
||||
}
|
||||
} // namespace Response
|
||||
|
||||
} // namespace InputCommon::CemuhookUDP
|
259
src/input_common/helpers/udp_protocol.h
Executable file
259
src/input_common/helpers/udp_protocol.h
Executable file
|
@ -0,0 +1,259 @@
|
|||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
|
||||
#include <boost/crc.hpp>
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/swap.h"
|
||||
|
||||
namespace InputCommon::CemuhookUDP {
|
||||
|
||||
constexpr std::size_t MAX_PACKET_SIZE = 100;
|
||||
constexpr u16 PROTOCOL_VERSION = 1001;
|
||||
constexpr u32 CLIENT_MAGIC = 0x43555344; // DSUC (but flipped for LE)
|
||||
constexpr u32 SERVER_MAGIC = 0x53555344; // DSUS (but flipped for LE)
|
||||
|
||||
enum class Type : u32 {
|
||||
Version = 0x00100000,
|
||||
PortInfo = 0x00100001,
|
||||
PadData = 0x00100002,
|
||||
};
|
||||
|
||||
struct Header {
|
||||
u32_le magic{};
|
||||
u16_le protocol_version{};
|
||||
u16_le payload_length{};
|
||||
u32_le crc{};
|
||||
u32_le id{};
|
||||
///> In the protocol, the type of the packet is not part of the header, but its convenient to
|
||||
///> include in the header so the callee doesn't have to duplicate the type twice when building
|
||||
///> the data
|
||||
Type type{};
|
||||
};
|
||||
static_assert(sizeof(Header) == 20, "UDP Message Header struct has wrong size");
|
||||
static_assert(std::is_trivially_copyable_v<Header>, "UDP Message Header is not trivially copyable");
|
||||
|
||||
using MacAddress = std::array<u8, 6>;
|
||||
constexpr MacAddress EMPTY_MAC_ADDRESS = {0, 0, 0, 0, 0, 0};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
template <typename T>
|
||||
struct Message {
|
||||
Header header{};
|
||||
T data;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
template <typename T>
|
||||
constexpr Type GetMessageType();
|
||||
|
||||
namespace Request {
|
||||
|
||||
struct Version {};
|
||||
/**
|
||||
* Requests the server to send information about what controllers are plugged into the ports
|
||||
* In citra's case, we only have one controller, so for simplicity's sake, we can just send a
|
||||
* request explicitly for the first controller port and leave it at that. In the future it would be
|
||||
* nice to make this configurable
|
||||
*/
|
||||
constexpr u32 MAX_PORTS = 4;
|
||||
struct PortInfo {
|
||||
u32_le pad_count{}; ///> Number of ports to request data for
|
||||
std::array<u8, MAX_PORTS> port;
|
||||
};
|
||||
static_assert(std::is_trivially_copyable_v<PortInfo>,
|
||||
"UDP Request PortInfo is not trivially copyable");
|
||||
|
||||
/**
|
||||
* Request the latest pad information from the server. If the server hasn't received this message
|
||||
* from the client in a reasonable time frame, the server will stop sending updates. The default
|
||||
* timeout seems to be 5 seconds.
|
||||
*/
|
||||
struct PadData {
|
||||
enum class Flags : u8 {
|
||||
AllPorts,
|
||||
Id,
|
||||
Mac,
|
||||
};
|
||||
/// Determines which method will be used as a look up for the controller
|
||||
Flags flags{};
|
||||
/// Index of the port of the controller to retrieve data about
|
||||
u8 port_id{};
|
||||
/// Mac address of the controller to retrieve data about
|
||||
MacAddress mac;
|
||||
};
|
||||
static_assert(sizeof(PadData) == 8, "UDP Request PadData struct has wrong size");
|
||||
static_assert(std::is_trivially_copyable_v<PadData>,
|
||||
"UDP Request PadData is not trivially copyable");
|
||||
|
||||
/**
|
||||
* Creates a message with the proper header data that can be sent to the server.
|
||||
* @param data Request body to send
|
||||
* @param client_id ID of the udp client (usually not checked on the server)
|
||||
*/
|
||||
template <typename T>
|
||||
Message<T> Create(const T data, const u32 client_id = 0) {
|
||||
boost::crc_32_type crc;
|
||||
Header header{
|
||||
CLIENT_MAGIC, PROTOCOL_VERSION, sizeof(T) + sizeof(Type), 0, client_id, GetMessageType<T>(),
|
||||
};
|
||||
Message<T> message{header, data};
|
||||
crc.process_bytes(&message, sizeof(Message<T>));
|
||||
message.header.crc = crc.checksum();
|
||||
return message;
|
||||
}
|
||||
} // namespace Request
|
||||
|
||||
namespace Response {
|
||||
|
||||
struct Version {
|
||||
u16_le version{};
|
||||
};
|
||||
static_assert(sizeof(Version) == 2, "UDP Response Version struct has wrong size");
|
||||
static_assert(std::is_trivially_copyable_v<Version>,
|
||||
"UDP Response Version is not trivially copyable");
|
||||
|
||||
struct PortInfo {
|
||||
u8 id{};
|
||||
u8 state{};
|
||||
u8 model{};
|
||||
u8 connection_type{};
|
||||
MacAddress mac;
|
||||
u8 battery{};
|
||||
u8 is_pad_active{};
|
||||
};
|
||||
static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size");
|
||||
static_assert(std::is_trivially_copyable_v<PortInfo>,
|
||||
"UDP Response PortInfo is not trivially copyable");
|
||||
|
||||
struct TouchPad {
|
||||
u8 is_active{};
|
||||
u8 id{};
|
||||
u16_le x{};
|
||||
u16_le y{};
|
||||
};
|
||||
static_assert(sizeof(TouchPad) == 6, "UDP Response TouchPad struct has wrong size ");
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct PadData {
|
||||
PortInfo info{};
|
||||
u32_le packet_counter{};
|
||||
|
||||
u16_le digital_button{};
|
||||
// The following union isn't trivially copyable but we don't use this input anyway.
|
||||
// union DigitalButton {
|
||||
// u16_le button;
|
||||
// BitField<0, 1, u16> button_1; // Share
|
||||
// BitField<1, 1, u16> button_2; // L3
|
||||
// BitField<2, 1, u16> button_3; // R3
|
||||
// BitField<3, 1, u16> button_4; // Options
|
||||
// BitField<4, 1, u16> button_5; // Up
|
||||
// BitField<5, 1, u16> button_6; // Right
|
||||
// BitField<6, 1, u16> button_7; // Down
|
||||
// BitField<7, 1, u16> button_8; // Left
|
||||
// BitField<8, 1, u16> button_9; // L2
|
||||
// BitField<9, 1, u16> button_10; // R2
|
||||
// BitField<10, 1, u16> button_11; // L1
|
||||
// BitField<11, 1, u16> button_12; // R1
|
||||
// BitField<12, 1, u16> button_13; // Triangle
|
||||
// BitField<13, 1, u16> button_14; // Circle
|
||||
// BitField<14, 1, u16> button_15; // Cross
|
||||
// BitField<15, 1, u16> button_16; // Square
|
||||
// } digital_button;
|
||||
|
||||
u8 home;
|
||||
/// If the device supports a "click" on the touchpad, this will change to 1 when a click happens
|
||||
u8 touch_hard_press{};
|
||||
u8 left_stick_x{};
|
||||
u8 left_stick_y{};
|
||||
u8 right_stick_x{};
|
||||
u8 right_stick_y{};
|
||||
|
||||
struct AnalogButton {
|
||||
u8 button_8{};
|
||||
u8 button_7{};
|
||||
u8 button_6{};
|
||||
u8 button_5{};
|
||||
u8 button_12{};
|
||||
u8 button_11{};
|
||||
u8 button_10{};
|
||||
u8 button_9{};
|
||||
u8 button_16{};
|
||||
u8 button_15{};
|
||||
u8 button_14{};
|
||||
u8 button_13{};
|
||||
} analog_button;
|
||||
|
||||
std::array<TouchPad, 2> touch;
|
||||
|
||||
u64_le motion_timestamp;
|
||||
|
||||
struct Accelerometer {
|
||||
float x{};
|
||||
float y{};
|
||||
float z{};
|
||||
} accel;
|
||||
|
||||
struct Gyroscope {
|
||||
float pitch{};
|
||||
float yaw{};
|
||||
float roll{};
|
||||
} gyro;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
static_assert(sizeof(PadData) == 80, "UDP Response PadData struct has wrong size ");
|
||||
static_assert(std::is_trivially_copyable_v<PadData>,
|
||||
"UDP Response PadData is not trivially copyable");
|
||||
|
||||
static_assert(sizeof(Message<PadData>) == MAX_PACKET_SIZE,
|
||||
"UDP MAX_PACKET_SIZE is no longer larger than Message<PadData>");
|
||||
|
||||
static_assert(sizeof(PadData::AnalogButton) == 12,
|
||||
"UDP Response AnalogButton struct has wrong size ");
|
||||
static_assert(sizeof(PadData::Accelerometer) == 12,
|
||||
"UDP Response Accelerometer struct has wrong size ");
|
||||
static_assert(sizeof(PadData::Gyroscope) == 12, "UDP Response Gyroscope struct has wrong size ");
|
||||
|
||||
/**
|
||||
* Create a Response Message from the data
|
||||
* @param data array of bytes sent from the server
|
||||
* @return boost::none if it failed to parse or Type if it succeeded. The client can then safely
|
||||
* copy the data into the appropriate struct for that Type
|
||||
*/
|
||||
std::optional<Type> Validate(u8* data, std::size_t size);
|
||||
|
||||
} // namespace Response
|
||||
|
||||
template <>
|
||||
constexpr Type GetMessageType<Request::Version>() {
|
||||
return Type::Version;
|
||||
}
|
||||
template <>
|
||||
constexpr Type GetMessageType<Request::PortInfo>() {
|
||||
return Type::PortInfo;
|
||||
}
|
||||
template <>
|
||||
constexpr Type GetMessageType<Request::PadData>() {
|
||||
return Type::PadData;
|
||||
}
|
||||
template <>
|
||||
constexpr Type GetMessageType<Response::Version>() {
|
||||
return Type::Version;
|
||||
}
|
||||
template <>
|
||||
constexpr Type GetMessageType<Response::PortInfo>() {
|
||||
return Type::PortInfo;
|
||||
}
|
||||
template <>
|
||||
constexpr Type GetMessageType<Response::PadData>() {
|
||||
return Type::PadData;
|
||||
}
|
||||
} // namespace InputCommon::CemuhookUDP
|
364
src/input_common/input_engine.cpp
Executable file
364
src/input_common/input_engine.cpp
Executable file
|
@ -0,0 +1,364 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/param_package.h"
|
||||
#include "input_common/input_engine.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
void InputEngine::PreSetController(const PadIdentifier& identifier) {
|
||||
std::lock_guard lock{mutex};
|
||||
if (!controller_list.contains(identifier)) {
|
||||
controller_list.insert_or_assign(identifier, ControllerData{});
|
||||
}
|
||||
}
|
||||
|
||||
void InputEngine::PreSetButton(const PadIdentifier& identifier, int button) {
|
||||
std::lock_guard lock{mutex};
|
||||
ControllerData& controller = controller_list.at(identifier);
|
||||
if (!controller.buttons.contains(button)) {
|
||||
controller.buttons.insert_or_assign(button, false);
|
||||
}
|
||||
}
|
||||
|
||||
void InputEngine::PreSetHatButton(const PadIdentifier& identifier, int button) {
|
||||
std::lock_guard lock{mutex};
|
||||
ControllerData& controller = controller_list.at(identifier);
|
||||
if (!controller.hat_buttons.contains(button)) {
|
||||
controller.hat_buttons.insert_or_assign(button, u8{0});
|
||||
}
|
||||
}
|
||||
|
||||
void InputEngine::PreSetAxis(const PadIdentifier& identifier, int axis) {
|
||||
std::lock_guard lock{mutex};
|
||||
ControllerData& controller = controller_list.at(identifier);
|
||||
if (!controller.axes.contains(axis)) {
|
||||
controller.axes.insert_or_assign(axis, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void InputEngine::PreSetMotion(const PadIdentifier& identifier, int motion) {
|
||||
std::lock_guard lock{mutex};
|
||||
ControllerData& controller = controller_list.at(identifier);
|
||||
if (!controller.motions.contains(motion)) {
|
||||
controller.motions.insert_or_assign(motion, BasicMotion{});
|
||||
}
|
||||
}
|
||||
|
||||
void InputEngine::SetButton(const PadIdentifier& identifier, int button, bool value) {
|
||||
{
|
||||
std::lock_guard lock{mutex};
|
||||
ControllerData& controller = controller_list.at(identifier);
|
||||
if (!configuring) {
|
||||
controller.buttons.insert_or_assign(button, value);
|
||||
}
|
||||
}
|
||||
TriggerOnButtonChange(identifier, button, value);
|
||||
}
|
||||
|
||||
void InputEngine::SetHatButton(const PadIdentifier& identifier, int button, u8 value) {
|
||||
{
|
||||
std::lock_guard lock{mutex};
|
||||
ControllerData& controller = controller_list.at(identifier);
|
||||
if (!configuring) {
|
||||
controller.hat_buttons.insert_or_assign(button, value);
|
||||
}
|
||||
}
|
||||
TriggerOnHatButtonChange(identifier, button, value);
|
||||
}
|
||||
|
||||
void InputEngine::SetAxis(const PadIdentifier& identifier, int axis, f32 value) {
|
||||
{
|
||||
std::lock_guard lock{mutex};
|
||||
ControllerData& controller = controller_list.at(identifier);
|
||||
if (!configuring) {
|
||||
controller.axes.insert_or_assign(axis, value);
|
||||
}
|
||||
}
|
||||
TriggerOnAxisChange(identifier, axis, value);
|
||||
}
|
||||
|
||||
void InputEngine::SetBattery(const PadIdentifier& identifier, BatteryLevel value) {
|
||||
{
|
||||
std::lock_guard lock{mutex};
|
||||
ControllerData& controller = controller_list.at(identifier);
|
||||
if (!configuring) {
|
||||
controller.battery = value;
|
||||
}
|
||||
}
|
||||
TriggerOnBatteryChange(identifier, value);
|
||||
}
|
||||
|
||||
void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, BasicMotion value) {
|
||||
{
|
||||
std::lock_guard lock{mutex};
|
||||
ControllerData& controller = controller_list.at(identifier);
|
||||
if (!configuring) {
|
||||
controller.motions.insert_or_assign(motion, value);
|
||||
}
|
||||
}
|
||||
TriggerOnMotionChange(identifier, motion, value);
|
||||
}
|
||||
|
||||
bool InputEngine::GetButton(const PadIdentifier& identifier, int button) const {
|
||||
std::lock_guard lock{mutex};
|
||||
if (!controller_list.contains(identifier)) {
|
||||
LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
|
||||
identifier.pad, identifier.port);
|
||||
return false;
|
||||
}
|
||||
ControllerData controller = controller_list.at(identifier);
|
||||
if (!controller.buttons.contains(button)) {
|
||||
LOG_ERROR(Input, "Invalid button {}", button);
|
||||
return false;
|
||||
}
|
||||
return controller.buttons.at(button);
|
||||
}
|
||||
|
||||
bool InputEngine::GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const {
|
||||
std::lock_guard lock{mutex};
|
||||
if (!controller_list.contains(identifier)) {
|
||||
LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
|
||||
identifier.pad, identifier.port);
|
||||
return false;
|
||||
}
|
||||
ControllerData controller = controller_list.at(identifier);
|
||||
if (!controller.hat_buttons.contains(button)) {
|
||||
LOG_ERROR(Input, "Invalid hat button {}", button);
|
||||
return false;
|
||||
}
|
||||
return (controller.hat_buttons.at(button) & direction) != 0;
|
||||
}
|
||||
|
||||
f32 InputEngine::GetAxis(const PadIdentifier& identifier, int axis) const {
|
||||
std::lock_guard lock{mutex};
|
||||
if (!controller_list.contains(identifier)) {
|
||||
LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
|
||||
identifier.pad, identifier.port);
|
||||
return 0.0f;
|
||||
}
|
||||
ControllerData controller = controller_list.at(identifier);
|
||||
if (!controller.axes.contains(axis)) {
|
||||
LOG_ERROR(Input, "Invalid axis {}", axis);
|
||||
return 0.0f;
|
||||
}
|
||||
return controller.axes.at(axis);
|
||||
}
|
||||
|
||||
BatteryLevel InputEngine::GetBattery(const PadIdentifier& identifier) const {
|
||||
std::lock_guard lock{mutex};
|
||||
if (!controller_list.contains(identifier)) {
|
||||
LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
|
||||
identifier.pad, identifier.port);
|
||||
return BatteryLevel::Charging;
|
||||
}
|
||||
ControllerData controller = controller_list.at(identifier);
|
||||
return controller.battery;
|
||||
}
|
||||
|
||||
BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) const {
|
||||
std::lock_guard lock{mutex};
|
||||
if (!controller_list.contains(identifier)) {
|
||||
LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
|
||||
identifier.pad, identifier.port);
|
||||
return {};
|
||||
}
|
||||
ControllerData controller = controller_list.at(identifier);
|
||||
return controller.motions.at(motion);
|
||||
}
|
||||
|
||||
void InputEngine::ResetButtonState() {
|
||||
for (std::pair<PadIdentifier, ControllerData> controller : controller_list) {
|
||||
for (std::pair<int, bool> button : controller.second.buttons) {
|
||||
SetButton(controller.first, button.first, false);
|
||||
}
|
||||
for (std::pair<int, bool> button : controller.second.hat_buttons) {
|
||||
SetHatButton(controller.first, button.first, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputEngine::ResetAnalogState() {
|
||||
for (std::pair<PadIdentifier, ControllerData> controller : controller_list) {
|
||||
for (std::pair<int, float> axis : controller.second.axes) {
|
||||
SetAxis(controller.first, axis.first, 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputEngine::TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value) {
|
||||
std::lock_guard lock{mutex_callback};
|
||||
for (const std::pair<int, InputIdentifier> poller_pair : callback_list) {
|
||||
const InputIdentifier& poller = poller_pair.second;
|
||||
if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Button, button)) {
|
||||
continue;
|
||||
}
|
||||
if (poller.callback.on_change) {
|
||||
poller.callback.on_change();
|
||||
}
|
||||
}
|
||||
if (!configuring || !mapping_callback.on_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
PreSetButton(identifier, button);
|
||||
if (value == GetButton(identifier, button)) {
|
||||
return;
|
||||
}
|
||||
mapping_callback.on_data(MappingData{
|
||||
.engine = GetEngineName(),
|
||||
.pad = identifier,
|
||||
.type = EngineInputType::Button,
|
||||
.index = button,
|
||||
.button_value = value,
|
||||
});
|
||||
}
|
||||
|
||||
void InputEngine::TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value) {
|
||||
std::lock_guard lock{mutex_callback};
|
||||
for (const std::pair<int, InputIdentifier> poller_pair : callback_list) {
|
||||
const InputIdentifier& poller = poller_pair.second;
|
||||
if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::HatButton, button)) {
|
||||
continue;
|
||||
}
|
||||
if (poller.callback.on_change) {
|
||||
poller.callback.on_change();
|
||||
}
|
||||
}
|
||||
if (!configuring || !mapping_callback.on_data) {
|
||||
return;
|
||||
}
|
||||
for (std::size_t index = 1; index < 0xff; index <<= 1) {
|
||||
bool button_value = (value & index) != 0;
|
||||
if (button_value == GetHatButton(identifier, button, static_cast<u8>(index))) {
|
||||
continue;
|
||||
}
|
||||
mapping_callback.on_data(MappingData{
|
||||
.engine = GetEngineName(),
|
||||
.pad = identifier,
|
||||
.type = EngineInputType::HatButton,
|
||||
.index = button,
|
||||
.hat_name = GetHatButtonName(static_cast<u8>(index)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void InputEngine::TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value) {
|
||||
std::lock_guard lock{mutex_callback};
|
||||
for (const std::pair<int, InputIdentifier> poller_pair : callback_list) {
|
||||
const InputIdentifier& poller = poller_pair.second;
|
||||
if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Analog, axis)) {
|
||||
continue;
|
||||
}
|
||||
if (poller.callback.on_change) {
|
||||
poller.callback.on_change();
|
||||
}
|
||||
}
|
||||
if (!configuring || !mapping_callback.on_data) {
|
||||
return;
|
||||
}
|
||||
if (std::abs(value - GetAxis(identifier, axis)) < 0.5f) {
|
||||
return;
|
||||
}
|
||||
mapping_callback.on_data(MappingData{
|
||||
.engine = GetEngineName(),
|
||||
.pad = identifier,
|
||||
.type = EngineInputType::Analog,
|
||||
.index = axis,
|
||||
.axis_value = value,
|
||||
});
|
||||
}
|
||||
|
||||
void InputEngine::TriggerOnBatteryChange(const PadIdentifier& identifier,
|
||||
[[maybe_unused]] BatteryLevel value) {
|
||||
std::lock_guard lock{mutex_callback};
|
||||
for (const std::pair<int, InputIdentifier> poller_pair : callback_list) {
|
||||
const InputIdentifier& poller = poller_pair.second;
|
||||
if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Battery, 0)) {
|
||||
continue;
|
||||
}
|
||||
if (poller.callback.on_change) {
|
||||
poller.callback.on_change();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int motion,
|
||||
BasicMotion value) {
|
||||
std::lock_guard lock{mutex_callback};
|
||||
for (const std::pair<int, InputIdentifier> poller_pair : callback_list) {
|
||||
const InputIdentifier& poller = poller_pair.second;
|
||||
if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Motion, motion)) {
|
||||
continue;
|
||||
}
|
||||
if (poller.callback.on_change) {
|
||||
poller.callback.on_change();
|
||||
}
|
||||
}
|
||||
if (!configuring || !mapping_callback.on_data) {
|
||||
return;
|
||||
}
|
||||
if (std::abs(value.gyro_x) < 1.0f && std::abs(value.gyro_y) < 1.0f &&
|
||||
std::abs(value.gyro_z) < 1.0f) {
|
||||
return;
|
||||
}
|
||||
mapping_callback.on_data(MappingData{
|
||||
.engine = GetEngineName(),
|
||||
.pad = identifier,
|
||||
.type = EngineInputType::Motion,
|
||||
.index = motion,
|
||||
.motion_value = value,
|
||||
});
|
||||
}
|
||||
|
||||
bool InputEngine::IsInputIdentifierEqual(const InputIdentifier& input_identifier,
|
||||
const PadIdentifier& identifier, EngineInputType type,
|
||||
int index) const {
|
||||
if (input_identifier.type != type) {
|
||||
return false;
|
||||
}
|
||||
if (input_identifier.index != index) {
|
||||
return false;
|
||||
}
|
||||
if (input_identifier.identifier != identifier) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void InputEngine::BeginConfiguration() {
|
||||
configuring = true;
|
||||
}
|
||||
|
||||
void InputEngine::EndConfiguration() {
|
||||
configuring = false;
|
||||
}
|
||||
|
||||
const std::string& InputEngine::GetEngineName() const {
|
||||
return input_engine;
|
||||
}
|
||||
|
||||
int InputEngine::SetCallback(InputIdentifier input_identifier) {
|
||||
std::lock_guard lock{mutex_callback};
|
||||
callback_list.insert_or_assign(last_callback_key, input_identifier);
|
||||
return last_callback_key++;
|
||||
}
|
||||
|
||||
void InputEngine::SetMappingCallback(MappingCallback callback) {
|
||||
std::lock_guard lock{mutex_callback};
|
||||
mapping_callback = std::move(callback);
|
||||
}
|
||||
|
||||
void InputEngine::DeleteCallback(int key) {
|
||||
std::lock_guard lock{mutex_callback};
|
||||
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 InputCommon
|
231
src/input_common/input_engine.h
Executable file
231
src/input_common/input_engine.h
Executable file
|
@ -0,0 +1,231 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/input.h"
|
||||
#include "common/param_package.h"
|
||||
#include "common/uuid.h"
|
||||
#include "input_common/main.h"
|
||||
|
||||
// Pad Identifier of data source
|
||||
struct PadIdentifier {
|
||||
Common::UUID guid{};
|
||||
std::size_t port{};
|
||||
std::size_t pad{};
|
||||
|
||||
friend constexpr bool operator==(const PadIdentifier&, const PadIdentifier&) = default;
|
||||
};
|
||||
|
||||
// Basic motion data containing data from the sensors and a timestamp in microsecons
|
||||
struct BasicMotion {
|
||||
float gyro_x;
|
||||
float gyro_y;
|
||||
float gyro_z;
|
||||
float accel_x;
|
||||
float accel_y;
|
||||
float accel_z;
|
||||
u64 delta_timestamp;
|
||||
};
|
||||
|
||||
// Stages of a battery charge
|
||||
enum class BatteryLevel {
|
||||
Empty,
|
||||
Critical,
|
||||
Low,
|
||||
Medium,
|
||||
Full,
|
||||
Charging,
|
||||
};
|
||||
|
||||
// Types of input that are stored in the engine
|
||||
enum class EngineInputType {
|
||||
None,
|
||||
Button,
|
||||
HatButton,
|
||||
Analog,
|
||||
Motion,
|
||||
Battery,
|
||||
};
|
||||
|
||||
namespace std {
|
||||
// Hash used to create lists from PadIdentifier data
|
||||
template <>
|
||||
struct hash<PadIdentifier> {
|
||||
size_t operator()(const PadIdentifier& pad_id) const noexcept {
|
||||
u64 hash_value = pad_id.guid.uuid[1] ^ pad_id.guid.uuid[0];
|
||||
hash_value ^= (static_cast<u64>(pad_id.port) << 32);
|
||||
hash_value ^= static_cast<u64>(pad_id.pad);
|
||||
return static_cast<size_t>(hash_value);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
// Data from the engine and device needed for creating a ParamPackage
|
||||
struct MappingData {
|
||||
std::string engine{};
|
||||
PadIdentifier pad{};
|
||||
EngineInputType type{};
|
||||
int index{};
|
||||
bool button_value{};
|
||||
std::string hat_name{};
|
||||
f32 axis_value{};
|
||||
BasicMotion motion_value{};
|
||||
};
|
||||
|
||||
// Triggered if data changed on the controller
|
||||
struct UpdateCallback {
|
||||
std::function<void()> on_change;
|
||||
};
|
||||
|
||||
// Triggered if data changed on the controller and the engine is on configuring mode
|
||||
struct MappingCallback {
|
||||
std::function<void(MappingData)> on_data;
|
||||
};
|
||||
|
||||
// Input Identifier of data source
|
||||
struct InputIdentifier {
|
||||
PadIdentifier identifier;
|
||||
EngineInputType type;
|
||||
int index;
|
||||
UpdateCallback callback;
|
||||
};
|
||||
|
||||
class InputEngine {
|
||||
public:
|
||||
explicit InputEngine(const std::string& input_engine_) : input_engine(input_engine_) {
|
||||
callback_list.clear();
|
||||
}
|
||||
|
||||
virtual ~InputEngine() = default;
|
||||
|
||||
// Enable configuring mode for mapping
|
||||
void BeginConfiguration();
|
||||
|
||||
// Disable configuring mode for mapping
|
||||
void EndConfiguration();
|
||||
|
||||
// Sets a led pattern for a controller
|
||||
virtual void SetLeds([[maybe_unused]] const PadIdentifier& identifier,
|
||||
[[maybe_unused]] const Common::Input::LedStatus led_status) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sets rumble to a controller
|
||||
virtual Common::Input::VibrationError SetRumble(
|
||||
[[maybe_unused]] const PadIdentifier& identifier,
|
||||
[[maybe_unused]] const Common::Input::VibrationStatus vibration) {
|
||||
return Common::Input::VibrationError::NotSupported;
|
||||
}
|
||||
|
||||
// Sets polling mode to a controller
|
||||
virtual Common::Input::PollingError SetPollingMode(
|
||||
[[maybe_unused]] const PadIdentifier& identifier,
|
||||
[[maybe_unused]] const Common::Input::PollingMode vibration) {
|
||||
return Common::Input::PollingError::NotSupported;
|
||||
}
|
||||
|
||||
// Returns the engine name
|
||||
[[nodiscard]] const std::string& GetEngineName() const;
|
||||
|
||||
/// Used for automapping features
|
||||
virtual std::vector<Common::ParamPackage> GetInputDevices() const {
|
||||
return {};
|
||||
};
|
||||
|
||||
/// Retrieves the button mappings for the given device
|
||||
virtual InputCommon::ButtonMapping GetButtonMappingForDevice(
|
||||
[[maybe_unused]] const Common::ParamPackage& params) {
|
||||
return {};
|
||||
};
|
||||
|
||||
/// Retrieves the analog mappings for the given device
|
||||
virtual InputCommon::AnalogMapping GetAnalogMappingForDevice(
|
||||
[[maybe_unused]] const Common::ParamPackage& params) {
|
||||
return {};
|
||||
};
|
||||
|
||||
/// Retrieves the motion mappings for the given device
|
||||
virtual InputCommon::MotionMapping GetMotionMappingForDevice(
|
||||
[[maybe_unused]] const Common::ParamPackage& params) {
|
||||
return {};
|
||||
};
|
||||
|
||||
/// Retrieves the name of the given input.
|
||||
virtual std::string GetUIName([[maybe_unused]] const Common::ParamPackage& params) const {
|
||||
return GetEngineName();
|
||||
};
|
||||
|
||||
/// Retrieves the index number of the given hat button direction
|
||||
virtual u8 GetHatButtonId([[maybe_unused]] const std::string direction_name) const {
|
||||
return 0;
|
||||
};
|
||||
|
||||
void PreSetController(const PadIdentifier& identifier);
|
||||
void PreSetButton(const PadIdentifier& identifier, int button);
|
||||
void PreSetHatButton(const PadIdentifier& identifier, int button);
|
||||
void PreSetAxis(const PadIdentifier& identifier, int axis);
|
||||
void PreSetMotion(const PadIdentifier& identifier, int motion);
|
||||
void ResetButtonState();
|
||||
void ResetAnalogState();
|
||||
|
||||
bool GetButton(const PadIdentifier& identifier, int button) const;
|
||||
bool GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const;
|
||||
f32 GetAxis(const PadIdentifier& identifier, int axis) const;
|
||||
BatteryLevel GetBattery(const PadIdentifier& identifier) const;
|
||||
BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const;
|
||||
|
||||
int SetCallback(InputIdentifier input_identifier);
|
||||
void SetMappingCallback(MappingCallback callback);
|
||||
void DeleteCallback(int key);
|
||||
|
||||
protected:
|
||||
void SetButton(const PadIdentifier& identifier, int button, bool value);
|
||||
void SetHatButton(const PadIdentifier& identifier, int button, u8 value);
|
||||
void SetAxis(const PadIdentifier& identifier, int axis, f32 value);
|
||||
void SetBattery(const PadIdentifier& identifier, BatteryLevel value);
|
||||
void SetMotion(const PadIdentifier& identifier, int motion, BasicMotion value);
|
||||
|
||||
virtual std::string GetHatButtonName([[maybe_unused]] u8 direction_value) const {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
private:
|
||||
struct ControllerData {
|
||||
std::unordered_map<int, bool> buttons;
|
||||
std::unordered_map<int, u8> hat_buttons;
|
||||
std::unordered_map<int, float> axes;
|
||||
std::unordered_map<int, BasicMotion> motions;
|
||||
BatteryLevel battery;
|
||||
};
|
||||
|
||||
void TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value);
|
||||
void TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value);
|
||||
void TriggerOnAxisChange(const PadIdentifier& identifier, int button, f32 value);
|
||||
void TriggerOnBatteryChange(const PadIdentifier& identifier, BatteryLevel value);
|
||||
void TriggerOnMotionChange(const PadIdentifier& identifier, int motion, BasicMotion value);
|
||||
|
||||
bool IsInputIdentifierEqual(const InputIdentifier& input_identifier,
|
||||
const PadIdentifier& identifier, EngineInputType type,
|
||||
int index) const;
|
||||
|
||||
mutable std::mutex mutex;
|
||||
mutable std::mutex mutex_callback;
|
||||
bool configuring{false};
|
||||
const std::string input_engine;
|
||||
int last_callback_key = 0;
|
||||
std::unordered_map<PadIdentifier, ControllerData> controller_list;
|
||||
std::unordered_map<int, InputIdentifier> callback_list;
|
||||
MappingCallback mapping_callback;
|
||||
};
|
||||
|
||||
} // namespace InputCommon
|
171
src/input_common/input_mapping.cpp
Executable file
171
src/input_common/input_mapping.cpp
Executable file
|
@ -0,0 +1,171 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "input_common/input_engine.h"
|
||||
#include "input_common/input_mapping.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
MappingFactory::MappingFactory() {}
|
||||
|
||||
void MappingFactory::BeginMapping(Polling::InputType type) {
|
||||
is_enabled = true;
|
||||
input_type = type;
|
||||
input_queue.Clear();
|
||||
first_axis = -1;
|
||||
second_axis = -1;
|
||||
}
|
||||
|
||||
[[nodiscard]] const Common::ParamPackage MappingFactory::GetNextInput() {
|
||||
Common::ParamPackage input;
|
||||
input_queue.Pop(input);
|
||||
return input;
|
||||
}
|
||||
|
||||
void MappingFactory::RegisterInput(const MappingData& data) {
|
||||
if (!is_enabled) {
|
||||
return;
|
||||
}
|
||||
switch (input_type) {
|
||||
case Polling::InputType::Button:
|
||||
RegisterButton(data);
|
||||
return;
|
||||
case Polling::InputType::Stick:
|
||||
RegisterStick(data);
|
||||
return;
|
||||
case Polling::InputType::Motion:
|
||||
RegisterMotion(data);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void MappingFactory::StopMapping() {
|
||||
is_enabled = false;
|
||||
input_type = Polling::InputType::None;
|
||||
input_queue.Clear();
|
||||
}
|
||||
|
||||
void MappingFactory::RegisterButton(const MappingData& data) {
|
||||
Common::ParamPackage new_input;
|
||||
new_input.Set("engine", data.engine);
|
||||
if (data.pad.guid != Common::UUID{}) {
|
||||
new_input.Set("guid", data.pad.guid.Format());
|
||||
}
|
||||
new_input.Set("port", static_cast<int>(data.pad.port));
|
||||
new_input.Set("pad", static_cast<int>(data.pad.pad));
|
||||
switch (data.type) {
|
||||
case EngineInputType::Button:
|
||||
// Workaround for old compatibility
|
||||
if (data.engine == "keyboard") {
|
||||
new_input.Set("code", data.index);
|
||||
break;
|
||||
}
|
||||
new_input.Set("button", data.index);
|
||||
break;
|
||||
case EngineInputType::HatButton:
|
||||
new_input.Set("hat", data.index);
|
||||
new_input.Set("direction", data.hat_name);
|
||||
break;
|
||||
case EngineInputType::Analog:
|
||||
new_input.Set("axis", data.index);
|
||||
new_input.Set("threshold", 0.5f);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
input_queue.Push(new_input);
|
||||
}
|
||||
|
||||
void MappingFactory::RegisterStick(const MappingData& data) {
|
||||
Common::ParamPackage new_input;
|
||||
new_input.Set("engine", data.engine);
|
||||
if (data.pad.guid != Common::UUID{}) {
|
||||
new_input.Set("guid", data.pad.guid.Format());
|
||||
}
|
||||
new_input.Set("port", static_cast<int>(data.pad.port));
|
||||
new_input.Set("pad", static_cast<int>(data.pad.pad));
|
||||
|
||||
// If engine is mouse map the mouse position as a joystick
|
||||
if (data.engine == "mouse") {
|
||||
new_input.Set("axis_x", 0);
|
||||
new_input.Set("axis_y", 1);
|
||||
new_input.Set("threshold", 0.5f);
|
||||
new_input.Set("range", 1.0f);
|
||||
new_input.Set("deadzone", 0.0f);
|
||||
input_queue.Push(new_input);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (data.type) {
|
||||
case EngineInputType::Button:
|
||||
case EngineInputType::HatButton:
|
||||
RegisterButton(data);
|
||||
return;
|
||||
case EngineInputType::Analog:
|
||||
if (first_axis == data.index) {
|
||||
return;
|
||||
}
|
||||
if (first_axis == -1) {
|
||||
first_axis = data.index;
|
||||
return;
|
||||
}
|
||||
new_input.Set("axis_x", first_axis);
|
||||
new_input.Set("axis_y", data.index);
|
||||
new_input.Set("threshold", 0.5f);
|
||||
new_input.Set("range", 0.95f);
|
||||
new_input.Set("deadzone", 0.15f);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
input_queue.Push(new_input);
|
||||
}
|
||||
|
||||
void MappingFactory::RegisterMotion(const MappingData& data) {
|
||||
Common::ParamPackage new_input;
|
||||
new_input.Set("engine", data.engine);
|
||||
if (data.pad.guid != Common::UUID{}) {
|
||||
new_input.Set("guid", data.pad.guid.Format());
|
||||
}
|
||||
new_input.Set("port", static_cast<int>(data.pad.port));
|
||||
new_input.Set("pad", static_cast<int>(data.pad.pad));
|
||||
switch (data.type) {
|
||||
case EngineInputType::Button:
|
||||
case EngineInputType::HatButton:
|
||||
RegisterButton(data);
|
||||
return;
|
||||
case EngineInputType::Analog:
|
||||
if (first_axis == data.index) {
|
||||
return;
|
||||
}
|
||||
if (second_axis == data.index) {
|
||||
return;
|
||||
}
|
||||
if (first_axis == -1) {
|
||||
first_axis = data.index;
|
||||
return;
|
||||
}
|
||||
if (second_axis == -1) {
|
||||
second_axis = data.index;
|
||||
return;
|
||||
}
|
||||
new_input.Set("axis_x", first_axis);
|
||||
new_input.Set("axis_y", second_axis);
|
||||
new_input.Set("axis_z", data.index);
|
||||
new_input.Set("range", 1.0f);
|
||||
new_input.Set("deadzone", 0.20f);
|
||||
break;
|
||||
case EngineInputType::Motion:
|
||||
new_input.Set("motion", data.index);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
input_queue.Push(new_input);
|
||||
}
|
||||
|
||||
} // namespace InputCommon
|
76
src/input_common/input_mapping.h
Executable file
76
src/input_common/input_mapping.h
Executable file
|
@ -0,0 +1,76 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included
|
||||
|
||||
#pragma once
|
||||
#include "common/threadsafe_queue.h"
|
||||
|
||||
namespace InputCommon {
|
||||
class InputEngine;
|
||||
struct MappingData;
|
||||
|
||||
class MappingFactory {
|
||||
public:
|
||||
MappingFactory();
|
||||
|
||||
/**
|
||||
* Resets all varables to beggin the mapping process
|
||||
* @param "type": type of input desired to be returned
|
||||
*/
|
||||
void BeginMapping(Polling::InputType type);
|
||||
|
||||
/// Returns an input event with mapping information from the input_queue
|
||||
[[nodiscard]] const Common::ParamPackage GetNextInput();
|
||||
|
||||
/**
|
||||
* Registers mapping input data from the driver
|
||||
* @param "data": An struct containing all the information needed to create a proper
|
||||
* ParamPackage
|
||||
*/
|
||||
void RegisterInput(const MappingData& data);
|
||||
|
||||
/// Stop polling from all backends
|
||||
void StopMapping();
|
||||
|
||||
private:
|
||||
/**
|
||||
* If provided data satisfies the requeriments it will push an element to the input_queue
|
||||
* Supported input:
|
||||
* - Button: Creates a basic button ParamPackage
|
||||
* - HatButton: Creates a basic hat button ParamPackage
|
||||
* - Analog: Creates a basic analog ParamPackage
|
||||
* @param "data": An struct containing all the information needed to create a proper
|
||||
* ParamPackage
|
||||
*/
|
||||
void RegisterButton(const MappingData& data);
|
||||
|
||||
/**
|
||||
* If provided data satisfies the requeriments it will push an element to the input_queue
|
||||
* Supported input:
|
||||
* - Button, HatButton: Pass the data to RegisterButton
|
||||
* - Analog: Stores the first axis and on the second axis creates a basic stick ParamPackage
|
||||
* @param "data": An struct containing all the information needed to create a proper
|
||||
* ParamPackage
|
||||
*/
|
||||
void RegisterStick(const MappingData& data);
|
||||
|
||||
/**
|
||||
* If provided data satisfies the requeriments it will push an element to the input_queue
|
||||
* Supported input:
|
||||
* - Button, HatButton: Pass the data to RegisterButton
|
||||
* - Analog: Stores the first two axis and on the third axis creates a basic Motion
|
||||
* ParamPackage
|
||||
* - Motion: Creates a basic Motion ParamPackage
|
||||
* @param "data": An struct containing all the information needed to create a proper
|
||||
* ParamPackage
|
||||
*/
|
||||
void RegisterMotion(const MappingData& data);
|
||||
|
||||
Common::SPSCQueue<Common::ParamPackage> input_queue;
|
||||
Polling::InputType input_type{Polling::InputType::None};
|
||||
bool is_enabled{};
|
||||
int first_axis = -1;
|
||||
int second_axis = -1;
|
||||
};
|
||||
|
||||
} // namespace InputCommon
|
962
src/input_common/input_poller.cpp
Executable file
962
src/input_common/input_poller.cpp
Executable file
|
@ -0,0 +1,962 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/input.h"
|
||||
|
||||
#include "input_common/input_engine.h"
|
||||
#include "input_common/input_poller.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
class DummyInput final : public Common::Input::InputDevice {
|
||||
public:
|
||||
explicit DummyInput() {}
|
||||
~DummyInput() {}
|
||||
};
|
||||
|
||||
class InputFromButton final : public Common::Input::InputDevice {
|
||||
public:
|
||||
explicit InputFromButton(PadIdentifier identifier_, int button_, bool toggle_, bool inverted_,
|
||||
InputEngine* input_engine_)
|
||||
: identifier(identifier_), button(button_), toggle(toggle_), inverted(inverted_),
|
||||
input_engine(input_engine_) {
|
||||
UpdateCallback engine_callback{[this]() { OnChange(); }};
|
||||
const InputIdentifier input_identifier{
|
||||
.identifier = identifier,
|
||||
.type = EngineInputType::Button,
|
||||
.index = button,
|
||||
.callback = engine_callback,
|
||||
};
|
||||
last_button_value = false;
|
||||
callback_key = input_engine->SetCallback(input_identifier);
|
||||
}
|
||||
|
||||
~InputFromButton() {
|
||||
input_engine->DeleteCallback(callback_key);
|
||||
}
|
||||
|
||||
Common::Input::ButtonStatus GetStatus() const {
|
||||
return {
|
||||
.value = input_engine->GetButton(identifier, button),
|
||||
.inverted = inverted,
|
||||
.toggle = toggle,
|
||||
};
|
||||
}
|
||||
|
||||
void ForceUpdate() {
|
||||
const Common::Input::CallbackStatus status{
|
||||
.type = Common::Input::InputType::Button,
|
||||
.button_status = GetStatus(),
|
||||
};
|
||||
|
||||
last_button_value = status.button_status.value;
|
||||
TriggerOnChange(status);
|
||||
}
|
||||
|
||||
void OnChange() {
|
||||
const Common::Input::CallbackStatus status{
|
||||
.type = Common::Input::InputType::Button,
|
||||
.button_status = GetStatus(),
|
||||
};
|
||||
|
||||
if (status.button_status.value != last_button_value) {
|
||||
last_button_value = status.button_status.value;
|
||||
TriggerOnChange(status);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const PadIdentifier identifier;
|
||||
const int button;
|
||||
const bool toggle;
|
||||
const bool inverted;
|
||||
int callback_key;
|
||||
bool last_button_value;
|
||||
InputEngine* input_engine;
|
||||
};
|
||||
|
||||
class InputFromHatButton final : public Common::Input::InputDevice {
|
||||
public:
|
||||
explicit InputFromHatButton(PadIdentifier identifier_, int button_, u8 direction_, bool toggle_,
|
||||
bool inverted_, InputEngine* input_engine_)
|
||||
: identifier(identifier_), button(button_), direction(direction_), toggle(toggle_),
|
||||
inverted(inverted_), input_engine(input_engine_) {
|
||||
UpdateCallback engine_callback{[this]() { OnChange(); }};
|
||||
const InputIdentifier input_identifier{
|
||||
.identifier = identifier,
|
||||
.type = EngineInputType::HatButton,
|
||||
.index = button,
|
||||
.callback = engine_callback,
|
||||
};
|
||||
last_button_value = false;
|
||||
callback_key = input_engine->SetCallback(input_identifier);
|
||||
}
|
||||
|
||||
~InputFromHatButton() {
|
||||
input_engine->DeleteCallback(callback_key);
|
||||
}
|
||||
|
||||
Common::Input::ButtonStatus GetStatus() const {
|
||||
return {
|
||||
.value = input_engine->GetHatButton(identifier, button, direction),
|
||||
.inverted = inverted,
|
||||
.toggle = toggle,
|
||||
};
|
||||
}
|
||||
|
||||
void ForceUpdate() {
|
||||
const Common::Input::CallbackStatus status{
|
||||
.type = Common::Input::InputType::Button,
|
||||
.button_status = GetStatus(),
|
||||
};
|
||||
|
||||
last_button_value = status.button_status.value;
|
||||
TriggerOnChange(status);
|
||||
}
|
||||
|
||||
void OnChange() {
|
||||
const Common::Input::CallbackStatus status{
|
||||
.type = Common::Input::InputType::Button,
|
||||
.button_status = GetStatus(),
|
||||
};
|
||||
|
||||
if (status.button_status.value != last_button_value) {
|
||||
last_button_value = status.button_status.value;
|
||||
TriggerOnChange(status);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const PadIdentifier identifier;
|
||||
const int button;
|
||||
const u8 direction;
|
||||
const bool toggle;
|
||||
const bool inverted;
|
||||
int callback_key;
|
||||
bool last_button_value;
|
||||
InputEngine* input_engine;
|
||||
};
|
||||
|
||||
class InputFromStick final : public Common::Input::InputDevice {
|
||||
public:
|
||||
explicit InputFromStick(PadIdentifier identifier_, int axis_x_, int axis_y_,
|
||||
Common::Input::AnalogProperties properties_x_,
|
||||
Common::Input::AnalogProperties properties_y_,
|
||||
InputEngine* input_engine_)
|
||||
: identifier(identifier_), axis_x(axis_x_), axis_y(axis_y_), properties_x(properties_x_),
|
||||
properties_y(properties_y_), input_engine(input_engine_) {
|
||||
UpdateCallback engine_callback{[this]() { OnChange(); }};
|
||||
const InputIdentifier x_input_identifier{
|
||||
.identifier = identifier,
|
||||
.type = EngineInputType::Analog,
|
||||
.index = axis_x,
|
||||
.callback = engine_callback,
|
||||
};
|
||||
const InputIdentifier y_input_identifier{
|
||||
.identifier = identifier,
|
||||
.type = EngineInputType::Analog,
|
||||
.index = axis_y,
|
||||
.callback = engine_callback,
|
||||
};
|
||||
last_axis_x_value = 0.0f;
|
||||
last_axis_y_value = 0.0f;
|
||||
callback_key_x = input_engine->SetCallback(x_input_identifier);
|
||||
callback_key_y = input_engine->SetCallback(y_input_identifier);
|
||||
}
|
||||
|
||||
~InputFromStick() {
|
||||
input_engine->DeleteCallback(callback_key_x);
|
||||
input_engine->DeleteCallback(callback_key_y);
|
||||
}
|
||||
|
||||
Common::Input::StickStatus GetStatus() const {
|
||||
Common::Input::StickStatus status;
|
||||
status.x = {
|
||||
.raw_value = input_engine->GetAxis(identifier, axis_x),
|
||||
.properties = properties_x,
|
||||
};
|
||||
status.y = {
|
||||
.raw_value = input_engine->GetAxis(identifier, axis_y),
|
||||
.properties = properties_y,
|
||||
};
|
||||
return status;
|
||||
}
|
||||
|
||||
void ForceUpdate() {
|
||||
const Common::Input::CallbackStatus status{
|
||||
.type = Common::Input::InputType::Stick,
|
||||
.stick_status = GetStatus(),
|
||||
};
|
||||
|
||||
last_axis_x_value = status.stick_status.x.raw_value;
|
||||
last_axis_y_value = status.stick_status.y.raw_value;
|
||||
TriggerOnChange(status);
|
||||
}
|
||||
|
||||
void OnChange() {
|
||||
const Common::Input::CallbackStatus status{
|
||||
.type = Common::Input::InputType::Stick,
|
||||
.stick_status = GetStatus(),
|
||||
};
|
||||
|
||||
if (status.stick_status.x.raw_value != last_axis_x_value ||
|
||||
status.stick_status.y.raw_value != last_axis_y_value) {
|
||||
last_axis_x_value = status.stick_status.x.raw_value;
|
||||
last_axis_y_value = status.stick_status.y.raw_value;
|
||||
TriggerOnChange(status);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const PadIdentifier identifier;
|
||||
const int axis_x;
|
||||
const int axis_y;
|
||||
const Common::Input::AnalogProperties properties_x;
|
||||
const Common::Input::AnalogProperties properties_y;
|
||||
int callback_key_x;
|
||||
int callback_key_y;
|
||||
float last_axis_x_value;
|
||||
float last_axis_y_value;
|
||||
InputEngine* input_engine;
|
||||
};
|
||||
|
||||
class InputFromTouch final : public Common::Input::InputDevice {
|
||||
public:
|
||||
explicit InputFromTouch(PadIdentifier identifier_, int touch_id_, int button_, bool toggle_,
|
||||
bool inverted_, int axis_x_, int axis_y_,
|
||||
Common::Input::AnalogProperties properties_x_,
|
||||
Common::Input::AnalogProperties properties_y_,
|
||||
InputEngine* input_engine_)
|
||||
: identifier(identifier_), touch_id(touch_id_), button(button_), toggle(toggle_),
|
||||
inverted(inverted_), axis_x(axis_x_), axis_y(axis_y_), properties_x(properties_x_),
|
||||
properties_y(properties_y_), input_engine(input_engine_) {
|
||||
UpdateCallback engine_callback{[this]() { OnChange(); }};
|
||||
const InputIdentifier button_input_identifier{
|
||||
.identifier = identifier,
|
||||
.type = EngineInputType::Button,
|
||||
.index = button,
|
||||
.callback = engine_callback,
|
||||
};
|
||||
const InputIdentifier x_input_identifier{
|
||||
.identifier = identifier,
|
||||
.type = EngineInputType::Analog,
|
||||
.index = axis_x,
|
||||
.callback = engine_callback,
|
||||
};
|
||||
const InputIdentifier y_input_identifier{
|
||||
.identifier = identifier,
|
||||
.type = EngineInputType::Analog,
|
||||
.index = axis_y,
|
||||
.callback = engine_callback,
|
||||
};
|
||||
last_axis_x_value = 0.0f;
|
||||
last_axis_y_value = 0.0f;
|
||||
last_button_value = false;
|
||||
callback_key_button = input_engine->SetCallback(button_input_identifier);
|
||||
callback_key_x = input_engine->SetCallback(x_input_identifier);
|
||||
callback_key_y = input_engine->SetCallback(y_input_identifier);
|
||||
}
|
||||
|
||||
~InputFromTouch() {
|
||||
input_engine->DeleteCallback(callback_key_button);
|
||||
input_engine->DeleteCallback(callback_key_x);
|
||||
input_engine->DeleteCallback(callback_key_y);
|
||||
}
|
||||
|
||||
Common::Input::TouchStatus GetStatus() const {
|
||||
Common::Input::TouchStatus status;
|
||||
status.id = touch_id;
|
||||
status.pressed = {
|
||||
.value = input_engine->GetButton(identifier, button),
|
||||
.inverted = inverted,
|
||||
.toggle = toggle,
|
||||
};
|
||||
status.x = {
|
||||
.raw_value = input_engine->GetAxis(identifier, axis_x),
|
||||
.properties = properties_x,
|
||||
};
|
||||
status.y = {
|
||||
.raw_value = input_engine->GetAxis(identifier, axis_y),
|
||||
.properties = properties_y,
|
||||
};
|
||||
return status;
|
||||
}
|
||||
|
||||
void OnChange() {
|
||||
const Common::Input::CallbackStatus status{
|
||||
.type = Common::Input::InputType::Touch,
|
||||
.touch_status = GetStatus(),
|
||||
};
|
||||
|
||||
if (status.touch_status.x.raw_value != last_axis_x_value ||
|
||||
status.touch_status.y.raw_value != last_axis_y_value ||
|
||||
status.touch_status.pressed.value != last_button_value) {
|
||||
last_axis_x_value = status.touch_status.x.raw_value;
|
||||
last_axis_y_value = status.touch_status.y.raw_value;
|
||||
last_button_value = status.touch_status.pressed.value;
|
||||
TriggerOnChange(status);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const PadIdentifier identifier;
|
||||
const int touch_id;
|
||||
const int button;
|
||||
const bool toggle;
|
||||
const bool inverted;
|
||||
const int axis_x;
|
||||
const int axis_y;
|
||||
const Common::Input::AnalogProperties properties_x;
|
||||
const Common::Input::AnalogProperties properties_y;
|
||||
int callback_key_button;
|
||||
int callback_key_x;
|
||||
int callback_key_y;
|
||||
bool last_button_value;
|
||||
float last_axis_x_value;
|
||||
float last_axis_y_value;
|
||||
InputEngine* input_engine;
|
||||
};
|
||||
|
||||
class InputFromTrigger final : public Common::Input::InputDevice {
|
||||
public:
|
||||
explicit InputFromTrigger(PadIdentifier identifier_, int button_, bool toggle_, bool inverted_,
|
||||
int axis_, Common::Input::AnalogProperties properties_,
|
||||
InputEngine* input_engine_)
|
||||
: identifier(identifier_), button(button_), toggle(toggle_), inverted(inverted_),
|
||||
axis(axis_), properties(properties_), input_engine(input_engine_) {
|
||||
UpdateCallback engine_callback{[this]() { OnChange(); }};
|
||||
const InputIdentifier button_input_identifier{
|
||||
.identifier = identifier,
|
||||
.type = EngineInputType::Button,
|
||||
.index = button,
|
||||
.callback = engine_callback,
|
||||
};
|
||||
const InputIdentifier axis_input_identifier{
|
||||
.identifier = identifier,
|
||||
.type = EngineInputType::Analog,
|
||||
.index = axis,
|
||||
.callback = engine_callback,
|
||||
};
|
||||
last_axis_value = 0.0f;
|
||||
last_button_value = false;
|
||||
callback_key_button = input_engine->SetCallback(button_input_identifier);
|
||||
axis_callback_key = input_engine->SetCallback(axis_input_identifier);
|
||||
}
|
||||
|
||||
~InputFromTrigger() {
|
||||
input_engine->DeleteCallback(callback_key_button);
|
||||
input_engine->DeleteCallback(axis_callback_key);
|
||||
}
|
||||
|
||||
Common::Input::TriggerStatus GetStatus() const {
|
||||
const Common::Input::AnalogStatus analog_status{
|
||||
.raw_value = input_engine->GetAxis(identifier, axis),
|
||||
.properties = properties,
|
||||
};
|
||||
const Common::Input::ButtonStatus button_status{
|
||||
.value = input_engine->GetButton(identifier, button),
|
||||
.inverted = inverted,
|
||||
.toggle = toggle,
|
||||
};
|
||||
return {
|
||||
.analog = analog_status,
|
||||
.pressed = button_status,
|
||||
};
|
||||
}
|
||||
|
||||
void OnChange() {
|
||||
const Common::Input::CallbackStatus status{
|
||||
.type = Common::Input::InputType::Trigger,
|
||||
.trigger_status = GetStatus(),
|
||||
};
|
||||
|
||||
if (status.trigger_status.analog.raw_value != last_axis_value ||
|
||||
status.trigger_status.pressed.value != last_button_value) {
|
||||
last_axis_value = status.trigger_status.analog.raw_value;
|
||||
last_button_value = status.trigger_status.pressed.value;
|
||||
TriggerOnChange(status);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const PadIdentifier identifier;
|
||||
const int button;
|
||||
const bool toggle;
|
||||
const bool inverted;
|
||||
const int axis;
|
||||
const Common::Input::AnalogProperties properties;
|
||||
int callback_key_button;
|
||||
int axis_callback_key;
|
||||
bool last_button_value;
|
||||
float last_axis_value;
|
||||
InputEngine* input_engine;
|
||||
};
|
||||
|
||||
class InputFromAnalog final : public Common::Input::InputDevice {
|
||||
public:
|
||||
explicit InputFromAnalog(PadIdentifier identifier_, int axis_,
|
||||
Common::Input::AnalogProperties properties_,
|
||||
InputEngine* input_engine_)
|
||||
: identifier(identifier_), axis(axis_), properties(properties_),
|
||||
input_engine(input_engine_) {
|
||||
UpdateCallback engine_callback{[this]() { OnChange(); }};
|
||||
const InputIdentifier input_identifier{
|
||||
.identifier = identifier,
|
||||
.type = EngineInputType::Analog,
|
||||
.index = axis,
|
||||
.callback = engine_callback,
|
||||
};
|
||||
last_axis_value = 0.0f;
|
||||
callback_key = input_engine->SetCallback(input_identifier);
|
||||
}
|
||||
|
||||
~InputFromAnalog() {
|
||||
input_engine->DeleteCallback(callback_key);
|
||||
}
|
||||
|
||||
Common::Input::AnalogStatus GetStatus() const {
|
||||
return {
|
||||
.raw_value = input_engine->GetAxis(identifier, axis),
|
||||
.properties = properties,
|
||||
};
|
||||
}
|
||||
|
||||
void OnChange() {
|
||||
const Common::Input::CallbackStatus status{
|
||||
.type = Common::Input::InputType::Analog,
|
||||
.analog_status = GetStatus(),
|
||||
};
|
||||
|
||||
if (status.analog_status.raw_value != last_axis_value) {
|
||||
last_axis_value = status.analog_status.raw_value;
|
||||
TriggerOnChange(status);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const PadIdentifier identifier;
|
||||
const int axis;
|
||||
const Common::Input::AnalogProperties properties;
|
||||
int callback_key;
|
||||
float last_axis_value;
|
||||
InputEngine* input_engine;
|
||||
};
|
||||
|
||||
class InputFromBattery final : public Common::Input::InputDevice {
|
||||
public:
|
||||
explicit InputFromBattery(PadIdentifier identifier_, InputEngine* input_engine_)
|
||||
: identifier(identifier_), input_engine(input_engine_) {
|
||||
UpdateCallback engine_callback{[this]() { OnChange(); }};
|
||||
const InputIdentifier input_identifier{
|
||||
.identifier = identifier,
|
||||
.type = EngineInputType::Battery,
|
||||
.index = 0,
|
||||
.callback = engine_callback,
|
||||
};
|
||||
last_battery_value = Common::Input::BatteryStatus::Charging;
|
||||
callback_key = input_engine->SetCallback(input_identifier);
|
||||
}
|
||||
|
||||
~InputFromBattery() {
|
||||
input_engine->DeleteCallback(callback_key);
|
||||
}
|
||||
|
||||
Common::Input::BatteryStatus GetStatus() const {
|
||||
return static_cast<Common::Input::BatteryLevel>(input_engine->GetBattery(identifier));
|
||||
}
|
||||
|
||||
void ForceUpdate() {
|
||||
const Common::Input::CallbackStatus status{
|
||||
.type = Common::Input::InputType::Battery,
|
||||
.battery_status = GetStatus(),
|
||||
};
|
||||
|
||||
last_battery_value = status.battery_status;
|
||||
TriggerOnChange(status);
|
||||
}
|
||||
|
||||
void OnChange() {
|
||||
const Common::Input::CallbackStatus status{
|
||||
.type = Common::Input::InputType::Battery,
|
||||
.battery_status = GetStatus(),
|
||||
};
|
||||
|
||||
if (status.battery_status != last_battery_value) {
|
||||
last_battery_value = status.battery_status;
|
||||
TriggerOnChange(status);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const PadIdentifier identifier;
|
||||
int callback_key;
|
||||
Common::Input::BatteryStatus last_battery_value;
|
||||
InputEngine* input_engine;
|
||||
};
|
||||
|
||||
class InputFromMotion final : public Common::Input::InputDevice {
|
||||
public:
|
||||
explicit InputFromMotion(PadIdentifier identifier_, int motion_sensor_,
|
||||
InputEngine* input_engine_)
|
||||
: identifier(identifier_), motion_sensor(motion_sensor_), input_engine(input_engine_) {
|
||||
UpdateCallback engine_callback{[this]() { OnChange(); }};
|
||||
const InputIdentifier input_identifier{
|
||||
.identifier = identifier,
|
||||
.type = EngineInputType::Motion,
|
||||
.index = motion_sensor,
|
||||
.callback = engine_callback,
|
||||
};
|
||||
callback_key = input_engine->SetCallback(input_identifier);
|
||||
}
|
||||
|
||||
~InputFromMotion() {
|
||||
input_engine->DeleteCallback(callback_key);
|
||||
}
|
||||
|
||||
Common::Input::MotionStatus GetStatus() const {
|
||||
const auto basic_motion = input_engine->GetMotion(identifier, motion_sensor);
|
||||
Common::Input::MotionStatus status{};
|
||||
const Common::Input::AnalogProperties properties = {
|
||||
.deadzone = 0.001f,
|
||||
.range = 1.0f,
|
||||
.offset = 0.0f,
|
||||
};
|
||||
status.accel.x = {.raw_value = basic_motion.accel_x, .properties = properties};
|
||||
status.accel.y = {.raw_value = basic_motion.accel_y, .properties = properties};
|
||||
status.accel.z = {.raw_value = basic_motion.accel_z, .properties = properties};
|
||||
status.gyro.x = {.raw_value = basic_motion.gyro_x, .properties = properties};
|
||||
status.gyro.y = {.raw_value = basic_motion.gyro_y, .properties = properties};
|
||||
status.gyro.z = {.raw_value = basic_motion.gyro_z, .properties = properties};
|
||||
status.delta_timestamp = basic_motion.delta_timestamp;
|
||||
return status;
|
||||
}
|
||||
|
||||
void OnChange() {
|
||||
const Common::Input::CallbackStatus status{
|
||||
.type = Common::Input::InputType::Motion,
|
||||
.motion_status = GetStatus(),
|
||||
};
|
||||
|
||||
TriggerOnChange(status);
|
||||
}
|
||||
|
||||
private:
|
||||
const PadIdentifier identifier;
|
||||
const int motion_sensor;
|
||||
int callback_key;
|
||||
InputEngine* input_engine;
|
||||
};
|
||||
|
||||
class InputFromAxisMotion final : public Common::Input::InputDevice {
|
||||
public:
|
||||
explicit InputFromAxisMotion(PadIdentifier identifier_, int axis_x_, int axis_y_, int axis_z_,
|
||||
Common::Input::AnalogProperties properties_x_,
|
||||
Common::Input::AnalogProperties properties_y_,
|
||||
Common::Input::AnalogProperties properties_z_,
|
||||
InputEngine* input_engine_)
|
||||
: identifier(identifier_), axis_x(axis_x_), axis_y(axis_y_), axis_z(axis_z_),
|
||||
properties_x(properties_x_), properties_y(properties_y_), properties_z(properties_z_),
|
||||
input_engine(input_engine_) {
|
||||
UpdateCallback engine_callback{[this]() { OnChange(); }};
|
||||
const InputIdentifier x_input_identifier{
|
||||
.identifier = identifier,
|
||||
.type = EngineInputType::Analog,
|
||||
.index = axis_x,
|
||||
.callback = engine_callback,
|
||||
};
|
||||
const InputIdentifier y_input_identifier{
|
||||
.identifier = identifier,
|
||||
.type = EngineInputType::Analog,
|
||||
.index = axis_y,
|
||||
.callback = engine_callback,
|
||||
};
|
||||
const InputIdentifier z_input_identifier{
|
||||
.identifier = identifier,
|
||||
.type = EngineInputType::Analog,
|
||||
.index = axis_z,
|
||||
.callback = engine_callback,
|
||||
};
|
||||
last_axis_x_value = 0.0f;
|
||||
last_axis_y_value = 0.0f;
|
||||
last_axis_z_value = 0.0f;
|
||||
callback_key_x = input_engine->SetCallback(x_input_identifier);
|
||||
callback_key_y = input_engine->SetCallback(y_input_identifier);
|
||||
callback_key_z = input_engine->SetCallback(z_input_identifier);
|
||||
}
|
||||
|
||||
~InputFromAxisMotion() {
|
||||
input_engine->DeleteCallback(callback_key_x);
|
||||
input_engine->DeleteCallback(callback_key_y);
|
||||
input_engine->DeleteCallback(callback_key_z);
|
||||
}
|
||||
|
||||
Common::Input::MotionStatus GetStatus() const {
|
||||
Common::Input::MotionStatus status{};
|
||||
status.gyro.x = {
|
||||
.raw_value = input_engine->GetAxis(identifier, axis_x),
|
||||
.properties = properties_x,
|
||||
};
|
||||
status.gyro.y = {
|
||||
.raw_value = input_engine->GetAxis(identifier, axis_y),
|
||||
.properties = properties_y,
|
||||
};
|
||||
status.gyro.z = {
|
||||
.raw_value = input_engine->GetAxis(identifier, axis_z),
|
||||
.properties = properties_z,
|
||||
};
|
||||
return status;
|
||||
}
|
||||
|
||||
void ForceUpdate() {
|
||||
const Common::Input::CallbackStatus status{
|
||||
.type = Common::Input::InputType::Motion,
|
||||
.motion_status = GetStatus(),
|
||||
};
|
||||
|
||||
last_axis_x_value = status.motion_status.gyro.x.raw_value;
|
||||
last_axis_y_value = status.motion_status.gyro.y.raw_value;
|
||||
last_axis_z_value = status.motion_status.gyro.z.raw_value;
|
||||
TriggerOnChange(status);
|
||||
}
|
||||
|
||||
void OnChange() {
|
||||
const Common::Input::CallbackStatus status{
|
||||
.type = Common::Input::InputType::Motion,
|
||||
.motion_status = GetStatus(),
|
||||
};
|
||||
|
||||
if (status.motion_status.gyro.x.raw_value != last_axis_x_value ||
|
||||
status.motion_status.gyro.y.raw_value != last_axis_y_value ||
|
||||
status.motion_status.gyro.z.raw_value != last_axis_z_value) {
|
||||
last_axis_x_value = status.motion_status.gyro.x.raw_value;
|
||||
last_axis_y_value = status.motion_status.gyro.y.raw_value;
|
||||
last_axis_z_value = status.motion_status.gyro.z.raw_value;
|
||||
TriggerOnChange(status);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const PadIdentifier identifier;
|
||||
const int axis_x;
|
||||
const int axis_y;
|
||||
const int axis_z;
|
||||
const Common::Input::AnalogProperties properties_x;
|
||||
const Common::Input::AnalogProperties properties_y;
|
||||
const Common::Input::AnalogProperties properties_z;
|
||||
int callback_key_x;
|
||||
int callback_key_y;
|
||||
int callback_key_z;
|
||||
float last_axis_x_value;
|
||||
float last_axis_y_value;
|
||||
float last_axis_z_value;
|
||||
InputEngine* input_engine;
|
||||
};
|
||||
|
||||
class OutputFromIdentifier final : public Common::Input::OutputDevice {
|
||||
public:
|
||||
explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_)
|
||||
: identifier(identifier_), input_engine(input_engine_) {}
|
||||
|
||||
virtual void SetLED(Common::Input::LedStatus led_status) {
|
||||
input_engine->SetLeds(identifier, led_status);
|
||||
}
|
||||
|
||||
virtual Common::Input::VibrationError SetVibration(
|
||||
Common::Input::VibrationStatus vibration_status) {
|
||||
return input_engine->SetRumble(identifier, vibration_status);
|
||||
}
|
||||
|
||||
virtual Common::Input::PollingError SetPollingMode(Common::Input::PollingMode polling_mode) {
|
||||
return input_engine->SetPollingMode(identifier, polling_mode);
|
||||
}
|
||||
|
||||
private:
|
||||
const PadIdentifier identifier;
|
||||
InputEngine* input_engine;
|
||||
};
|
||||
|
||||
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateButtonDevice(
|
||||
const Common::ParamPackage& params) {
|
||||
const PadIdentifier identifier = {
|
||||
.guid = Common::UUID{params.Get("guid", "")},
|
||||
.port = static_cast<std::size_t>(params.Get("port", 0)),
|
||||
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
|
||||
};
|
||||
|
||||
const auto button_id = params.Get("button", 0);
|
||||
const auto keyboard_key = params.Get("code", 0);
|
||||
const auto toggle = params.Get("toggle", false);
|
||||
const auto inverted = params.Get("inverted", false);
|
||||
input_engine->PreSetController(identifier);
|
||||
input_engine->PreSetButton(identifier, button_id);
|
||||
input_engine->PreSetButton(identifier, keyboard_key);
|
||||
if (keyboard_key != 0) {
|
||||
return std::make_unique<InputFromButton>(identifier, keyboard_key, toggle, inverted,
|
||||
input_engine.get());
|
||||
}
|
||||
return std::make_unique<InputFromButton>(identifier, button_id, toggle, inverted,
|
||||
input_engine.get());
|
||||
}
|
||||
|
||||
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateHatButtonDevice(
|
||||
const Common::ParamPackage& params) {
|
||||
const PadIdentifier identifier = {
|
||||
.guid = Common::UUID{params.Get("guid", "")},
|
||||
.port = static_cast<std::size_t>(params.Get("port", 0)),
|
||||
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
|
||||
};
|
||||
|
||||
const auto button_id = params.Get("hat", 0);
|
||||
const auto direction = input_engine->GetHatButtonId(params.Get("direction", ""));
|
||||
const auto toggle = params.Get("toggle", false);
|
||||
const auto inverted = params.Get("inverted", false);
|
||||
|
||||
input_engine->PreSetController(identifier);
|
||||
input_engine->PreSetHatButton(identifier, button_id);
|
||||
return std::make_unique<InputFromHatButton>(identifier, button_id, direction, toggle, inverted,
|
||||
input_engine.get());
|
||||
}
|
||||
|
||||
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateStickDevice(
|
||||
const Common::ParamPackage& params) {
|
||||
const auto deadzone = std::clamp(params.Get("deadzone", 0.15f), 0.0f, 1.0f);
|
||||
const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f);
|
||||
const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f);
|
||||
const PadIdentifier identifier = {
|
||||
.guid = Common::UUID{params.Get("guid", "")},
|
||||
.port = static_cast<std::size_t>(params.Get("port", 0)),
|
||||
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
|
||||
};
|
||||
|
||||
const auto axis_x = params.Get("axis_x", 0);
|
||||
const Common::Input::AnalogProperties properties_x = {
|
||||
.deadzone = deadzone,
|
||||
.range = range,
|
||||
.threshold = threshold,
|
||||
.offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f),
|
||||
.inverted = params.Get("invert_x", "+") == "-",
|
||||
};
|
||||
|
||||
const auto axis_y = params.Get("axis_y", 1);
|
||||
const Common::Input::AnalogProperties properties_y = {
|
||||
.deadzone = deadzone,
|
||||
.range = range,
|
||||
.threshold = threshold,
|
||||
.offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f),
|
||||
.inverted = params.Get("invert_y", "+") != "+",
|
||||
};
|
||||
input_engine->PreSetController(identifier);
|
||||
input_engine->PreSetAxis(identifier, axis_x);
|
||||
input_engine->PreSetAxis(identifier, axis_y);
|
||||
return std::make_unique<InputFromStick>(identifier, axis_x, axis_y, properties_x, properties_y,
|
||||
input_engine.get());
|
||||
}
|
||||
|
||||
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateAnalogDevice(
|
||||
const Common::ParamPackage& params) {
|
||||
const PadIdentifier identifier = {
|
||||
.guid = Common::UUID{params.Get("guid", "")},
|
||||
.port = static_cast<std::size_t>(params.Get("port", 0)),
|
||||
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
|
||||
};
|
||||
|
||||
const auto axis = params.Get("axis", 0);
|
||||
const Common::Input::AnalogProperties properties = {
|
||||
.deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f),
|
||||
.range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f),
|
||||
.threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f),
|
||||
.offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f),
|
||||
.inverted = params.Get("invert", "+") == "-",
|
||||
};
|
||||
input_engine->PreSetController(identifier);
|
||||
input_engine->PreSetAxis(identifier, axis);
|
||||
return std::make_unique<InputFromAnalog>(identifier, axis, properties, input_engine.get());
|
||||
}
|
||||
|
||||
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateTriggerDevice(
|
||||
const Common::ParamPackage& params) {
|
||||
const PadIdentifier identifier = {
|
||||
.guid = Common::UUID{params.Get("guid", "")},
|
||||
.port = static_cast<std::size_t>(params.Get("port", 0)),
|
||||
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
|
||||
};
|
||||
|
||||
const auto button = params.Get("button", 0);
|
||||
const auto toggle = params.Get("toggle", false);
|
||||
const auto inverted = params.Get("inverted", false);
|
||||
|
||||
const auto axis = params.Get("axis", 0);
|
||||
const Common::Input::AnalogProperties properties = {
|
||||
.deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f),
|
||||
.range = std::clamp(params.Get("range", 1.0f), 0.25f, 2.50f),
|
||||
.threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f),
|
||||
.offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f),
|
||||
.inverted = params.Get("invert", false) != 0,
|
||||
};
|
||||
input_engine->PreSetController(identifier);
|
||||
input_engine->PreSetAxis(identifier, axis);
|
||||
input_engine->PreSetButton(identifier, button);
|
||||
return std::make_unique<InputFromTrigger>(identifier, button, toggle, inverted, axis,
|
||||
properties, input_engine.get());
|
||||
}
|
||||
|
||||
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateTouchDevice(
|
||||
const Common::ParamPackage& params) {
|
||||
const auto touch_id = params.Get("touch_id", 0);
|
||||
const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
|
||||
const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f);
|
||||
const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f);
|
||||
const PadIdentifier identifier = {
|
||||
.guid = Common::UUID{params.Get("guid", "")},
|
||||
.port = static_cast<std::size_t>(params.Get("port", 0)),
|
||||
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
|
||||
};
|
||||
|
||||
const auto button = params.Get("button", 0);
|
||||
const auto toggle = params.Get("toggle", false);
|
||||
const auto inverted = params.Get("inverted", false);
|
||||
|
||||
const auto axis_x = params.Get("axis_x", 0);
|
||||
const Common::Input::AnalogProperties properties_x = {
|
||||
.deadzone = deadzone,
|
||||
.range = range,
|
||||
.threshold = threshold,
|
||||
.offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f),
|
||||
.inverted = params.Get("invert_x", "+") == "-",
|
||||
};
|
||||
|
||||
const auto axis_y = params.Get("axis_y", 1);
|
||||
const Common::Input::AnalogProperties properties_y = {
|
||||
.deadzone = deadzone,
|
||||
.range = range,
|
||||
.threshold = threshold,
|
||||
.offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f),
|
||||
.inverted = params.Get("invert_y", false) != 0,
|
||||
};
|
||||
input_engine->PreSetController(identifier);
|
||||
input_engine->PreSetAxis(identifier, axis_x);
|
||||
input_engine->PreSetAxis(identifier, axis_y);
|
||||
input_engine->PreSetButton(identifier, button);
|
||||
return std::make_unique<InputFromTouch>(identifier, touch_id, button, toggle, inverted, axis_x,
|
||||
axis_y, properties_x, properties_y, input_engine.get());
|
||||
}
|
||||
|
||||
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateBatteryDevice(
|
||||
const Common::ParamPackage& params) {
|
||||
const PadIdentifier identifier = {
|
||||
.guid = Common::UUID{params.Get("guid", "")},
|
||||
.port = static_cast<std::size_t>(params.Get("port", 0)),
|
||||
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
|
||||
};
|
||||
|
||||
input_engine->PreSetController(identifier);
|
||||
return std::make_unique<InputFromBattery>(identifier, input_engine.get());
|
||||
}
|
||||
|
||||
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice(
|
||||
Common::ParamPackage params) {
|
||||
const PadIdentifier identifier = {
|
||||
.guid = Common::UUID{params.Get("guid", "")},
|
||||
.port = static_cast<std::size_t>(params.Get("port", 0)),
|
||||
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
|
||||
};
|
||||
|
||||
if (params.Has("motion")) {
|
||||
const auto motion_sensor = params.Get("motion", 0);
|
||||
input_engine->PreSetController(identifier);
|
||||
input_engine->PreSetMotion(identifier, motion_sensor);
|
||||
return std::make_unique<InputFromMotion>(identifier, motion_sensor, input_engine.get());
|
||||
}
|
||||
|
||||
const auto deadzone = std::clamp(params.Get("deadzone", 0.15f), 0.0f, 1.0f);
|
||||
const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f);
|
||||
const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f);
|
||||
|
||||
const auto axis_x = params.Get("axis_x", 0);
|
||||
const Common::Input::AnalogProperties properties_x = {
|
||||
.deadzone = deadzone,
|
||||
.range = range,
|
||||
.threshold = threshold,
|
||||
.offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f),
|
||||
.inverted = params.Get("invert_x", "+") == "-",
|
||||
};
|
||||
|
||||
const auto axis_y = params.Get("axis_y", 1);
|
||||
const Common::Input::AnalogProperties properties_y = {
|
||||
.deadzone = deadzone,
|
||||
.range = range,
|
||||
.threshold = threshold,
|
||||
.offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f),
|
||||
.inverted = params.Get("invert_y", "+") != "+",
|
||||
};
|
||||
|
||||
const auto axis_z = params.Get("axis_z", 1);
|
||||
const Common::Input::AnalogProperties properties_z = {
|
||||
.deadzone = deadzone,
|
||||
.range = range,
|
||||
.threshold = threshold,
|
||||
.offset = std::clamp(params.Get("offset_z", 0.0f), -1.0f, 1.0f),
|
||||
.inverted = params.Get("invert_z", "+") != "+",
|
||||
};
|
||||
input_engine->PreSetController(identifier);
|
||||
input_engine->PreSetAxis(identifier, axis_x);
|
||||
input_engine->PreSetAxis(identifier, axis_y);
|
||||
input_engine->PreSetAxis(identifier, axis_z);
|
||||
return std::make_unique<InputFromAxisMotion>(identifier, axis_x, axis_y, axis_z, properties_x,
|
||||
properties_y, properties_z, input_engine.get());
|
||||
}
|
||||
|
||||
InputFactory::InputFactory(std::shared_ptr<InputEngine> input_engine_)
|
||||
: input_engine(std::move(input_engine_)) {}
|
||||
|
||||
std::unique_ptr<Common::Input::InputDevice> InputFactory::Create(
|
||||
const Common::ParamPackage& params) {
|
||||
if (params.Has("battery")) {
|
||||
return CreateBatteryDevice(params);
|
||||
}
|
||||
if (params.Has("button") && params.Has("axis")) {
|
||||
return CreateTriggerDevice(params);
|
||||
}
|
||||
if (params.Has("button") && params.Has("axis_x") && params.Has("axis_y")) {
|
||||
return CreateTouchDevice(params);
|
||||
}
|
||||
if (params.Has("button") || params.Has("code")) {
|
||||
return CreateButtonDevice(params);
|
||||
}
|
||||
if (params.Has("hat")) {
|
||||
return CreateHatButtonDevice(params);
|
||||
}
|
||||
if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) {
|
||||
return CreateMotionDevice(params);
|
||||
}
|
||||
if (params.Has("motion")) {
|
||||
return CreateMotionDevice(params);
|
||||
}
|
||||
if (params.Has("axis_x") && params.Has("axis_y")) {
|
||||
return CreateStickDevice(params);
|
||||
}
|
||||
if (params.Has("axis")) {
|
||||
return CreateAnalogDevice(params);
|
||||
}
|
||||
LOG_ERROR(Input, "Invalid parameters given");
|
||||
return std::make_unique<DummyInput>();
|
||||
}
|
||||
|
||||
OutputFactory::OutputFactory(std::shared_ptr<InputEngine> input_engine_)
|
||||
: input_engine(std::move(input_engine_)) {}
|
||||
|
||||
std::unique_ptr<Common::Input::OutputDevice> OutputFactory::Create(
|
||||
const Common::ParamPackage& params) {
|
||||
const PadIdentifier identifier = {
|
||||
.guid = Common::UUID{params.Get("guid", "")},
|
||||
.port = static_cast<std::size_t>(params.Get("port", 0)),
|
||||
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
|
||||
};
|
||||
|
||||
input_engine->PreSetController(identifier);
|
||||
return std::make_unique<OutputFromIdentifier>(identifier, input_engine.get());
|
||||
}
|
||||
|
||||
} // namespace InputCommon
|
217
src/input_common/input_poller.h
Executable file
217
src/input_common/input_poller.h
Executable file
|
@ -0,0 +1,217 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Input {
|
||||
class InputDevice;
|
||||
|
||||
template <typename InputDevice>
|
||||
class Factory;
|
||||
}; // namespace Input
|
||||
|
||||
namespace InputCommon {
|
||||
class InputEngine;
|
||||
/**
|
||||
* An Input factory. It receives input events and forward them to all input devices it created.
|
||||
*/
|
||||
|
||||
class OutputFactory final : public Common::Input::Factory<Common::Input::OutputDevice> {
|
||||
public:
|
||||
explicit OutputFactory(std::shared_ptr<InputEngine> input_engine_);
|
||||
|
||||
/**
|
||||
* Creates an output device from the parameters given.
|
||||
* @param params contains parameters for creating the device:
|
||||
* @param - "guid": text string for identifing controllers
|
||||
* @param - "port": port of the connected device
|
||||
* @param - "pad": slot of the connected controller
|
||||
* @return an unique ouput device with the parameters specified
|
||||
*/
|
||||
std::unique_ptr<Common::Input::OutputDevice> Create(
|
||||
const Common::ParamPackage& params) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<InputEngine> input_engine;
|
||||
};
|
||||
|
||||
class InputFactory final : public Common::Input::Factory<Common::Input::InputDevice> {
|
||||
public:
|
||||
explicit InputFactory(std::shared_ptr<InputEngine> input_engine_);
|
||||
|
||||
/**
|
||||
* Creates an input device from the parameters given. Identifies the type of input to be
|
||||
* returned if it contains the following parameters:
|
||||
* - button: Contains "button" or "code"
|
||||
* - hat_button: Contains "hat"
|
||||
* - analog: Contains "axis"
|
||||
* - trigger: Contains "button" and "axis"
|
||||
* - stick: Contains "axis_x" and "axis_y"
|
||||
* - motion: Contains "axis_x", "axis_y" and "axis_z"
|
||||
* - motion: Contains "motion"
|
||||
* - touch: Contains "button", "axis_x" and "axis_y"
|
||||
* - battery: Contains "battery"
|
||||
* - output: Contains "output"
|
||||
* @param params contains parameters for creating the device:
|
||||
* @param - "code": the code of the keyboard key to bind with the input
|
||||
* @param - "button": same as "code" but for controller buttons
|
||||
* @param - "hat": similar as "button" but it's a group of hat buttons from SDL
|
||||
* @param - "axis": the axis number of the axis to bind with the input
|
||||
* @param - "motion": the motion number of the motion to bind with the input
|
||||
* @param - "axis_x": same as axis but specifing horizontal direction
|
||||
* @param - "axis_y": same as axis but specifing vertical direction
|
||||
* @param - "axis_z": same as axis but specifing forward direction
|
||||
* @param - "battery": Only used as a placeholder to set the input type
|
||||
* @return an unique input device with the parameters specified
|
||||
*/
|
||||
std::unique_ptr<Common::Input::InputDevice> Create(const Common::ParamPackage& params) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Creates a button device from the parameters given.
|
||||
* @param params contains parameters for creating the device:
|
||||
* @param - "code": the code of the keyboard key to bind with the input
|
||||
* @param - "button": same as "code" but for controller buttons
|
||||
* @param - "toggle": press once to enable, press again to disable
|
||||
* @param - "inverted": inverts the output of the button
|
||||
* @param - "guid": text string for identifing controllers
|
||||
* @param - "port": port of the connected device
|
||||
* @param - "pad": slot of the connected controller
|
||||
* @return an unique input device with the parameters specified
|
||||
*/
|
||||
std::unique_ptr<Common::Input::InputDevice> CreateButtonDevice(
|
||||
const Common::ParamPackage& params);
|
||||
|
||||
/**
|
||||
* Creates a hat button device from the parameters given.
|
||||
* @param params contains parameters for creating the device:
|
||||
* @param - "button": the controller hat id to bind with the input
|
||||
* @param - "direction": the direction id to be detected
|
||||
* @param - "toggle": press once to enable, press again to disable
|
||||
* @param - "inverted": inverts the output of the button
|
||||
* @param - "guid": text string for identifing controllers
|
||||
* @param - "port": port of the connected device
|
||||
* @param - "pad": slot of the connected controller
|
||||
* @return an unique input device with the parameters specified
|
||||
*/
|
||||
std::unique_ptr<Common::Input::InputDevice> CreateHatButtonDevice(
|
||||
const Common::ParamPackage& params);
|
||||
|
||||
/**
|
||||
* Creates a stick device from the parameters given.
|
||||
* @param params contains parameters for creating the device:
|
||||
* @param - "axis_x": the controller horizontal axis id to bind with the input
|
||||
* @param - "axis_y": the controller vertical axis id to bind with the input
|
||||
* @param - "deadzone": the mimimum required value to be detected
|
||||
* @param - "range": the maximum value required to reach 100%
|
||||
* @param - "threshold": the mimimum required value to considered pressed
|
||||
* @param - "offset_x": the amount of offset in the x axis
|
||||
* @param - "offset_y": the amount of offset in the y axis
|
||||
* @param - "invert_x": inverts the sign of the horizontal axis
|
||||
* @param - "invert_y": inverts the sign of the vertical axis
|
||||
* @param - "guid": text string for identifing controllers
|
||||
* @param - "port": port of the connected device
|
||||
* @param - "pad": slot of the connected controller
|
||||
* @return an unique input device with the parameters specified
|
||||
*/
|
||||
std::unique_ptr<Common::Input::InputDevice> CreateStickDevice(
|
||||
const Common::ParamPackage& params);
|
||||
|
||||
/**
|
||||
* Creates an analog device from the parameters given.
|
||||
* @param params contains parameters for creating the device:
|
||||
* @param - "axis": the controller axis id to bind with the input
|
||||
* @param - "deadzone": the mimimum required value to be detected
|
||||
* @param - "range": the maximum value required to reach 100%
|
||||
* @param - "threshold": the mimimum required value to considered pressed
|
||||
* @param - "offset": the amount of offset in the axis
|
||||
* @param - "invert": inverts the sign of the axis
|
||||
* @param - "guid": text string for identifing controllers
|
||||
* @param - "port": port of the connected device
|
||||
* @param - "pad": slot of the connected controller
|
||||
* @return an unique input device with the parameters specified
|
||||
*/
|
||||
std::unique_ptr<Common::Input::InputDevice> CreateAnalogDevice(
|
||||
const Common::ParamPackage& params);
|
||||
|
||||
/**
|
||||
* Creates a trigger device from the parameters given.
|
||||
* @param params contains parameters for creating the device:
|
||||
* @param - "button": the controller hat id to bind with the input
|
||||
* @param - "direction": the direction id to be detected
|
||||
* @param - "toggle": press once to enable, press again to disable
|
||||
* @param - "inverted": inverts the output of the button
|
||||
* @param - "axis": the controller axis id to bind with the input
|
||||
* @param - "deadzone": the mimimum required value to be detected
|
||||
* @param - "range": the maximum value required to reach 100%
|
||||
* @param - "threshold": the mimimum required value to considered pressed
|
||||
* @param - "offset": the amount of offset in the axis
|
||||
* @param - "invert": inverts the sign of the axis
|
||||
* @param - "guid": text string for identifing controllers
|
||||
* @param - "port": port of the connected device
|
||||
* @param - "pad": slot of the connected controller
|
||||
* @return an unique input device with the parameters specified
|
||||
*/
|
||||
std::unique_ptr<Common::Input::InputDevice> CreateTriggerDevice(
|
||||
const Common::ParamPackage& params);
|
||||
|
||||
/**
|
||||
* Creates a touch device from the parameters given.
|
||||
* @param params contains parameters for creating the device:
|
||||
* @param - "button": the controller hat id to bind with the input
|
||||
* @param - "direction": the direction id to be detected
|
||||
* @param - "toggle": press once to enable, press again to disable
|
||||
* @param - "inverted": inverts the output of the button
|
||||
* @param - "axis_x": the controller horizontal axis id to bind with the input
|
||||
* @param - "axis_y": the controller vertical axis id to bind with the input
|
||||
* @param - "deadzone": the mimimum required value to be detected
|
||||
* @param - "range": the maximum value required to reach 100%
|
||||
* @param - "threshold": the mimimum required value to considered pressed
|
||||
* @param - "offset_x": the amount of offset in the x axis
|
||||
* @param - "offset_y": the amount of offset in the y axis
|
||||
* @param - "invert_x": inverts the sign of the horizontal axis
|
||||
* @param - "invert_y": inverts the sign of the vertical axis
|
||||
* @param - "guid": text string for identifing controllers
|
||||
* @param - "port": port of the connected device
|
||||
* @param - "pad": slot of the connected controller
|
||||
* @return an unique input device with the parameters specified
|
||||
*/
|
||||
std::unique_ptr<Common::Input::InputDevice> CreateTouchDevice(
|
||||
const Common::ParamPackage& params);
|
||||
|
||||
/**
|
||||
* Creates a battery device from the parameters given.
|
||||
* @param params contains parameters for creating the device:
|
||||
* @param - "guid": text string for identifing controllers
|
||||
* @param - "port": port of the connected device
|
||||
* @param - "pad": slot of the connected controller
|
||||
* @return an unique input device with the parameters specified
|
||||
*/
|
||||
std::unique_ptr<Common::Input::InputDevice> CreateBatteryDevice(
|
||||
const Common::ParamPackage& params);
|
||||
|
||||
/**
|
||||
* Creates a motion device from the parameters given.
|
||||
* @param params contains parameters for creating the device:
|
||||
* @param - "axis_x": the controller horizontal axis id to bind with the input
|
||||
* @param - "axis_y": the controller vertical axis id to bind with the input
|
||||
* @param - "axis_z": the controller fordward axis id to bind with the input
|
||||
* @param - "deadzone": the mimimum required value to be detected
|
||||
* @param - "range": the maximum value required to reach 100%
|
||||
* @param - "offset_x": the amount of offset in the x axis
|
||||
* @param - "offset_y": the amount of offset in the y axis
|
||||
* @param - "offset_z": the amount of offset in the z axis
|
||||
* @param - "invert_x": inverts the sign of the horizontal axis
|
||||
* @param - "invert_y": inverts the sign of the vertical axis
|
||||
* @param - "invert_z": inverts the sign of the fordward axis
|
||||
* @param - "guid": text string for identifing controllers
|
||||
* @param - "port": port of the connected device
|
||||
* @param - "pad": slot of the connected controller
|
||||
* @return an unique input device with the parameters specified
|
||||
*/
|
||||
std::unique_ptr<Common::Input::InputDevice> CreateMotionDevice(Common::ParamPackage params);
|
||||
|
||||
std::shared_ptr<InputEngine> input_engine;
|
||||
};
|
||||
} // namespace InputCommon
|
|
@ -4,146 +4,164 @@
|
|||
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include "common/input.h"
|
||||
#include "common/param_package.h"
|
||||
#include "common/settings.h"
|
||||
#include "input_common/analog_from_button.h"
|
||||
#include "input_common/gcadapter/gc_adapter.h"
|
||||
#include "input_common/gcadapter/gc_poller.h"
|
||||
#include "input_common/keyboard.h"
|
||||
#include "input_common/drivers/gc_adapter.h"
|
||||
#include "input_common/drivers/keyboard.h"
|
||||
#include "input_common/drivers/mouse.h"
|
||||
#include "input_common/drivers/tas_input.h"
|
||||
#include "input_common/drivers/touch_screen.h"
|
||||
#include "input_common/drivers/udp_client.h"
|
||||
#include "input_common/helpers/stick_from_buttons.h"
|
||||
#include "input_common/helpers/touch_from_buttons.h"
|
||||
#include "input_common/input_engine.h"
|
||||
#include "input_common/input_mapping.h"
|
||||
#include "input_common/input_poller.h"
|
||||
#include "input_common/main.h"
|
||||
#include "input_common/motion_from_button.h"
|
||||
#include "input_common/mouse/mouse_input.h"
|
||||
#include "input_common/mouse/mouse_poller.h"
|
||||
#include "input_common/tas/tas_input.h"
|
||||
#include "input_common/tas/tas_poller.h"
|
||||
#include "input_common/touch_from_button.h"
|
||||
#include "input_common/udp/client.h"
|
||||
#include "input_common/udp/udp.h"
|
||||
#ifdef HAVE_SDL2
|
||||
#include "input_common/sdl/sdl.h"
|
||||
#include "input_common/drivers/sdl_driver.h"
|
||||
#endif
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
struct InputSubsystem::Impl {
|
||||
void Initialize() {
|
||||
gcadapter = std::make_shared<GCAdapter::Adapter>();
|
||||
gcbuttons = std::make_shared<GCButtonFactory>(gcadapter);
|
||||
Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons);
|
||||
gcanalog = std::make_shared<GCAnalogFactory>(gcadapter);
|
||||
Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog);
|
||||
gcvibration = std::make_shared<GCVibrationFactory>(gcadapter);
|
||||
Input::RegisterFactory<Input::VibrationDevice>("gcpad", gcvibration);
|
||||
mapping_factory = std::make_shared<MappingFactory>();
|
||||
MappingCallback mapping_callback{[this](MappingData data) { RegisterInput(data); }};
|
||||
|
||||
keyboard = std::make_shared<Keyboard>();
|
||||
Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
|
||||
Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
|
||||
std::make_shared<AnalogFromButton>());
|
||||
Input::RegisterFactory<Input::MotionDevice>("keyboard",
|
||||
std::make_shared<MotionFromButton>());
|
||||
Input::RegisterFactory<Input::TouchDevice>("touch_from_button",
|
||||
std::make_shared<TouchFromButtonFactory>());
|
||||
keyboard = std::make_shared<Keyboard>("keyboard");
|
||||
keyboard->SetMappingCallback(mapping_callback);
|
||||
keyboard_factory = std::make_shared<InputFactory>(keyboard);
|
||||
keyboard_output_factory = std::make_shared<OutputFactory>(keyboard);
|
||||
Common::Input::RegisterFactory<Common::Input::InputDevice>(keyboard->GetEngineName(),
|
||||
keyboard_factory);
|
||||
Common::Input::RegisterFactory<Common::Input::OutputDevice>(keyboard->GetEngineName(),
|
||||
keyboard_output_factory);
|
||||
|
||||
mouse = std::make_shared<Mouse>("mouse");
|
||||
mouse->SetMappingCallback(mapping_callback);
|
||||
mouse_factory = std::make_shared<InputFactory>(mouse);
|
||||
mouse_output_factory = std::make_shared<OutputFactory>(mouse);
|
||||
Common::Input::RegisterFactory<Common::Input::InputDevice>(mouse->GetEngineName(),
|
||||
mouse_factory);
|
||||
Common::Input::RegisterFactory<Common::Input::OutputDevice>(mouse->GetEngineName(),
|
||||
mouse_output_factory);
|
||||
|
||||
touch_screen = std::make_shared<TouchScreen>("touch");
|
||||
touch_screen_factory = std::make_shared<InputFactory>(touch_screen);
|
||||
Common::Input::RegisterFactory<Common::Input::InputDevice>(touch_screen->GetEngineName(),
|
||||
touch_screen_factory);
|
||||
|
||||
gcadapter = std::make_shared<GCAdapter>("gcpad");
|
||||
gcadapter->SetMappingCallback(mapping_callback);
|
||||
gcadapter_input_factory = std::make_shared<InputFactory>(gcadapter);
|
||||
gcadapter_output_factory = std::make_shared<OutputFactory>(gcadapter);
|
||||
Common::Input::RegisterFactory<Common::Input::InputDevice>(gcadapter->GetEngineName(),
|
||||
gcadapter_input_factory);
|
||||
Common::Input::RegisterFactory<Common::Input::OutputDevice>(gcadapter->GetEngineName(),
|
||||
gcadapter_output_factory);
|
||||
|
||||
udp_client = std::make_shared<CemuhookUDP::UDPClient>("cemuhookudp");
|
||||
udp_client->SetMappingCallback(mapping_callback);
|
||||
udp_client_factory = std::make_shared<InputFactory>(udp_client);
|
||||
Common::Input::RegisterFactory<Common::Input::InputDevice>(udp_client->GetEngineName(),
|
||||
udp_client_factory);
|
||||
|
||||
tas_input = std::make_shared<TasInput::Tas>("tas");
|
||||
tas_input->SetMappingCallback(mapping_callback);
|
||||
tas_input_factory = std::make_shared<InputFactory>(tas_input);
|
||||
tas_output_factory = std::make_shared<OutputFactory>(tas_input);
|
||||
Common::Input::RegisterFactory<Common::Input::InputDevice>(tas_input->GetEngineName(),
|
||||
tas_input_factory);
|
||||
Common::Input::RegisterFactory<Common::Input::OutputDevice>(tas_input->GetEngineName(),
|
||||
tas_output_factory);
|
||||
|
||||
#ifdef HAVE_SDL2
|
||||
sdl = SDL::Init();
|
||||
sdl = std::make_shared<SDLDriver>("sdl");
|
||||
sdl->SetMappingCallback(mapping_callback);
|
||||
sdl_input_factory = std::make_shared<InputFactory>(sdl);
|
||||
sdl_output_factory = std::make_shared<OutputFactory>(sdl);
|
||||
Common::Input::RegisterFactory<Common::Input::InputDevice>(sdl->GetEngineName(),
|
||||
sdl_input_factory);
|
||||
Common::Input::RegisterFactory<Common::Input::OutputDevice>(sdl->GetEngineName(),
|
||||
sdl_output_factory);
|
||||
#endif
|
||||
|
||||
udp = std::make_shared<InputCommon::CemuhookUDP::Client>();
|
||||
udpmotion = std::make_shared<UDPMotionFactory>(udp);
|
||||
Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", udpmotion);
|
||||
udptouch = std::make_shared<UDPTouchFactory>(udp);
|
||||
Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", udptouch);
|
||||
|
||||
mouse = std::make_shared<MouseInput::Mouse>();
|
||||
mousebuttons = std::make_shared<MouseButtonFactory>(mouse);
|
||||
Input::RegisterFactory<Input::ButtonDevice>("mouse", mousebuttons);
|
||||
mouseanalog = std::make_shared<MouseAnalogFactory>(mouse);
|
||||
Input::RegisterFactory<Input::AnalogDevice>("mouse", mouseanalog);
|
||||
mousemotion = std::make_shared<MouseMotionFactory>(mouse);
|
||||
Input::RegisterFactory<Input::MotionDevice>("mouse", mousemotion);
|
||||
mousetouch = std::make_shared<MouseTouchFactory>(mouse);
|
||||
Input::RegisterFactory<Input::TouchDevice>("mouse", mousetouch);
|
||||
|
||||
tas = std::make_shared<TasInput::Tas>();
|
||||
tasbuttons = std::make_shared<TasButtonFactory>(tas);
|
||||
Input::RegisterFactory<Input::ButtonDevice>("tas", tasbuttons);
|
||||
tasanalog = std::make_shared<TasAnalogFactory>(tas);
|
||||
Input::RegisterFactory<Input::AnalogDevice>("tas", tasanalog);
|
||||
Common::Input::RegisterFactory<Common::Input::InputDevice>(
|
||||
"touch_from_button", std::make_shared<TouchFromButton>());
|
||||
Common::Input::RegisterFactory<Common::Input::InputDevice>(
|
||||
"analog_from_button", std::make_shared<StickFromButton>());
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
Input::UnregisterFactory<Input::ButtonDevice>("keyboard");
|
||||
Input::UnregisterFactory<Input::MotionDevice>("keyboard");
|
||||
Common::Input::UnregisterFactory<Common::Input::InputDevice>(keyboard->GetEngineName());
|
||||
Common::Input::UnregisterFactory<Common::Input::OutputDevice>(keyboard->GetEngineName());
|
||||
keyboard.reset();
|
||||
Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
|
||||
Input::UnregisterFactory<Input::TouchDevice>("touch_from_button");
|
||||
|
||||
Common::Input::UnregisterFactory<Common::Input::InputDevice>(mouse->GetEngineName());
|
||||
Common::Input::UnregisterFactory<Common::Input::OutputDevice>(mouse->GetEngineName());
|
||||
mouse.reset();
|
||||
|
||||
Common::Input::UnregisterFactory<Common::Input::InputDevice>(touch_screen->GetEngineName());
|
||||
touch_screen.reset();
|
||||
|
||||
Common::Input::UnregisterFactory<Common::Input::InputDevice>(gcadapter->GetEngineName());
|
||||
Common::Input::UnregisterFactory<Common::Input::OutputDevice>(gcadapter->GetEngineName());
|
||||
gcadapter.reset();
|
||||
|
||||
Common::Input::UnregisterFactory<Common::Input::InputDevice>(udp_client->GetEngineName());
|
||||
udp_client.reset();
|
||||
|
||||
Common::Input::UnregisterFactory<Common::Input::InputDevice>(tas_input->GetEngineName());
|
||||
Common::Input::UnregisterFactory<Common::Input::OutputDevice>(tas_input->GetEngineName());
|
||||
tas_input.reset();
|
||||
|
||||
#ifdef HAVE_SDL2
|
||||
Common::Input::UnregisterFactory<Common::Input::InputDevice>(sdl->GetEngineName());
|
||||
Common::Input::UnregisterFactory<Common::Input::OutputDevice>(sdl->GetEngineName());
|
||||
sdl.reset();
|
||||
#endif
|
||||
Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
|
||||
Input::UnregisterFactory<Input::AnalogDevice>("gcpad");
|
||||
Input::UnregisterFactory<Input::VibrationDevice>("gcpad");
|
||||
|
||||
gcbuttons.reset();
|
||||
gcanalog.reset();
|
||||
gcvibration.reset();
|
||||
|
||||
Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
|
||||
Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
|
||||
|
||||
udpmotion.reset();
|
||||
udptouch.reset();
|
||||
|
||||
Input::UnregisterFactory<Input::ButtonDevice>("mouse");
|
||||
Input::UnregisterFactory<Input::AnalogDevice>("mouse");
|
||||
Input::UnregisterFactory<Input::MotionDevice>("mouse");
|
||||
Input::UnregisterFactory<Input::TouchDevice>("mouse");
|
||||
|
||||
mousebuttons.reset();
|
||||
mouseanalog.reset();
|
||||
mousemotion.reset();
|
||||
mousetouch.reset();
|
||||
|
||||
Input::UnregisterFactory<Input::ButtonDevice>("tas");
|
||||
Input::UnregisterFactory<Input::AnalogDevice>("tas");
|
||||
|
||||
tasbuttons.reset();
|
||||
tasanalog.reset();
|
||||
Common::Input::UnregisterFactory<Common::Input::InputDevice>("touch_from_button");
|
||||
Common::Input::UnregisterFactory<Common::Input::InputDevice>("analog_from_button");
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
|
||||
std::vector<Common::ParamPackage> devices = {
|
||||
Common::ParamPackage{{"display", "Any"}, {"class", "any"}},
|
||||
Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}},
|
||||
Common::ParamPackage{{"display", "Any"}, {"engine", "any"}},
|
||||
};
|
||||
if (Settings::values.tas_enable) {
|
||||
devices.emplace_back(
|
||||
Common::ParamPackage{{"display", "TAS Controller"}, {"class", "tas"}});
|
||||
}
|
||||
|
||||
auto keyboard_devices = keyboard->GetInputDevices();
|
||||
devices.insert(devices.end(), keyboard_devices.begin(), keyboard_devices.end());
|
||||
auto mouse_devices = mouse->GetInputDevices();
|
||||
devices.insert(devices.end(), mouse_devices.begin(), mouse_devices.end());
|
||||
auto gcadapter_devices = gcadapter->GetInputDevices();
|
||||
devices.insert(devices.end(), gcadapter_devices.begin(), gcadapter_devices.end());
|
||||
#ifdef HAVE_SDL2
|
||||
auto sdl_devices = sdl->GetInputDevices();
|
||||
devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());
|
||||
#endif
|
||||
auto udp_devices = udp->GetInputDevices();
|
||||
devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
|
||||
auto gcpad_devices = gcadapter->GetInputDevices();
|
||||
devices.insert(devices.end(), gcpad_devices.begin(), gcpad_devices.end());
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
[[nodiscard]] AnalogMapping GetAnalogMappingForDevice(
|
||||
const Common::ParamPackage& params) const {
|
||||
if (!params.Has("class") || params.Get("class", "") == "any") {
|
||||
if (!params.Has("engine") || params.Get("engine", "") == "any") {
|
||||
return {};
|
||||
}
|
||||
if (params.Get("class", "") == "gcpad") {
|
||||
const std::string engine = params.Get("engine", "");
|
||||
if (engine == mouse->GetEngineName()) {
|
||||
return mouse->GetAnalogMappingForDevice(params);
|
||||
}
|
||||
if (engine == gcadapter->GetEngineName()) {
|
||||
return gcadapter->GetAnalogMappingForDevice(params);
|
||||
}
|
||||
if (params.Get("class", "") == "tas") {
|
||||
return tas->GetAnalogMappingForDevice(params);
|
||||
if (engine == tas_input->GetEngineName()) {
|
||||
return tas_input->GetAnalogMappingForDevice(params);
|
||||
}
|
||||
#ifdef HAVE_SDL2
|
||||
if (params.Get("class", "") == "sdl") {
|
||||
if (engine == sdl->GetEngineName()) {
|
||||
return sdl->GetAnalogMappingForDevice(params);
|
||||
}
|
||||
#endif
|
||||
|
@ -152,17 +170,18 @@ struct InputSubsystem::Impl {
|
|||
|
||||
[[nodiscard]] ButtonMapping GetButtonMappingForDevice(
|
||||
const Common::ParamPackage& params) const {
|
||||
if (!params.Has("class") || params.Get("class", "") == "any") {
|
||||
if (!params.Has("engine") || params.Get("engine", "") == "any") {
|
||||
return {};
|
||||
}
|
||||
if (params.Get("class", "") == "gcpad") {
|
||||
const std::string engine = params.Get("engine", "");
|
||||
if (engine == gcadapter->GetEngineName()) {
|
||||
return gcadapter->GetButtonMappingForDevice(params);
|
||||
}
|
||||
if (params.Get("class", "") == "tas") {
|
||||
return tas->GetButtonMappingForDevice(params);
|
||||
if (engine == tas_input->GetEngineName()) {
|
||||
return tas_input->GetButtonMappingForDevice(params);
|
||||
}
|
||||
#ifdef HAVE_SDL2
|
||||
if (params.Get("class", "") == "sdl") {
|
||||
if (engine == sdl->GetEngineName()) {
|
||||
return sdl->GetButtonMappingForDevice(params);
|
||||
}
|
||||
#endif
|
||||
|
@ -171,40 +190,115 @@ struct InputSubsystem::Impl {
|
|||
|
||||
[[nodiscard]] MotionMapping GetMotionMappingForDevice(
|
||||
const Common::ParamPackage& params) const {
|
||||
if (!params.Has("class") || params.Get("class", "") == "any") {
|
||||
if (!params.Has("engine") || params.Get("engine", "") == "any") {
|
||||
return {};
|
||||
}
|
||||
if (params.Get("class", "") == "cemuhookudp") {
|
||||
// TODO return the correct motion device
|
||||
return {};
|
||||
const std::string engine = params.Get("engine", "");
|
||||
if (engine == gcadapter->GetEngineName()) {
|
||||
return gcadapter->GetMotionMappingForDevice(params);
|
||||
}
|
||||
#ifdef HAVE_SDL2
|
||||
if (params.Get("class", "") == "sdl") {
|
||||
if (engine == sdl->GetEngineName()) {
|
||||
return sdl->GetMotionMappingForDevice(params);
|
||||
}
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
|
||||
std::shared_ptr<Keyboard> keyboard;
|
||||
std::string GetButtonName(const Common::ParamPackage& params) const {
|
||||
if (!params.Has("engine") || params.Get("engine", "") == "any") {
|
||||
return "Unknown";
|
||||
}
|
||||
const std::string engine = params.Get("engine", "");
|
||||
if (engine == mouse->GetEngineName()) {
|
||||
return mouse->GetUIName(params);
|
||||
}
|
||||
if (engine == gcadapter->GetEngineName()) {
|
||||
return gcadapter->GetUIName(params);
|
||||
}
|
||||
if (engine == udp_client->GetEngineName()) {
|
||||
return udp_client->GetUIName(params);
|
||||
}
|
||||
if (engine == tas_input->GetEngineName()) {
|
||||
return tas_input->GetUIName(params);
|
||||
}
|
||||
#ifdef HAVE_SDL2
|
||||
std::unique_ptr<SDL::State> sdl;
|
||||
if (engine == sdl->GetEngineName()) {
|
||||
return sdl->GetUIName(params);
|
||||
}
|
||||
#endif
|
||||
return "Bad engine";
|
||||
}
|
||||
|
||||
bool IsController(const Common::ParamPackage& params) {
|
||||
const std::string engine = params.Get("engine", "");
|
||||
if (engine == mouse->GetEngineName()) {
|
||||
return true;
|
||||
}
|
||||
if (engine == gcadapter->GetEngineName()) {
|
||||
return true;
|
||||
}
|
||||
if (engine == tas_input->GetEngineName()) {
|
||||
return true;
|
||||
}
|
||||
#ifdef HAVE_SDL2
|
||||
if (engine == sdl->GetEngineName()) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
void BeginConfiguration() {
|
||||
keyboard->BeginConfiguration();
|
||||
mouse->BeginConfiguration();
|
||||
gcadapter->BeginConfiguration();
|
||||
udp_client->BeginConfiguration();
|
||||
#ifdef HAVE_SDL2
|
||||
sdl->BeginConfiguration();
|
||||
#endif
|
||||
}
|
||||
|
||||
void EndConfiguration() {
|
||||
keyboard->EndConfiguration();
|
||||
mouse->EndConfiguration();
|
||||
gcadapter->EndConfiguration();
|
||||
udp_client->EndConfiguration();
|
||||
#ifdef HAVE_SDL2
|
||||
sdl->EndConfiguration();
|
||||
#endif
|
||||
}
|
||||
|
||||
void RegisterInput(MappingData data) {
|
||||
mapping_factory->RegisterInput(data);
|
||||
}
|
||||
|
||||
std::shared_ptr<MappingFactory> mapping_factory;
|
||||
|
||||
std::shared_ptr<Keyboard> keyboard;
|
||||
std::shared_ptr<Mouse> mouse;
|
||||
std::shared_ptr<GCAdapter> gcadapter;
|
||||
std::shared_ptr<TouchScreen> touch_screen;
|
||||
std::shared_ptr<TasInput::Tas> tas_input;
|
||||
std::shared_ptr<CemuhookUDP::UDPClient> udp_client;
|
||||
|
||||
std::shared_ptr<InputFactory> keyboard_factory;
|
||||
std::shared_ptr<InputFactory> mouse_factory;
|
||||
std::shared_ptr<InputFactory> gcadapter_input_factory;
|
||||
std::shared_ptr<InputFactory> touch_screen_factory;
|
||||
std::shared_ptr<InputFactory> udp_client_factory;
|
||||
std::shared_ptr<InputFactory> tas_input_factory;
|
||||
|
||||
std::shared_ptr<OutputFactory> keyboard_output_factory;
|
||||
std::shared_ptr<OutputFactory> mouse_output_factory;
|
||||
std::shared_ptr<OutputFactory> gcadapter_output_factory;
|
||||
std::shared_ptr<OutputFactory> tas_output_factory;
|
||||
|
||||
#ifdef HAVE_SDL2
|
||||
std::shared_ptr<SDLDriver> sdl;
|
||||
std::shared_ptr<InputFactory> sdl_input_factory;
|
||||
std::shared_ptr<OutputFactory> sdl_output_factory;
|
||||
#endif
|
||||
std::shared_ptr<GCButtonFactory> gcbuttons;
|
||||
std::shared_ptr<GCAnalogFactory> gcanalog;
|
||||
std::shared_ptr<GCVibrationFactory> gcvibration;
|
||||
std::shared_ptr<UDPMotionFactory> udpmotion;
|
||||
std::shared_ptr<UDPTouchFactory> udptouch;
|
||||
std::shared_ptr<MouseButtonFactory> mousebuttons;
|
||||
std::shared_ptr<MouseAnalogFactory> mouseanalog;
|
||||
std::shared_ptr<MouseMotionFactory> mousemotion;
|
||||
std::shared_ptr<MouseTouchFactory> mousetouch;
|
||||
std::shared_ptr<TasButtonFactory> tasbuttons;
|
||||
std::shared_ptr<TasAnalogFactory> tasanalog;
|
||||
std::shared_ptr<CemuhookUDP::Client> udp;
|
||||
std::shared_ptr<GCAdapter::Adapter> gcadapter;
|
||||
std::shared_ptr<MouseInput::Mouse> mouse;
|
||||
std::shared_ptr<TasInput::Tas> tas;
|
||||
};
|
||||
|
||||
InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {}
|
||||
|
@ -227,20 +321,28 @@ const Keyboard* InputSubsystem::GetKeyboard() const {
|
|||
return impl->keyboard.get();
|
||||
}
|
||||
|
||||
MouseInput::Mouse* InputSubsystem::GetMouse() {
|
||||
Mouse* InputSubsystem::GetMouse() {
|
||||
return impl->mouse.get();
|
||||
}
|
||||
|
||||
const MouseInput::Mouse* InputSubsystem::GetMouse() const {
|
||||
const Mouse* InputSubsystem::GetMouse() const {
|
||||
return impl->mouse.get();
|
||||
}
|
||||
|
||||
TouchScreen* InputSubsystem::GetTouchScreen() {
|
||||
return impl->touch_screen.get();
|
||||
}
|
||||
|
||||
const TouchScreen* InputSubsystem::GetTouchScreen() const {
|
||||
return impl->touch_screen.get();
|
||||
}
|
||||
|
||||
TasInput::Tas* InputSubsystem::GetTas() {
|
||||
return impl->tas.get();
|
||||
return impl->tas_input.get();
|
||||
}
|
||||
|
||||
const TasInput::Tas* InputSubsystem::GetTas() const {
|
||||
return impl->tas.get();
|
||||
return impl->tas_input.get();
|
||||
}
|
||||
|
||||
std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const {
|
||||
|
@ -259,100 +361,37 @@ MotionMapping InputSubsystem::GetMotionMappingForDevice(const Common::ParamPacka
|
|||
return impl->GetMotionMappingForDevice(device);
|
||||
}
|
||||
|
||||
GCAnalogFactory* InputSubsystem::GetGCAnalogs() {
|
||||
return impl->gcanalog.get();
|
||||
std::string InputSubsystem::GetButtonName(const Common::ParamPackage& params) const {
|
||||
const std::string toggle = params.Get("toggle", false) ? "~" : "";
|
||||
const std::string inverted = params.Get("inverted", false) ? "!" : "";
|
||||
const std::string button_name = impl->GetButtonName(params);
|
||||
std::string axis_direction = "";
|
||||
if (params.Has("axis")) {
|
||||
axis_direction = params.Get("invert", "+");
|
||||
}
|
||||
return fmt::format("{}{}{}{}", toggle, inverted, button_name, axis_direction);
|
||||
}
|
||||
|
||||
const GCAnalogFactory* InputSubsystem::GetGCAnalogs() const {
|
||||
return impl->gcanalog.get();
|
||||
}
|
||||
|
||||
GCButtonFactory* InputSubsystem::GetGCButtons() {
|
||||
return impl->gcbuttons.get();
|
||||
}
|
||||
|
||||
const GCButtonFactory* InputSubsystem::GetGCButtons() const {
|
||||
return impl->gcbuttons.get();
|
||||
}
|
||||
|
||||
UDPMotionFactory* InputSubsystem::GetUDPMotions() {
|
||||
return impl->udpmotion.get();
|
||||
}
|
||||
|
||||
const UDPMotionFactory* InputSubsystem::GetUDPMotions() const {
|
||||
return impl->udpmotion.get();
|
||||
}
|
||||
|
||||
UDPTouchFactory* InputSubsystem::GetUDPTouch() {
|
||||
return impl->udptouch.get();
|
||||
}
|
||||
|
||||
const UDPTouchFactory* InputSubsystem::GetUDPTouch() const {
|
||||
return impl->udptouch.get();
|
||||
}
|
||||
|
||||
MouseButtonFactory* InputSubsystem::GetMouseButtons() {
|
||||
return impl->mousebuttons.get();
|
||||
}
|
||||
|
||||
const MouseButtonFactory* InputSubsystem::GetMouseButtons() const {
|
||||
return impl->mousebuttons.get();
|
||||
}
|
||||
|
||||
MouseAnalogFactory* InputSubsystem::GetMouseAnalogs() {
|
||||
return impl->mouseanalog.get();
|
||||
}
|
||||
|
||||
const MouseAnalogFactory* InputSubsystem::GetMouseAnalogs() const {
|
||||
return impl->mouseanalog.get();
|
||||
}
|
||||
|
||||
MouseMotionFactory* InputSubsystem::GetMouseMotions() {
|
||||
return impl->mousemotion.get();
|
||||
}
|
||||
|
||||
const MouseMotionFactory* InputSubsystem::GetMouseMotions() const {
|
||||
return impl->mousemotion.get();
|
||||
}
|
||||
|
||||
MouseTouchFactory* InputSubsystem::GetMouseTouch() {
|
||||
return impl->mousetouch.get();
|
||||
}
|
||||
|
||||
const MouseTouchFactory* InputSubsystem::GetMouseTouch() const {
|
||||
return impl->mousetouch.get();
|
||||
}
|
||||
|
||||
TasButtonFactory* InputSubsystem::GetTasButtons() {
|
||||
return impl->tasbuttons.get();
|
||||
}
|
||||
|
||||
const TasButtonFactory* InputSubsystem::GetTasButtons() const {
|
||||
return impl->tasbuttons.get();
|
||||
}
|
||||
|
||||
TasAnalogFactory* InputSubsystem::GetTasAnalogs() {
|
||||
return impl->tasanalog.get();
|
||||
}
|
||||
|
||||
const TasAnalogFactory* InputSubsystem::GetTasAnalogs() const {
|
||||
return impl->tasanalog.get();
|
||||
bool InputSubsystem::IsController(const Common::ParamPackage& params) const {
|
||||
return impl->IsController(params);
|
||||
}
|
||||
|
||||
void InputSubsystem::ReloadInputDevices() {
|
||||
if (!impl->udp) {
|
||||
return;
|
||||
}
|
||||
impl->udp->ReloadSockets();
|
||||
impl->udp_client.get()->ReloadSockets();
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers(
|
||||
[[maybe_unused]] Polling::DeviceType type) const {
|
||||
#ifdef HAVE_SDL2
|
||||
return impl->sdl->GetPollers(type);
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
void InputSubsystem::BeginMapping(Polling::InputType type) {
|
||||
impl->BeginConfiguration();
|
||||
impl->mapping_factory->BeginMapping(type);
|
||||
}
|
||||
|
||||
const Common::ParamPackage InputSubsystem::GetNextInput() const {
|
||||
return impl->mapping_factory->GetNextInput();
|
||||
}
|
||||
|
||||
void InputSubsystem::StopMapping() const {
|
||||
impl->EndConfiguration();
|
||||
impl->mapping_factory->StopMapping();
|
||||
}
|
||||
|
||||
std::string GenerateKeyboardParam(int key_code) {
|
||||
|
|
|
@ -25,56 +25,26 @@ namespace Settings::NativeMotion {
|
|||
enum Values : int;
|
||||
}
|
||||
|
||||
namespace MouseInput {
|
||||
namespace InputCommon {
|
||||
class Keyboard;
|
||||
class Mouse;
|
||||
}
|
||||
class TouchScreen;
|
||||
struct MappingData;
|
||||
} // namespace InputCommon
|
||||
|
||||
namespace TasInput {
|
||||
namespace InputCommon::TasInput {
|
||||
class Tas;
|
||||
}
|
||||
} // namespace InputCommon::TasInput
|
||||
|
||||
namespace InputCommon {
|
||||
namespace Polling {
|
||||
|
||||
enum class DeviceType { Button, AnalogPreferred, Motion };
|
||||
|
||||
/**
|
||||
* A class that can be used to get inputs from an input device like controllers without having to
|
||||
* poll the device's status yourself
|
||||
*/
|
||||
class DevicePoller {
|
||||
public:
|
||||
virtual ~DevicePoller() = default;
|
||||
/// Setup and start polling for inputs, should be called before GetNextInput
|
||||
/// If a device_id is provided, events should be filtered to only include events from this
|
||||
/// device id
|
||||
virtual void Start(const std::string& device_id = "") = 0;
|
||||
/// Stop polling
|
||||
virtual void Stop() = 0;
|
||||
/**
|
||||
* Every call to this function returns the next input recorded since calling Start
|
||||
* @return A ParamPackage of the recorded input, which can be used to create an InputDevice.
|
||||
* If there has been no input, the package is empty
|
||||
*/
|
||||
virtual Common::ParamPackage GetNextInput() = 0;
|
||||
};
|
||||
/// Type of input desired for mapping purposes
|
||||
enum class InputType { None, Button, Stick, Motion, Touch };
|
||||
} // namespace Polling
|
||||
|
||||
class GCAnalogFactory;
|
||||
class GCButtonFactory;
|
||||
class UDPMotionFactory;
|
||||
class UDPTouchFactory;
|
||||
class MouseButtonFactory;
|
||||
class MouseAnalogFactory;
|
||||
class MouseMotionFactory;
|
||||
class MouseTouchFactory;
|
||||
class TasButtonFactory;
|
||||
class TasAnalogFactory;
|
||||
class Keyboard;
|
||||
|
||||
/**
|
||||
* Given a ParamPackage for a Device returned from `GetInputDevices`, attempt to get the default
|
||||
* mapping for the device. This is currently only implemented for the SDL backend devices.
|
||||
* mapping for the device.
|
||||
*/
|
||||
using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>;
|
||||
using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>;
|
||||
|
@ -104,20 +74,27 @@ public:
|
|||
[[nodiscard]] const Keyboard* GetKeyboard() const;
|
||||
|
||||
/// Retrieves the underlying mouse device.
|
||||
[[nodiscard]] MouseInput::Mouse* GetMouse();
|
||||
[[nodiscard]] Mouse* GetMouse();
|
||||
|
||||
/// Retrieves the underlying mouse device.
|
||||
[[nodiscard]] const MouseInput::Mouse* GetMouse() const;
|
||||
[[nodiscard]] const Mouse* GetMouse() const;
|
||||
|
||||
/// Retrieves the underlying tas device.
|
||||
/// Retrieves the underlying touch screen device.
|
||||
[[nodiscard]] TouchScreen* GetTouchScreen();
|
||||
|
||||
/// Retrieves the underlying touch screen device.
|
||||
[[nodiscard]] const TouchScreen* GetTouchScreen() const;
|
||||
|
||||
/// Retrieves the underlying tas input device.
|
||||
[[nodiscard]] TasInput::Tas* GetTas();
|
||||
|
||||
/// Retrieves the underlying tas device.
|
||||
/// Retrieves the underlying tas input device.
|
||||
[[nodiscard]] const TasInput::Tas* GetTas() const;
|
||||
|
||||
/**
|
||||
* Returns all available input devices that this Factory can create a new device with.
|
||||
* Each returned ParamPackage should have a `display` field used for display, a class field for
|
||||
* backends to determine if this backend is meant to service the request and any other
|
||||
* Each returned ParamPackage should have a `display` field used for display, a `engine` field
|
||||
* for backends to determine if this backend is meant to service the request and any other
|
||||
* information needed to identify this in the backend later.
|
||||
*/
|
||||
[[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const;
|
||||
|
@ -131,83 +108,33 @@ public:
|
|||
/// Retrieves the motion mappings for the given device.
|
||||
[[nodiscard]] MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& device) const;
|
||||
|
||||
/// Retrieves the underlying GameCube analog handler.
|
||||
[[nodiscard]] GCAnalogFactory* GetGCAnalogs();
|
||||
/// Returns a string contaning the name of the button from the input engine.
|
||||
[[nodiscard]] std::string GetButtonName(const Common::ParamPackage& params) const;
|
||||
|
||||
/// Retrieves the underlying GameCube analog handler.
|
||||
[[nodiscard]] const GCAnalogFactory* GetGCAnalogs() const;
|
||||
/// Returns true if device is a controller.
|
||||
[[nodiscard]] bool IsController(const Common::ParamPackage& params) const;
|
||||
|
||||
/// Retrieves the underlying GameCube button handler.
|
||||
[[nodiscard]] GCButtonFactory* GetGCButtons();
|
||||
|
||||
/// Retrieves the underlying GameCube button handler.
|
||||
[[nodiscard]] const GCButtonFactory* GetGCButtons() const;
|
||||
|
||||
/// Retrieves the underlying udp motion handler.
|
||||
[[nodiscard]] UDPMotionFactory* GetUDPMotions();
|
||||
|
||||
/// Retrieves the underlying udp motion handler.
|
||||
[[nodiscard]] const UDPMotionFactory* GetUDPMotions() const;
|
||||
|
||||
/// Retrieves the underlying udp touch handler.
|
||||
[[nodiscard]] UDPTouchFactory* GetUDPTouch();
|
||||
|
||||
/// Retrieves the underlying udp touch handler.
|
||||
[[nodiscard]] const UDPTouchFactory* GetUDPTouch() const;
|
||||
|
||||
/// Retrieves the underlying mouse button handler.
|
||||
[[nodiscard]] MouseButtonFactory* GetMouseButtons();
|
||||
|
||||
/// Retrieves the underlying mouse button handler.
|
||||
[[nodiscard]] const MouseButtonFactory* GetMouseButtons() const;
|
||||
|
||||
/// Retrieves the underlying mouse analog handler.
|
||||
[[nodiscard]] MouseAnalogFactory* GetMouseAnalogs();
|
||||
|
||||
/// Retrieves the underlying mouse analog handler.
|
||||
[[nodiscard]] const MouseAnalogFactory* GetMouseAnalogs() const;
|
||||
|
||||
/// Retrieves the underlying mouse motion handler.
|
||||
[[nodiscard]] MouseMotionFactory* GetMouseMotions();
|
||||
|
||||
/// Retrieves the underlying mouse motion handler.
|
||||
[[nodiscard]] const MouseMotionFactory* GetMouseMotions() const;
|
||||
|
||||
/// Retrieves the underlying mouse touch handler.
|
||||
[[nodiscard]] MouseTouchFactory* GetMouseTouch();
|
||||
|
||||
/// Retrieves the underlying mouse touch handler.
|
||||
[[nodiscard]] const MouseTouchFactory* GetMouseTouch() const;
|
||||
|
||||
/// Retrieves the underlying tas button handler.
|
||||
[[nodiscard]] TasButtonFactory* GetTasButtons();
|
||||
|
||||
/// Retrieves the underlying tas button handler.
|
||||
[[nodiscard]] const TasButtonFactory* GetTasButtons() const;
|
||||
|
||||
/// Retrieves the underlying tas analogs handler.
|
||||
[[nodiscard]] TasAnalogFactory* GetTasAnalogs();
|
||||
|
||||
/// Retrieves the underlying tas analogs handler.
|
||||
[[nodiscard]] const TasAnalogFactory* GetTasAnalogs() const;
|
||||
|
||||
/// Reloads the input devices
|
||||
/// Reloads the input devices.
|
||||
void ReloadInputDevices();
|
||||
|
||||
/// Get all DevicePoller from all backends for a specific device type
|
||||
[[nodiscard]] std::vector<std::unique_ptr<Polling::DevicePoller>> GetPollers(
|
||||
Polling::DeviceType type) const;
|
||||
/// Start polling from all backends for a desired input type.
|
||||
void BeginMapping(Polling::InputType type);
|
||||
|
||||
/// Returns an input event with mapping information.
|
||||
[[nodiscard]] const Common::ParamPackage GetNextInput() const;
|
||||
|
||||
/// Stop polling from all backends.
|
||||
void StopMapping() const;
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
/// Generates a serialized param package for creating a keyboard button device
|
||||
/// Generates a serialized param package for creating a keyboard button device.
|
||||
std::string GenerateKeyboardParam(int key_code);
|
||||
|
||||
/// Generates a serialized param package for creating an analog device taking input from keyboard
|
||||
/// Generates a serialized param package for creating an analog device taking input from keyboard.
|
||||
std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
|
||||
int key_modifier, float modifier_scale);
|
||||
|
||||
} // namespace InputCommon
|
||||
|
|
|
@ -6,8 +6,12 @@
|
|||
#include <thread>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/param_package.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hid/emulated_controller.h"
|
||||
#include "core/hid/hid_core.h"
|
||||
#include "core/hid/hid_types.h"
|
||||
#include "core/hle/lock.h"
|
||||
#include "core/hle/service/hid/controllers/npad.h"
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
|
@ -23,49 +27,32 @@
|
|||
|
||||
namespace {
|
||||
|
||||
constexpr std::size_t HANDHELD_INDEX = 8;
|
||||
|
||||
constexpr std::array<std::array<bool, 4>, 8> led_patterns{{
|
||||
{true, false, false, false},
|
||||
{true, true, false, false},
|
||||
{true, true, true, false},
|
||||
{true, true, true, true},
|
||||
{true, false, false, true},
|
||||
{true, false, true, false},
|
||||
{true, false, true, true},
|
||||
{false, true, true, false},
|
||||
}};
|
||||
|
||||
void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index,
|
||||
bool connected, Core::System& system) {
|
||||
if (!system.IsPoweredOn()) {
|
||||
return;
|
||||
void UpdateController(Core::HID::EmulatedController* controller,
|
||||
Core::HID::NpadType controller_type, bool connected) {
|
||||
if (controller->IsConnected()) {
|
||||
controller->Disconnect();
|
||||
}
|
||||
controller->SetNpadType(controller_type);
|
||||
if (connected) {
|
||||
controller->Connect();
|
||||
}
|
||||
|
||||
auto& npad =
|
||||
system.ServiceManager()
|
||||
.GetService<Service::HID::Hid>("hid")
|
||||
->GetAppletResource()
|
||||
->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
|
||||
|
||||
npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected);
|
||||
}
|
||||
|
||||
// Returns true if the given controller type is compatible with the given parameters.
|
||||
bool IsControllerCompatible(Settings::ControllerType controller_type,
|
||||
bool IsControllerCompatible(Core::HID::NpadType controller_type,
|
||||
Core::Frontend::ControllerParameters parameters) {
|
||||
switch (controller_type) {
|
||||
case Settings::ControllerType::ProController:
|
||||
case Core::HID::NpadType::ProController:
|
||||
return parameters.allow_pro_controller;
|
||||
case Settings::ControllerType::DualJoyconDetached:
|
||||
case Core::HID::NpadType::JoyconDual:
|
||||
return parameters.allow_dual_joycons;
|
||||
case Settings::ControllerType::LeftJoycon:
|
||||
case Core::HID::NpadType::JoyconLeft:
|
||||
return parameters.allow_left_joycon;
|
||||
case Settings::ControllerType::RightJoycon:
|
||||
case Core::HID::NpadType::JoyconRight:
|
||||
return parameters.allow_right_joycon;
|
||||
case Settings::ControllerType::Handheld:
|
||||
case Core::HID::NpadType::Handheld:
|
||||
return parameters.enable_single_mode && parameters.allow_handheld;
|
||||
case Settings::ControllerType::GameCube:
|
||||
case Core::HID::NpadType::GameCube:
|
||||
return parameters.allow_gamecube_controller;
|
||||
default:
|
||||
return false;
|
||||
|
@ -196,7 +183,7 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
|
|||
connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged),
|
||||
[this, i](int index) {
|
||||
UpdateDockedState(GetControllerTypeFromIndex(index, i) ==
|
||||
Settings::ControllerType::Handheld);
|
||||
Core::HID::NpadType::Handheld);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -249,17 +236,17 @@ void QtControllerSelectorDialog::ApplyConfiguration() {
|
|||
}
|
||||
|
||||
void QtControllerSelectorDialog::LoadConfiguration() {
|
||||
const auto* handheld = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
|
||||
for (std::size_t index = 0; index < NUM_PLAYERS; ++index) {
|
||||
const auto connected =
|
||||
Settings::values.players.GetValue()[index].connected ||
|
||||
(index == 0 && Settings::values.players.GetValue()[HANDHELD_INDEX].connected);
|
||||
const auto* controller = system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
const auto connected = controller->IsConnected() || (index == 0 && handheld->IsConnected());
|
||||
player_groupboxes[index]->setChecked(connected);
|
||||
connected_controller_checkboxes[index]->setChecked(connected);
|
||||
emulated_controllers[index]->setCurrentIndex(GetIndexFromControllerType(
|
||||
Settings::values.players.GetValue()[index].controller_type, index));
|
||||
emulated_controllers[index]->setCurrentIndex(
|
||||
GetIndexFromControllerType(controller->GetNpadType(), index));
|
||||
}
|
||||
|
||||
UpdateDockedState(Settings::values.players.GetValue()[HANDHELD_INDEX].connected);
|
||||
UpdateDockedState(handheld->IsConnected());
|
||||
|
||||
ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue());
|
||||
ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue());
|
||||
|
@ -415,33 +402,32 @@ void QtControllerSelectorDialog::SetEmulatedControllers(std::size_t player_index
|
|||
emulated_controllers[player_index]->clear();
|
||||
|
||||
pairs.emplace_back(emulated_controllers[player_index]->count(),
|
||||
Settings::ControllerType::ProController);
|
||||
Core::HID::NpadType::ProController);
|
||||
emulated_controllers[player_index]->addItem(tr("Pro Controller"));
|
||||
|
||||
pairs.emplace_back(emulated_controllers[player_index]->count(),
|
||||
Settings::ControllerType::DualJoyconDetached);
|
||||
Core::HID::NpadType::JoyconDual);
|
||||
emulated_controllers[player_index]->addItem(tr("Dual Joycons"));
|
||||
|
||||
pairs.emplace_back(emulated_controllers[player_index]->count(),
|
||||
Settings::ControllerType::LeftJoycon);
|
||||
Core::HID::NpadType::JoyconLeft);
|
||||
emulated_controllers[player_index]->addItem(tr("Left Joycon"));
|
||||
|
||||
pairs.emplace_back(emulated_controllers[player_index]->count(),
|
||||
Settings::ControllerType::RightJoycon);
|
||||
Core::HID::NpadType::JoyconRight);
|
||||
emulated_controllers[player_index]->addItem(tr("Right Joycon"));
|
||||
|
||||
if (player_index == 0) {
|
||||
pairs.emplace_back(emulated_controllers[player_index]->count(),
|
||||
Settings::ControllerType::Handheld);
|
||||
Core::HID::NpadType::Handheld);
|
||||
emulated_controllers[player_index]->addItem(tr("Handheld"));
|
||||
}
|
||||
|
||||
pairs.emplace_back(emulated_controllers[player_index]->count(),
|
||||
Settings::ControllerType::GameCube);
|
||||
pairs.emplace_back(emulated_controllers[player_index]->count(), Core::HID::NpadType::GameCube);
|
||||
emulated_controllers[player_index]->addItem(tr("GameCube Controller"));
|
||||
}
|
||||
|
||||
Settings::ControllerType QtControllerSelectorDialog::GetControllerTypeFromIndex(
|
||||
Core::HID::NpadType QtControllerSelectorDialog::GetControllerTypeFromIndex(
|
||||
int index, std::size_t player_index) const {
|
||||
const auto& pairs = index_controller_type_pairs[player_index];
|
||||
|
||||
|
@ -449,13 +435,13 @@ Settings::ControllerType QtControllerSelectorDialog::GetControllerTypeFromIndex(
|
|||
[index](const auto& pair) { return pair.first == index; });
|
||||
|
||||
if (it == pairs.end()) {
|
||||
return Settings::ControllerType::ProController;
|
||||
return Core::HID::NpadType::ProController;
|
||||
}
|
||||
|
||||
return it->second;
|
||||
}
|
||||
|
||||
int QtControllerSelectorDialog::GetIndexFromControllerType(Settings::ControllerType type,
|
||||
int QtControllerSelectorDialog::GetIndexFromControllerType(Core::HID::NpadType type,
|
||||
std::size_t player_index) const {
|
||||
const auto& pairs = index_controller_type_pairs[player_index];
|
||||
|
||||
|
@ -479,16 +465,16 @@ void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index)
|
|||
const QString stylesheet = [this, player_index] {
|
||||
switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(),
|
||||
player_index)) {
|
||||
case Settings::ControllerType::ProController:
|
||||
case Settings::ControllerType::GameCube:
|
||||
case Core::HID::NpadType::ProController:
|
||||
case Core::HID::NpadType::GameCube:
|
||||
return QStringLiteral("image: url(:/controller/applet_pro_controller%0); ");
|
||||
case Settings::ControllerType::DualJoyconDetached:
|
||||
case Core::HID::NpadType::JoyconDual:
|
||||
return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ");
|
||||
case Settings::ControllerType::LeftJoycon:
|
||||
case Core::HID::NpadType::JoyconLeft:
|
||||
return QStringLiteral("image: url(:/controller/applet_joycon_left%0); ");
|
||||
case Settings::ControllerType::RightJoycon:
|
||||
case Core::HID::NpadType::JoyconRight:
|
||||
return QStringLiteral("image: url(:/controller/applet_joycon_right%0); ");
|
||||
case Settings::ControllerType::Handheld:
|
||||
case Core::HID::NpadType::Handheld:
|
||||
return QStringLiteral("image: url(:/controller/applet_handheld%0); ");
|
||||
default:
|
||||
return QString{};
|
||||
|
@ -516,54 +502,42 @@ void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index)
|
|||
}
|
||||
|
||||
void QtControllerSelectorDialog::UpdateControllerState(std::size_t player_index) {
|
||||
auto& player = Settings::values.players.GetValue()[player_index];
|
||||
auto* controller = system.HIDCore().GetEmulatedControllerByIndex(player_index);
|
||||
|
||||
const auto controller_type = GetControllerTypeFromIndex(
|
||||
emulated_controllers[player_index]->currentIndex(), player_index);
|
||||
const auto player_connected = player_groupboxes[player_index]->isChecked() &&
|
||||
controller_type != Settings::ControllerType::Handheld;
|
||||
controller_type != Core::HID::NpadType::Handheld;
|
||||
|
||||
if (player.controller_type == controller_type && player.connected == player_connected) {
|
||||
if (controller->GetNpadType() == controller_type &&
|
||||
controller->IsConnected() == player_connected) {
|
||||
// Set vibration devices in the event that the input device has changed.
|
||||
ConfigureVibration::SetVibrationDevices(player_index);
|
||||
return;
|
||||
}
|
||||
|
||||
// Disconnect the controller first.
|
||||
UpdateController(controller_type, player_index, false, system);
|
||||
|
||||
player.controller_type = controller_type;
|
||||
player.connected = player_connected;
|
||||
UpdateController(controller, controller_type, false);
|
||||
|
||||
ConfigureVibration::SetVibrationDevices(player_index);
|
||||
|
||||
// Handheld
|
||||
if (player_index == 0) {
|
||||
auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX];
|
||||
if (controller_type == Settings::ControllerType::Handheld) {
|
||||
handheld = player;
|
||||
if (controller_type == Core::HID::NpadType::Handheld) {
|
||||
auto* handheld =
|
||||
system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
|
||||
UpdateController(handheld, Core::HID::NpadType::Handheld,
|
||||
player_groupboxes[player_index]->isChecked());
|
||||
}
|
||||
handheld.connected = player_groupboxes[player_index]->isChecked() &&
|
||||
controller_type == Settings::ControllerType::Handheld;
|
||||
UpdateController(Settings::ControllerType::Handheld, 8, handheld.connected, system);
|
||||
}
|
||||
|
||||
if (!player.connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This emulates a delay between disconnecting and reconnecting controllers as some games
|
||||
// do not respond to a change in controller type if it was instantaneous.
|
||||
using namespace std::chrono_literals;
|
||||
std::this_thread::sleep_for(60ms);
|
||||
|
||||
UpdateController(controller_type, player_index, player_connected, system);
|
||||
UpdateController(controller, controller_type, player_connected);
|
||||
}
|
||||
|
||||
void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) {
|
||||
if (!player_groupboxes[player_index]->isChecked() ||
|
||||
GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(),
|
||||
player_index) == Settings::ControllerType::Handheld) {
|
||||
player_index) == Core::HID::NpadType::Handheld) {
|
||||
led_patterns_boxes[player_index][0]->setChecked(false);
|
||||
led_patterns_boxes[player_index][1]->setChecked(false);
|
||||
led_patterns_boxes[player_index][2]->setChecked(false);
|
||||
|
@ -571,10 +545,12 @@ void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) {
|
|||
return;
|
||||
}
|
||||
|
||||
led_patterns_boxes[player_index][0]->setChecked(led_patterns[player_index][0]);
|
||||
led_patterns_boxes[player_index][1]->setChecked(led_patterns[player_index][1]);
|
||||
led_patterns_boxes[player_index][2]->setChecked(led_patterns[player_index][2]);
|
||||
led_patterns_boxes[player_index][3]->setChecked(led_patterns[player_index][3]);
|
||||
const auto* controller = system.HIDCore().GetEmulatedControllerByIndex(player_index);
|
||||
const auto led_pattern = controller->GetLedPattern();
|
||||
led_patterns_boxes[player_index][0]->setChecked(led_pattern.position1);
|
||||
led_patterns_boxes[player_index][1]->setChecked(led_pattern.position2);
|
||||
led_patterns_boxes[player_index][2]->setChecked(led_pattern.position3);
|
||||
led_patterns_boxes[player_index][3]->setChecked(led_pattern.position4);
|
||||
}
|
||||
|
||||
void QtControllerSelectorDialog::UpdateBorderColor(std::size_t player_index) {
|
||||
|
@ -654,10 +630,9 @@ void QtControllerSelectorDialog::DisableUnsupportedPlayers() {
|
|||
}
|
||||
|
||||
for (std::size_t index = max_supported_players; index < NUM_PLAYERS; ++index) {
|
||||
auto* controller = system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
// Disconnect any unsupported players here and disable or hide them if applicable.
|
||||
Settings::values.players.GetValue()[index].connected = false;
|
||||
UpdateController(Settings::values.players.GetValue()[index].controller_type, index, false,
|
||||
system);
|
||||
UpdateController(controller, controller->GetNpadType(), false);
|
||||
// Hide the player widgets when max_supported_controllers is less than or equal to 4.
|
||||
if (max_supported_players <= 4) {
|
||||
player_widgets[index]->hide();
|
||||
|
|
|
@ -23,14 +23,18 @@ namespace InputCommon {
|
|||
class InputSubsystem;
|
||||
}
|
||||
|
||||
namespace Settings {
|
||||
enum class ControllerType;
|
||||
}
|
||||
|
||||
namespace Ui {
|
||||
class QtControllerSelectorDialog;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Core::HID {
|
||||
enum class NpadType : u8;
|
||||
}
|
||||
|
||||
class QtControllerSelectorDialog final : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -70,10 +74,10 @@ private:
|
|||
void SetEmulatedControllers(std::size_t player_index);
|
||||
|
||||
// Gets the Controller Type for a given controller combobox index per player.
|
||||
Settings::ControllerType GetControllerTypeFromIndex(int index, std::size_t player_index) const;
|
||||
Core::HID::NpadType GetControllerTypeFromIndex(int index, std::size_t player_index) const;
|
||||
|
||||
// Gets the controller combobox index for a given Controller Type per player.
|
||||
int GetIndexFromControllerType(Settings::ControllerType type, std::size_t player_index) const;
|
||||
int GetIndexFromControllerType(Core::HID::NpadType type, std::size_t player_index) const;
|
||||
|
||||
// Updates the controller icons per player.
|
||||
void UpdateControllerIcon(std::size_t player_index);
|
||||
|
@ -135,7 +139,7 @@ private:
|
|||
std::array<QComboBox*, NUM_PLAYERS> emulated_controllers;
|
||||
|
||||
/// Pairs of emulated controller index and Controller Type enum per player.
|
||||
std::array<std::vector<std::pair<int, Settings::ControllerType>>, NUM_PLAYERS>
|
||||
std::array<std::vector<std::pair<int, Core::HID::NpadType>>, NUM_PLAYERS>
|
||||
index_controller_type_pairs;
|
||||
|
||||
// Labels representing the number of connected controllers
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/input_interpreter.h"
|
||||
#include "core/hid/emulated_controller.h"
|
||||
#include "core/hid/hid_core.h"
|
||||
#include "core/hid/hid_types.h"
|
||||
#include "core/hid/input_interpreter.h"
|
||||
#include "ui_qt_software_keyboard.h"
|
||||
#include "yuzu/applets/qt_software_keyboard.h"
|
||||
#include "yuzu/main.h"
|
||||
|
@ -484,7 +487,7 @@ void QtSoftwareKeyboardDialog::open() {
|
|||
void QtSoftwareKeyboardDialog::reject() {
|
||||
// Pressing the ESC key in a dialog calls QDialog::reject().
|
||||
// We will override this behavior to the "Cancel" action on the software keyboard.
|
||||
TranslateButtonPress(HIDButton::X);
|
||||
TranslateButtonPress(Core::HID::NpadButton::X);
|
||||
}
|
||||
|
||||
void QtSoftwareKeyboardDialog::keyPressEvent(QKeyEvent* event) {
|
||||
|
@ -722,7 +725,7 @@ void QtSoftwareKeyboardDialog::SetTextDrawType() {
|
|||
|
||||
connect(
|
||||
ui->line_edit_osk, &QLineEdit::returnPressed, this,
|
||||
[this] { TranslateButtonPress(HIDButton::Plus); }, Qt::QueuedConnection);
|
||||
[this] { TranslateButtonPress(Core::HID::NpadButton::Plus); }, Qt::QueuedConnection);
|
||||
|
||||
ui->line_edit_osk->setPlaceholderText(
|
||||
QString::fromStdU16String(initialize_parameters.guide_text));
|
||||
|
@ -795,9 +798,10 @@ void QtSoftwareKeyboardDialog::SetTextDrawType() {
|
|||
}
|
||||
|
||||
void QtSoftwareKeyboardDialog::SetControllerImage() {
|
||||
const auto controller_type = Settings::values.players.GetValue()[8].connected
|
||||
? Settings::values.players.GetValue()[8].controller_type
|
||||
: Settings::values.players.GetValue()[0].controller_type;
|
||||
const auto* handheld = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
|
||||
const auto* player_1 = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
|
||||
const auto controller_type =
|
||||
handheld->IsConnected() ? handheld->GetNpadType() : player_1->GetNpadType();
|
||||
|
||||
const QString theme = [] {
|
||||
if (QIcon::themeName().contains(QStringLiteral("dark")) ||
|
||||
|
@ -809,8 +813,8 @@ void QtSoftwareKeyboardDialog::SetControllerImage() {
|
|||
}();
|
||||
|
||||
switch (controller_type) {
|
||||
case Settings::ControllerType::ProController:
|
||||
case Settings::ControllerType::GameCube:
|
||||
case Core::HID::NpadType::ProController:
|
||||
case Core::HID::NpadType::GameCube:
|
||||
ui->icon_controller->setStyleSheet(
|
||||
QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme));
|
||||
ui->icon_controller_shift->setStyleSheet(
|
||||
|
@ -818,7 +822,7 @@ void QtSoftwareKeyboardDialog::SetControllerImage() {
|
|||
ui->icon_controller_num->setStyleSheet(
|
||||
QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme));
|
||||
break;
|
||||
case Settings::ControllerType::DualJoyconDetached:
|
||||
case Core::HID::NpadType::JoyconDual:
|
||||
ui->icon_controller->setStyleSheet(
|
||||
QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme));
|
||||
ui->icon_controller_shift->setStyleSheet(
|
||||
|
@ -826,7 +830,7 @@ void QtSoftwareKeyboardDialog::SetControllerImage() {
|
|||
ui->icon_controller_num->setStyleSheet(
|
||||
QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme));
|
||||
break;
|
||||
case Settings::ControllerType::LeftJoycon:
|
||||
case Core::HID::NpadType::JoyconLeft:
|
||||
ui->icon_controller->setStyleSheet(
|
||||
QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);")
|
||||
.arg(theme));
|
||||
|
@ -837,7 +841,7 @@ void QtSoftwareKeyboardDialog::SetControllerImage() {
|
|||
QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);")
|
||||
.arg(theme));
|
||||
break;
|
||||
case Settings::ControllerType::RightJoycon:
|
||||
case Core::HID::NpadType::JoyconRight:
|
||||
ui->icon_controller->setStyleSheet(
|
||||
QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);")
|
||||
.arg(theme));
|
||||
|
@ -848,7 +852,7 @@ void QtSoftwareKeyboardDialog::SetControllerImage() {
|
|||
QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);")
|
||||
.arg(theme));
|
||||
break;
|
||||
case Settings::ControllerType::Handheld:
|
||||
case Core::HID::NpadType::Handheld:
|
||||
ui->icon_controller->setStyleSheet(
|
||||
QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme));
|
||||
ui->icon_controller_shift->setStyleSheet(
|
||||
|
@ -1208,9 +1212,9 @@ void QtSoftwareKeyboardDialog::SetupMouseHover() {
|
|||
}
|
||||
}
|
||||
|
||||
template <HIDButton... T>
|
||||
template <Core::HID::NpadButton... T>
|
||||
void QtSoftwareKeyboardDialog::HandleButtonPressedOnce() {
|
||||
const auto f = [this](HIDButton button) {
|
||||
const auto f = [this](Core::HID::NpadButton button) {
|
||||
if (input_interpreter->IsButtonPressedOnce(button)) {
|
||||
TranslateButtonPress(button);
|
||||
}
|
||||
|
@ -1219,9 +1223,9 @@ void QtSoftwareKeyboardDialog::HandleButtonPressedOnce() {
|
|||
(f(T), ...);
|
||||
}
|
||||
|
||||
template <HIDButton... T>
|
||||
template <Core::HID::NpadButton... T>
|
||||
void QtSoftwareKeyboardDialog::HandleButtonHold() {
|
||||
const auto f = [this](HIDButton button) {
|
||||
const auto f = [this](Core::HID::NpadButton button) {
|
||||
if (input_interpreter->IsButtonHeld(button)) {
|
||||
TranslateButtonPress(button);
|
||||
}
|
||||
|
@ -1230,9 +1234,9 @@ void QtSoftwareKeyboardDialog::HandleButtonHold() {
|
|||
(f(T), ...);
|
||||
}
|
||||
|
||||
void QtSoftwareKeyboardDialog::TranslateButtonPress(HIDButton button) {
|
||||
void QtSoftwareKeyboardDialog::TranslateButtonPress(Core::HID::NpadButton button) {
|
||||
switch (button) {
|
||||
case HIDButton::A:
|
||||
case Core::HID::NpadButton::A:
|
||||
switch (bottom_osk_index) {
|
||||
case BottomOSKIndex::LowerCase:
|
||||
case BottomOSKIndex::UpperCase:
|
||||
|
@ -1245,7 +1249,7 @@ void QtSoftwareKeyboardDialog::TranslateButtonPress(HIDButton button) {
|
|||
break;
|
||||
}
|
||||
break;
|
||||
case HIDButton::B:
|
||||
case Core::HID::NpadButton::B:
|
||||
switch (bottom_osk_index) {
|
||||
case BottomOSKIndex::LowerCase:
|
||||
ui->button_backspace->click();
|
||||
|
@ -1260,7 +1264,7 @@ void QtSoftwareKeyboardDialog::TranslateButtonPress(HIDButton button) {
|
|||
break;
|
||||
}
|
||||
break;
|
||||
case HIDButton::X:
|
||||
case Core::HID::NpadButton::X:
|
||||
if (is_inline) {
|
||||
emit SubmitInlineText(SwkbdReplyType::DecidedCancel, current_text, cursor_position);
|
||||
} else {
|
||||
|
@ -1271,7 +1275,7 @@ void QtSoftwareKeyboardDialog::TranslateButtonPress(HIDButton button) {
|
|||
emit SubmitNormalText(SwkbdResult::Cancel, std::move(text));
|
||||
}
|
||||
break;
|
||||
case HIDButton::Y:
|
||||
case Core::HID::NpadButton::Y:
|
||||
switch (bottom_osk_index) {
|
||||
case BottomOSKIndex::LowerCase:
|
||||
ui->button_space->click();
|
||||
|
@ -1284,8 +1288,8 @@ void QtSoftwareKeyboardDialog::TranslateButtonPress(HIDButton button) {
|
|||
break;
|
||||
}
|
||||
break;
|
||||
case HIDButton::LStick:
|
||||
case HIDButton::RStick:
|
||||
case Core::HID::NpadButton::StickL:
|
||||
case Core::HID::NpadButton::StickR:
|
||||
switch (bottom_osk_index) {
|
||||
case BottomOSKIndex::LowerCase:
|
||||
ui->button_shift->click();
|
||||
|
@ -1298,13 +1302,13 @@ void QtSoftwareKeyboardDialog::TranslateButtonPress(HIDButton button) {
|
|||
break;
|
||||
}
|
||||
break;
|
||||
case HIDButton::L:
|
||||
case Core::HID::NpadButton::L:
|
||||
MoveTextCursorDirection(Direction::Left);
|
||||
break;
|
||||
case HIDButton::R:
|
||||
case Core::HID::NpadButton::R:
|
||||
MoveTextCursorDirection(Direction::Right);
|
||||
break;
|
||||
case HIDButton::Plus:
|
||||
case Core::HID::NpadButton::Plus:
|
||||
switch (bottom_osk_index) {
|
||||
case BottomOSKIndex::LowerCase:
|
||||
ui->button_ok->click();
|
||||
|
@ -1319,24 +1323,24 @@ void QtSoftwareKeyboardDialog::TranslateButtonPress(HIDButton button) {
|
|||
break;
|
||||
}
|
||||
break;
|
||||
case HIDButton::DLeft:
|
||||
case HIDButton::LStickLeft:
|
||||
case HIDButton::RStickLeft:
|
||||
case Core::HID::NpadButton::Left:
|
||||
case Core::HID::NpadButton::StickLLeft:
|
||||
case Core::HID::NpadButton::StickRLeft:
|
||||
MoveButtonDirection(Direction::Left);
|
||||
break;
|
||||
case HIDButton::DUp:
|
||||
case HIDButton::LStickUp:
|
||||
case HIDButton::RStickUp:
|
||||
case Core::HID::NpadButton::Up:
|
||||
case Core::HID::NpadButton::StickLUp:
|
||||
case Core::HID::NpadButton::StickRUp:
|
||||
MoveButtonDirection(Direction::Up);
|
||||
break;
|
||||
case HIDButton::DRight:
|
||||
case HIDButton::LStickRight:
|
||||
case HIDButton::RStickRight:
|
||||
case Core::HID::NpadButton::Right:
|
||||
case Core::HID::NpadButton::StickLRight:
|
||||
case Core::HID::NpadButton::StickRRight:
|
||||
MoveButtonDirection(Direction::Right);
|
||||
break;
|
||||
case HIDButton::DDown:
|
||||
case HIDButton::LStickDown:
|
||||
case HIDButton::RStickDown:
|
||||
case Core::HID::NpadButton::Down:
|
||||
case Core::HID::NpadButton::StickLDown:
|
||||
case Core::HID::NpadButton::StickRDown:
|
||||
MoveButtonDirection(Direction::Down);
|
||||
break;
|
||||
default:
|
||||
|
@ -1467,19 +1471,25 @@ void QtSoftwareKeyboardDialog::InputThread() {
|
|||
while (input_thread_running) {
|
||||
input_interpreter->PollInput();
|
||||
|
||||
HandleButtonPressedOnce<HIDButton::A, HIDButton::B, HIDButton::X, HIDButton::Y,
|
||||
HIDButton::LStick, HIDButton::RStick, HIDButton::L, HIDButton::R,
|
||||
HIDButton::Plus, HIDButton::DLeft, HIDButton::DUp,
|
||||
HIDButton::DRight, HIDButton::DDown, HIDButton::LStickLeft,
|
||||
HIDButton::LStickUp, HIDButton::LStickRight, HIDButton::LStickDown,
|
||||
HIDButton::RStickLeft, HIDButton::RStickUp, HIDButton::RStickRight,
|
||||
HIDButton::RStickDown>();
|
||||
HandleButtonPressedOnce<
|
||||
Core::HID::NpadButton::A, Core::HID::NpadButton::B, Core::HID::NpadButton::X,
|
||||
Core::HID::NpadButton::Y, Core::HID::NpadButton::StickL, Core::HID::NpadButton::StickR,
|
||||
Core::HID::NpadButton::L, Core::HID::NpadButton::R, Core::HID::NpadButton::Plus,
|
||||
Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
|
||||
Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
|
||||
Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
|
||||
Core::HID::NpadButton::StickLDown, Core::HID::NpadButton::StickRLeft,
|
||||
Core::HID::NpadButton::StickRUp, Core::HID::NpadButton::StickRRight,
|
||||
Core::HID::NpadButton::StickRDown>();
|
||||
|
||||
HandleButtonHold<HIDButton::B, HIDButton::L, HIDButton::R, HIDButton::DLeft, HIDButton::DUp,
|
||||
HIDButton::DRight, HIDButton::DDown, HIDButton::LStickLeft,
|
||||
HIDButton::LStickUp, HIDButton::LStickRight, HIDButton::LStickDown,
|
||||
HIDButton::RStickLeft, HIDButton::RStickUp, HIDButton::RStickRight,
|
||||
HIDButton::RStickDown>();
|
||||
HandleButtonHold<Core::HID::NpadButton::B, Core::HID::NpadButton::L,
|
||||
Core::HID::NpadButton::R, Core::HID::NpadButton::Left,
|
||||
Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
|
||||
Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
|
||||
Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
|
||||
Core::HID::NpadButton::StickLDown, Core::HID::NpadButton::StickRLeft,
|
||||
Core::HID::NpadButton::StickRUp, Core::HID::NpadButton::StickRRight,
|
||||
Core::HID::NpadButton::StickRDown>();
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
}
|
||||
|
|
|
@ -14,14 +14,16 @@
|
|||
|
||||
#include "core/frontend/applets/software_keyboard.h"
|
||||
|
||||
enum class HIDButton : u8;
|
||||
|
||||
class InputInterpreter;
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Core::HID {
|
||||
enum class NpadButton : u64;
|
||||
}
|
||||
|
||||
namespace Ui {
|
||||
class QtSoftwareKeyboardDialog;
|
||||
}
|
||||
|
@ -146,7 +148,7 @@ private:
|
|||
*
|
||||
* @tparam HIDButton The list of buttons that can be converted into keyboard input.
|
||||
*/
|
||||
template <HIDButton... T>
|
||||
template <Core::HID::NpadButton... T>
|
||||
void HandleButtonPressedOnce();
|
||||
|
||||
/**
|
||||
|
@ -154,7 +156,7 @@ private:
|
|||
*
|
||||
* @tparam HIDButton The list of buttons that can be converted into keyboard input.
|
||||
*/
|
||||
template <HIDButton... T>
|
||||
template <Core::HID::NpadButton... T>
|
||||
void HandleButtonHold();
|
||||
|
||||
/**
|
||||
|
@ -162,7 +164,7 @@ private:
|
|||
*
|
||||
* @param button The button press to process.
|
||||
*/
|
||||
void TranslateButtonPress(HIDButton button);
|
||||
void TranslateButtonPress(Core::HID::NpadButton button);
|
||||
|
||||
/**
|
||||
* Moves the focus of a button in a certain direction.
|
||||
|
|
|
@ -14,9 +14,11 @@
|
|||
#endif
|
||||
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/param_package.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/input_interpreter.h"
|
||||
#include "input_common/keyboard.h"
|
||||
#include "core/hid/hid_types.h"
|
||||
#include "core/hid/input_interpreter.h"
|
||||
#include "input_common/drivers/keyboard.h"
|
||||
#include "input_common/main.h"
|
||||
#include "yuzu/applets/qt_web_browser.h"
|
||||
#include "yuzu/applets/qt_web_browser_scripts.h"
|
||||
|
@ -27,19 +29,19 @@
|
|||
|
||||
namespace {
|
||||
|
||||
constexpr int HIDButtonToKey(HIDButton button) {
|
||||
constexpr int HIDButtonToKey(Core::HID::NpadButton button) {
|
||||
switch (button) {
|
||||
case HIDButton::DLeft:
|
||||
case HIDButton::LStickLeft:
|
||||
case Core::HID::NpadButton::Left:
|
||||
case Core::HID::NpadButton::StickLLeft:
|
||||
return Qt::Key_Left;
|
||||
case HIDButton::DUp:
|
||||
case HIDButton::LStickUp:
|
||||
case Core::HID::NpadButton::Up:
|
||||
case Core::HID::NpadButton::StickLUp:
|
||||
return Qt::Key_Up;
|
||||
case HIDButton::DRight:
|
||||
case HIDButton::LStickRight:
|
||||
case Core::HID::NpadButton::Right:
|
||||
case Core::HID::NpadButton::StickLRight:
|
||||
return Qt::Key_Right;
|
||||
case HIDButton::DDown:
|
||||
case HIDButton::LStickDown:
|
||||
case Core::HID::NpadButton::Down:
|
||||
case Core::HID::NpadButton::StickLDown:
|
||||
return Qt::Key_Down;
|
||||
default:
|
||||
return 0;
|
||||
|
@ -208,25 +210,25 @@ void QtNXWebEngineView::keyReleaseEvent(QKeyEvent* event) {
|
|||
}
|
||||
}
|
||||
|
||||
template <HIDButton... T>
|
||||
template <Core::HID::NpadButton... T>
|
||||
void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() {
|
||||
const auto f = [this](HIDButton button) {
|
||||
const auto f = [this](Core::HID::NpadButton button) {
|
||||
if (input_interpreter->IsButtonPressedOnce(button)) {
|
||||
page()->runJavaScript(
|
||||
QStringLiteral("yuzu_key_callbacks[%1] == null;").arg(static_cast<u8>(button)),
|
||||
[this, button](const QVariant& variant) {
|
||||
if (variant.toBool()) {
|
||||
switch (button) {
|
||||
case HIDButton::A:
|
||||
case Core::HID::NpadButton::A:
|
||||
SendMultipleKeyPressEvents<Qt::Key_A, Qt::Key_Space, Qt::Key_Return>();
|
||||
break;
|
||||
case HIDButton::B:
|
||||
case Core::HID::NpadButton::B:
|
||||
SendKeyPressEvent(Qt::Key_B);
|
||||
break;
|
||||
case HIDButton::X:
|
||||
case Core::HID::NpadButton::X:
|
||||
SendKeyPressEvent(Qt::Key_X);
|
||||
break;
|
||||
case HIDButton::Y:
|
||||
case Core::HID::NpadButton::Y:
|
||||
SendKeyPressEvent(Qt::Key_Y);
|
||||
break;
|
||||
default:
|
||||
|
@ -244,9 +246,9 @@ void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() {
|
|||
(f(T), ...);
|
||||
}
|
||||
|
||||
template <HIDButton... T>
|
||||
template <Core::HID::NpadButton... T>
|
||||
void QtNXWebEngineView::HandleWindowKeyButtonPressedOnce() {
|
||||
const auto f = [this](HIDButton button) {
|
||||
const auto f = [this](Core::HID::NpadButton button) {
|
||||
if (input_interpreter->IsButtonPressedOnce(button)) {
|
||||
SendKeyPressEvent(HIDButtonToKey(button));
|
||||
}
|
||||
|
@ -255,9 +257,9 @@ void QtNXWebEngineView::HandleWindowKeyButtonPressedOnce() {
|
|||
(f(T), ...);
|
||||
}
|
||||
|
||||
template <HIDButton... T>
|
||||
template <Core::HID::NpadButton... T>
|
||||
void QtNXWebEngineView::HandleWindowKeyButtonHold() {
|
||||
const auto f = [this](HIDButton button) {
|
||||
const auto f = [this](Core::HID::NpadButton button) {
|
||||
if (input_interpreter->IsButtonHeld(button)) {
|
||||
SendKeyPressEvent(HIDButtonToKey(button));
|
||||
}
|
||||
|
@ -308,17 +310,21 @@ void QtNXWebEngineView::InputThread() {
|
|||
while (input_thread_running) {
|
||||
input_interpreter->PollInput();
|
||||
|
||||
HandleWindowFooterButtonPressedOnce<HIDButton::A, HIDButton::B, HIDButton::X, HIDButton::Y,
|
||||
HIDButton::L, HIDButton::R>();
|
||||
HandleWindowFooterButtonPressedOnce<Core::HID::NpadButton::A, Core::HID::NpadButton::B,
|
||||
Core::HID::NpadButton::X, Core::HID::NpadButton::Y,
|
||||
Core::HID::NpadButton::L, Core::HID::NpadButton::R>();
|
||||
|
||||
HandleWindowKeyButtonPressedOnce<HIDButton::DLeft, HIDButton::DUp, HIDButton::DRight,
|
||||
HIDButton::DDown, HIDButton::LStickLeft,
|
||||
HIDButton::LStickUp, HIDButton::LStickRight,
|
||||
HIDButton::LStickDown>();
|
||||
HandleWindowKeyButtonPressedOnce<
|
||||
Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
|
||||
Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
|
||||
Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
|
||||
Core::HID::NpadButton::StickLDown>();
|
||||
|
||||
HandleWindowKeyButtonHold<HIDButton::DLeft, HIDButton::DUp, HIDButton::DRight,
|
||||
HIDButton::DDown, HIDButton::LStickLeft, HIDButton::LStickUp,
|
||||
HIDButton::LStickRight, HIDButton::LStickDown>();
|
||||
HandleWindowKeyButtonHold<
|
||||
Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
|
||||
Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
|
||||
Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
|
||||
Core::HID::NpadButton::StickLDown>();
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
}
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
|
||||
#include "core/frontend/applets/web_browser.h"
|
||||
|
||||
enum class HIDButton : u8;
|
||||
|
||||
class GMainWindow;
|
||||
class InputInterpreter;
|
||||
class UrlRequestInterceptor;
|
||||
|
@ -26,6 +24,10 @@ namespace Core {
|
|||
class System;
|
||||
}
|
||||
|
||||
namespace Core::HID {
|
||||
enum class NpadButton : u64;
|
||||
}
|
||||
|
||||
namespace InputCommon {
|
||||
class InputSubsystem;
|
||||
}
|
||||
|
@ -114,7 +116,7 @@ private:
|
|||
*
|
||||
* @tparam HIDButton The list of buttons contained in yuzu_key_callbacks
|
||||
*/
|
||||
template <HIDButton... T>
|
||||
template <Core::HID::NpadButton... T>
|
||||
void HandleWindowFooterButtonPressedOnce();
|
||||
|
||||
/**
|
||||
|
@ -123,7 +125,7 @@ private:
|
|||
*
|
||||
* @tparam HIDButton The list of buttons that can be converted into keyboard input.
|
||||
*/
|
||||
template <HIDButton... T>
|
||||
template <Core::HID::NpadButton... T>
|
||||
void HandleWindowKeyButtonPressedOnce();
|
||||
|
||||
/**
|
||||
|
@ -132,7 +134,7 @@ private:
|
|||
*
|
||||
* @tparam HIDButton The list of buttons that can be converted into keyboard input.
|
||||
*/
|
||||
template <HIDButton... T>
|
||||
template <Core::HID::NpadButton... T>
|
||||
void HandleWindowKeyButtonHold();
|
||||
|
||||
/**
|
||||
|
|
|
@ -27,16 +27,18 @@
|
|||
|
||||
#include "common/assert.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/param_package.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/framebuffer_layout.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "input_common/keyboard.h"
|
||||
#include "input_common/drivers/keyboard.h"
|
||||
#include "input_common/drivers/mouse.h"
|
||||
#include "input_common/drivers/tas_input.h"
|
||||
#include "input_common/drivers/touch_screen.h"
|
||||
#include "input_common/main.h"
|
||||
#include "input_common/mouse/mouse_input.h"
|
||||
#include "input_common/tas/tas_input.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
#include "yuzu/bootmanager.h"
|
||||
|
@ -297,7 +299,6 @@ GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
|
|||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
setLayout(layout);
|
||||
input_subsystem->Initialize();
|
||||
|
||||
this->setMouseTracking(true);
|
||||
|
||||
connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
|
||||
|
@ -396,22 +397,22 @@ void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
|
|||
}
|
||||
}
|
||||
|
||||
MouseInput::MouseButton GRenderWindow::QtButtonToMouseButton(Qt::MouseButton button) {
|
||||
InputCommon::MouseButton GRenderWindow::QtButtonToMouseButton(Qt::MouseButton button) {
|
||||
switch (button) {
|
||||
case Qt::LeftButton:
|
||||
return MouseInput::MouseButton::Left;
|
||||
return InputCommon::MouseButton::Left;
|
||||
case Qt::RightButton:
|
||||
return MouseInput::MouseButton::Right;
|
||||
return InputCommon::MouseButton::Right;
|
||||
case Qt::MiddleButton:
|
||||
return MouseInput::MouseButton::Wheel;
|
||||
return InputCommon::MouseButton::Wheel;
|
||||
case Qt::BackButton:
|
||||
return MouseInput::MouseButton::Backward;
|
||||
return InputCommon::MouseButton::Backward;
|
||||
case Qt::ForwardButton:
|
||||
return MouseInput::MouseButton::Forward;
|
||||
return InputCommon::MouseButton::Forward;
|
||||
case Qt::TaskButton:
|
||||
return MouseInput::MouseButton::Task;
|
||||
return InputCommon::MouseButton::Task;
|
||||
default:
|
||||
return MouseInput::MouseButton::Extra;
|
||||
return InputCommon::MouseButton::Extra;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -424,12 +425,9 @@ void GRenderWindow::mousePressEvent(QMouseEvent* event) {
|
|||
// coordinates and map them to the current render area
|
||||
const auto pos = mapFromGlobal(QCursor::pos());
|
||||
const auto [x, y] = ScaleTouch(pos);
|
||||
const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
|
||||
const auto button = QtButtonToMouseButton(event->button());
|
||||
input_subsystem->GetMouse()->PressButton(x, y, button);
|
||||
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
this->TouchPressed(x, y, 0);
|
||||
}
|
||||
input_subsystem->GetMouse()->PressButton(x, y, touch_x, touch_y, button);
|
||||
|
||||
emit MouseActivity();
|
||||
}
|
||||
|
@ -443,10 +441,10 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
|
|||
// coordinates and map them to the current render area
|
||||
const auto pos = mapFromGlobal(QCursor::pos());
|
||||
const auto [x, y] = ScaleTouch(pos);
|
||||
const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
|
||||
const int center_x = width() / 2;
|
||||
const int center_y = height() / 2;
|
||||
input_subsystem->GetMouse()->MouseMove(x, y, center_x, center_y);
|
||||
this->TouchMoved(x, y, 0);
|
||||
input_subsystem->GetMouse()->MouseMove(x, y, touch_x, touch_y, center_x, center_y);
|
||||
|
||||
if (Settings::values.mouse_panning) {
|
||||
QCursor::setPos(mapToGlobal({center_x, center_y}));
|
||||
|
@ -463,10 +461,6 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
|
|||
|
||||
const auto button = QtButtonToMouseButton(event->button());
|
||||
input_subsystem->GetMouse()->ReleaseButton(button);
|
||||
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
this->TouchReleased(0);
|
||||
}
|
||||
}
|
||||
|
||||
void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) {
|
||||
|
@ -489,7 +483,7 @@ void GRenderWindow::TouchUpdateEvent(const QTouchEvent* event) {
|
|||
for (std::size_t id = 0; id < touch_ids.size(); ++id) {
|
||||
if (!TouchExist(touch_ids[id], touch_points)) {
|
||||
touch_ids[id] = 0;
|
||||
this->TouchReleased(id + 1);
|
||||
input_subsystem->GetTouchScreen()->TouchReleased(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -498,28 +492,28 @@ void GRenderWindow::TouchEndEvent() {
|
|||
for (std::size_t id = 0; id < touch_ids.size(); ++id) {
|
||||
if (touch_ids[id] != 0) {
|
||||
touch_ids[id] = 0;
|
||||
this->TouchReleased(id + 1);
|
||||
input_subsystem->GetTouchScreen()->TouchReleased(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GRenderWindow::TouchStart(const QTouchEvent::TouchPoint& touch_point) {
|
||||
void GRenderWindow::TouchStart(const QTouchEvent::TouchPoint& touch_point) {
|
||||
for (std::size_t id = 0; id < touch_ids.size(); ++id) {
|
||||
if (touch_ids[id] == 0) {
|
||||
touch_ids[id] = touch_point.id() + 1;
|
||||
const auto [x, y] = ScaleTouch(touch_point.pos());
|
||||
this->TouchPressed(x, y, id + 1);
|
||||
return true;
|
||||
const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
|
||||
input_subsystem->GetTouchScreen()->TouchPressed(touch_x, touch_y, id);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GRenderWindow::TouchUpdate(const QTouchEvent::TouchPoint& touch_point) {
|
||||
for (std::size_t id = 0; id < touch_ids.size(); ++id) {
|
||||
if (touch_ids[id] == static_cast<std::size_t>(touch_point.id() + 1)) {
|
||||
const auto [x, y] = ScaleTouch(touch_point.pos());
|
||||
this->TouchMoved(x, y, id + 1);
|
||||
const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
|
||||
input_subsystem->GetTouchScreen()->TouchMoved(touch_x, touch_y, id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -552,7 +546,7 @@ void GRenderWindow::focusOutEvent(QFocusEvent* event) {
|
|||
QWidget::focusOutEvent(event);
|
||||
input_subsystem->GetKeyboard()->ReleaseAllKeys();
|
||||
input_subsystem->GetMouse()->ReleaseAllButtons();
|
||||
this->TouchReleased(0);
|
||||
input_subsystem->GetTouchScreen()->ReleaseAllTouch();
|
||||
}
|
||||
|
||||
void GRenderWindow::resizeEvent(QResizeEvent* event) {
|
||||
|
|
|
@ -30,11 +30,8 @@ class System;
|
|||
|
||||
namespace InputCommon {
|
||||
class InputSubsystem;
|
||||
}
|
||||
|
||||
namespace MouseInput {
|
||||
enum class MouseButton;
|
||||
}
|
||||
} // namespace InputCommon
|
||||
|
||||
namespace VideoCore {
|
||||
enum class LoadCallbackStage;
|
||||
|
@ -161,7 +158,7 @@ public:
|
|||
void keyReleaseEvent(QKeyEvent* event) override;
|
||||
|
||||
/// Converts a Qt mouse button into MouseInput mouse button
|
||||
static MouseInput::MouseButton QtButtonToMouseButton(Qt::MouseButton button);
|
||||
static InputCommon::MouseButton QtButtonToMouseButton(Qt::MouseButton button);
|
||||
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
void mouseMoveEvent(QMouseEvent* event) override;
|
||||
|
@ -209,7 +206,7 @@ private:
|
|||
void TouchUpdateEvent(const QTouchEvent* event);
|
||||
void TouchEndEvent();
|
||||
|
||||
bool TouchStart(const QTouchEvent::TouchPoint& touch_point);
|
||||
void TouchStart(const QTouchEvent::TouchPoint& touch_point);
|
||||
bool TouchUpdate(const QTouchEvent::TouchPoint& touch_point);
|
||||
bool TouchExist(std::size_t id, const QList<QTouchEvent::TouchPoint>& touch_points) const;
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "core/hle/service/hid/controllers/npad.h"
|
||||
#include "input_common/main.h"
|
||||
#include "input_common/udp/client.h"
|
||||
#include "yuzu/configuration/config.h"
|
||||
|
||||
namespace FS = Common::FS;
|
||||
|
@ -574,7 +573,6 @@ void Config::ReadControlValues() {
|
|||
|
||||
ReadBasicSetting(Settings::values.tas_enable);
|
||||
ReadBasicSetting(Settings::values.tas_loop);
|
||||
ReadBasicSetting(Settings::values.tas_swap_controllers);
|
||||
ReadBasicSetting(Settings::values.pause_tas_on_load);
|
||||
|
||||
ReadGlobalSetting(Settings::values.use_docked_mode);
|
||||
|
@ -1210,7 +1208,6 @@ void Config::SaveControlValues() {
|
|||
|
||||
WriteBasicSetting(Settings::values.tas_enable);
|
||||
WriteBasicSetting(Settings::values.tas_loop);
|
||||
WriteBasicSetting(Settings::values.tas_swap_controllers);
|
||||
WriteBasicSetting(Settings::values.pause_tas_on_load);
|
||||
|
||||
qt_config->endGroup();
|
||||
|
|
|
@ -74,7 +74,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry,
|
|||
hotkeys_tab->Populate(registry);
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
input_tab->Initialize(input_subsystem);
|
||||
input_tab->Initialize(input_subsystem, system_);
|
||||
|
||||
general_tab->SetResetCallback([&] { this->close(); });
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ ConfigureInput::ConfigureInput(Core::System& system_, QWidget* parent)
|
|||
|
||||
ConfigureInput::~ConfigureInput() = default;
|
||||
|
||||
void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
|
||||
void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, Core::System& system,
|
||||
std::size_t max_players) {
|
||||
player_controllers = {
|
||||
new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem, profiles.get(),
|
||||
|
@ -114,6 +114,7 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
|
|||
player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i]));
|
||||
player_tabs[i]->layout()->addWidget(player_controllers[i]);
|
||||
connect(player_controllers[i], &ConfigureInputPlayer::Connected, [&, i](bool is_connected) {
|
||||
// Ensures that the controllers are always connected in sequential order
|
||||
if (is_connected) {
|
||||
for (std::size_t index = 0; index <= i; ++index) {
|
||||
player_connected[index]->setChecked(is_connected);
|
||||
|
@ -146,9 +147,10 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
|
|||
advanced = new ConfigureInputAdvanced(this);
|
||||
ui->tabAdvanced->setLayout(new QHBoxLayout(ui->tabAdvanced));
|
||||
ui->tabAdvanced->layout()->addWidget(advanced);
|
||||
connect(advanced, &ConfigureInputAdvanced::CallDebugControllerDialog, [this, input_subsystem] {
|
||||
CallConfigureDialog<ConfigureDebugController>(*this, input_subsystem, profiles.get(),
|
||||
system);
|
||||
connect(advanced, &ConfigureInputAdvanced::CallDebugControllerDialog,
|
||||
[this, input_subsystem, &system] {
|
||||
CallConfigureDialog<ConfigureDebugController>(*this, input_subsystem,
|
||||
profiles.get(), system);
|
||||
});
|
||||
connect(advanced, &ConfigureInputAdvanced::CallMouseConfigDialog, [this, input_subsystem] {
|
||||
CallConfigureDialog<ConfigureMouseAdvanced>(*this, input_subsystem);
|
||||
|
@ -184,22 +186,8 @@ QList<QWidget*> ConfigureInput::GetSubTabs() const {
|
|||
void ConfigureInput::ApplyConfiguration() {
|
||||
for (auto* controller : player_controllers) {
|
||||
controller->ApplyConfiguration();
|
||||
controller->TryDisconnectSelectedController();
|
||||
}
|
||||
|
||||
// This emulates a delay between disconnecting and reconnecting controllers as some games
|
||||
// do not respond to a change in controller type if it was instantaneous.
|
||||
using namespace std::chrono_literals;
|
||||
std::this_thread::sleep_for(150ms);
|
||||
|
||||
for (auto* controller : player_controllers) {
|
||||
controller->TryConnectSelectedController();
|
||||
}
|
||||
|
||||
// This emulates a delay between disconnecting and reconnecting controllers as some games
|
||||
// do not respond to a change in controller type if it was instantaneous.
|
||||
std::this_thread::sleep_for(150ms);
|
||||
|
||||
advanced->ApplyConfiguration();
|
||||
|
||||
const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue();
|
||||
|
@ -223,8 +211,10 @@ void ConfigureInput::RetranslateUI() {
|
|||
}
|
||||
|
||||
void ConfigureInput::LoadConfiguration() {
|
||||
const auto* handheld = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
|
||||
|
||||
LoadPlayerControllerIndices();
|
||||
UpdateDockedState(Settings::values.players.GetValue()[8].connected);
|
||||
UpdateDockedState(handheld->IsConnected());
|
||||
|
||||
ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue());
|
||||
ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue());
|
||||
|
@ -232,9 +222,16 @@ void ConfigureInput::LoadConfiguration() {
|
|||
|
||||
void ConfigureInput::LoadPlayerControllerIndices() {
|
||||
for (std::size_t i = 0; i < player_connected.size(); ++i) {
|
||||
const auto connected = Settings::values.players.GetValue()[i].connected ||
|
||||
(i == 0 && Settings::values.players.GetValue()[8].connected);
|
||||
player_connected[i]->setChecked(connected);
|
||||
if (i == 0) {
|
||||
auto* handheld =
|
||||
system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
|
||||
if (handheld->IsConnected()) {
|
||||
player_connected[i]->setChecked(true);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const auto* controller = system.HIDCore().GetEmulatedControllerByIndex(i);
|
||||
player_connected[i]->setChecked(controller->IsConnected());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,8 @@ public:
|
|||
~ConfigureInput() override;
|
||||
|
||||
/// Initializes the input dialog with the given input subsystem.
|
||||
void Initialize(InputCommon::InputSubsystem* input_subsystem_, std::size_t max_players = 8);
|
||||
void Initialize(InputCommon::InputSubsystem* input_subsystem_, Core::System& system,
|
||||
std::size_t max_players = 8);
|
||||
|
||||
/// Save all button configurations to settings file.
|
||||
void ApplyConfiguration();
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -38,14 +38,22 @@ class InputSubsystem;
|
|||
}
|
||||
|
||||
namespace InputCommon::Polling {
|
||||
class DevicePoller;
|
||||
enum class DeviceType;
|
||||
enum class InputType;
|
||||
} // namespace InputCommon::Polling
|
||||
|
||||
namespace Ui {
|
||||
class ConfigureInputPlayer;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Core::HID {
|
||||
class EmulatedController;
|
||||
enum class NpadType : u8;
|
||||
} // namespace Core::HID
|
||||
|
||||
class ConfigureInputPlayer : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -59,18 +67,6 @@ public:
|
|||
/// Save all button configurations to settings file.
|
||||
void ApplyConfiguration();
|
||||
|
||||
/**
|
||||
* Attempts to connect the currently selected controller in the HID backend.
|
||||
* This function will not do anything if it is not connected in the frontend.
|
||||
*/
|
||||
void TryConnectSelectedController();
|
||||
|
||||
/**
|
||||
* Attempts to disconnect the currently selected controller in the HID backend.
|
||||
* This function will not do anything if the configuration has not changed.
|
||||
*/
|
||||
void TryDisconnectSelectedController();
|
||||
|
||||
/// Set the connection state checkbox (used to sync state).
|
||||
void ConnectPlayer(bool connected);
|
||||
|
||||
|
@ -104,6 +100,10 @@ protected:
|
|||
void showEvent(QShowEvent* event) override;
|
||||
|
||||
private:
|
||||
QString ButtonToText(const Common::ParamPackage& param);
|
||||
|
||||
QString AnalogToText(const Common::ParamPackage& param, const std::string& dir);
|
||||
|
||||
void changeEvent(QEvent* event) override;
|
||||
void RetranslateUI();
|
||||
|
||||
|
@ -113,7 +113,7 @@ private:
|
|||
/// Called when the button was pressed.
|
||||
void HandleClick(QPushButton* button, std::size_t button_id,
|
||||
std::function<void(const Common::ParamPackage&)> new_input_setter,
|
||||
InputCommon::Polling::DeviceType type);
|
||||
InputCommon::Polling::InputType type);
|
||||
|
||||
/// Finish polling and configure input using the input_setter.
|
||||
void SetPollingResult(const Common::ParamPackage& params, bool abort);
|
||||
|
@ -134,17 +134,14 @@ private:
|
|||
void SetConnectableControllers();
|
||||
|
||||
/// Gets the Controller Type for a given controller combobox index.
|
||||
Settings::ControllerType GetControllerTypeFromIndex(int index) const;
|
||||
Core::HID::NpadType GetControllerTypeFromIndex(int index) const;
|
||||
|
||||
/// Gets the controller combobox index for a given Controller Type.
|
||||
int GetIndexFromControllerType(Settings::ControllerType type) const;
|
||||
int GetIndexFromControllerType(Core::HID::NpadType type) const;
|
||||
|
||||
/// Update the available input devices.
|
||||
void UpdateInputDevices();
|
||||
|
||||
/// Update the current controller icon.
|
||||
void UpdateControllerIcon();
|
||||
|
||||
/// Hides and disables controller settings based on the current controller type.
|
||||
void UpdateControllerAvailableButtons();
|
||||
|
||||
|
@ -185,7 +182,7 @@ private:
|
|||
std::unique_ptr<QTimer> poll_timer;
|
||||
|
||||
/// Stores a pair of "Connected Controllers" combobox index and Controller Type enum.
|
||||
std::vector<std::pair<int, Settings::ControllerType>> index_controller_type_pairs;
|
||||
std::vector<std::pair<int, Core::HID::NpadType>> index_controller_type_pairs;
|
||||
|
||||
static constexpr int PLAYER_COUNT = 8;
|
||||
std::array<QCheckBox*, PLAYER_COUNT> player_connected_checkbox;
|
||||
|
@ -193,9 +190,7 @@ private:
|
|||
/// This will be the the setting function when an input is awaiting configuration.
|
||||
std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
|
||||
|
||||
std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param;
|
||||
std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param;
|
||||
std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions> motions_param;
|
||||
Core::HID::EmulatedController* emulated_controller;
|
||||
|
||||
static constexpr int ANALOG_SUB_BUTTONS_NUM = 4;
|
||||
|
||||
|
@ -221,15 +216,9 @@ private:
|
|||
|
||||
static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons;
|
||||
|
||||
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers;
|
||||
|
||||
/// A flag to indicate that the "Map Analog Stick" pop-up has been shown and accepted once.
|
||||
bool map_analog_stick_accepted{};
|
||||
|
||||
/// A flag to indicate if keyboard keys are okay when configuring an input. If this is false,
|
||||
/// keyboard events are ignored.
|
||||
bool want_keyboard_mouse{};
|
||||
|
||||
/// List of physical devices users can map with. If a SDL backed device is selected, then you
|
||||
/// can use this device to get a default mapping.
|
||||
std::vector<Common::ParamPackage> input_devices;
|
||||
|
|
|
@ -89,31 +89,6 @@
|
|||
<height>21</height>
|
||||
</size>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Pro Controller</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Dual Joycons</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Left Joycon</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Right Joycon</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Handheld</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -148,16 +123,6 @@
|
|||
<height>21</height>
|
||||
</size>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Any</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Keyboard/Mouse</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -7,9 +7,11 @@
|
|||
#include <array>
|
||||
#include <QFrame>
|
||||
#include <QPointer>
|
||||
#include "common/input.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/frontend/input.h"
|
||||
#include "yuzu/debugger/controller.h"
|
||||
#include "core/hid/emulated_controller.h"
|
||||
#include "core/hid/hid_core.h"
|
||||
#include "core/hid/hid_types.h"
|
||||
|
||||
class QLabel;
|
||||
|
||||
|
@ -24,17 +26,26 @@ public:
|
|||
explicit PlayerControlPreview(QWidget* parent);
|
||||
~PlayerControlPreview() override;
|
||||
|
||||
void SetPlayerInput(std::size_t index, const ButtonParam& buttons_param,
|
||||
const AnalogParam& analogs_param);
|
||||
void SetPlayerInputRaw(std::size_t index, const Settings::ButtonsRaw& buttons_,
|
||||
Settings::AnalogsRaw analogs_);
|
||||
void SetConnectedStatus(bool checked);
|
||||
void SetControllerType(Settings::ControllerType type);
|
||||
// Sets the emulated controller to be displayed
|
||||
void SetController(Core::HID::EmulatedController* controller);
|
||||
|
||||
// Disables events from the emulated controller
|
||||
void UnloadController();
|
||||
|
||||
// Starts blinking animation at the button specified
|
||||
void BeginMappingButton(std::size_t button_id);
|
||||
void BeginMappingAnalog(std::size_t button_id);
|
||||
|
||||
// Starts moving animation at the stick specified
|
||||
void BeginMappingAnalog(std::size_t stick_id);
|
||||
|
||||
// Stops any ongoing animation
|
||||
void EndMapping();
|
||||
|
||||
// Handles emulated controller events
|
||||
void ControllerUpdate(Core::HID::ControllerTriggerType type);
|
||||
|
||||
// Updates input on sheduled interval
|
||||
void UpdateInput();
|
||||
void SetCallBack(ControllerCallback callback_);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
|
@ -63,22 +74,6 @@ private:
|
|||
SR,
|
||||
};
|
||||
|
||||
struct AxisValue {
|
||||
QPointF value{};
|
||||
QPointF raw_value{};
|
||||
Input::AnalogProperties properties{};
|
||||
int size{};
|
||||
QPoint offset{};
|
||||
bool active{};
|
||||
};
|
||||
|
||||
struct LedPattern {
|
||||
bool position1;
|
||||
bool position2;
|
||||
bool position3;
|
||||
bool position4;
|
||||
};
|
||||
|
||||
struct ColorMapping {
|
||||
QColor outline{};
|
||||
QColor primary{};
|
||||
|
@ -101,7 +96,6 @@ private:
|
|||
QColor deadzone{};
|
||||
};
|
||||
|
||||
static LedPattern GetColorPattern(std::size_t index, bool player_on);
|
||||
void UpdateColors();
|
||||
void ResetInputs();
|
||||
|
||||
|
@ -122,47 +116,75 @@ private:
|
|||
void DrawGCBody(QPainter& p, QPointF center);
|
||||
|
||||
// Draw triggers functions
|
||||
void DrawProTriggers(QPainter& p, QPointF center, bool left_pressed, bool right_pressed);
|
||||
void DrawGCTriggers(QPainter& p, QPointF center, bool left_pressed, bool right_pressed);
|
||||
void DrawHandheldTriggers(QPainter& p, QPointF center, bool left_pressed, bool right_pressed);
|
||||
void DrawDualTriggers(QPainter& p, QPointF center, bool left_pressed, bool right_pressed);
|
||||
void DrawDualTriggersTopView(QPainter& p, QPointF center, bool left_pressed,
|
||||
bool right_pressed);
|
||||
void DrawDualZTriggersTopView(QPainter& p, QPointF center, bool left_pressed,
|
||||
bool right_pressed);
|
||||
void DrawLeftTriggers(QPainter& p, QPointF center, bool left_pressed);
|
||||
void DrawLeftZTriggers(QPainter& p, QPointF center, bool left_pressed);
|
||||
void DrawLeftTriggersTopView(QPainter& p, QPointF center, bool left_pressed);
|
||||
void DrawLeftZTriggersTopView(QPainter& p, QPointF center, bool left_pressed);
|
||||
void DrawRightTriggers(QPainter& p, QPointF center, bool right_pressed);
|
||||
void DrawRightZTriggers(QPainter& p, QPointF center, bool right_pressed);
|
||||
void DrawRightTriggersTopView(QPainter& p, QPointF center, bool right_pressed);
|
||||
void DrawRightZTriggersTopView(QPainter& p, QPointF center, bool right_pressed);
|
||||
void DrawProTriggers(QPainter& p, QPointF center,
|
||||
const Common::Input::ButtonStatus& left_pressed,
|
||||
const Common::Input::ButtonStatus& right_pressed);
|
||||
void DrawGCTriggers(QPainter& p, QPointF center, Common::Input::TriggerStatus left_trigger,
|
||||
Common::Input::TriggerStatus right_trigger);
|
||||
void DrawHandheldTriggers(QPainter& p, QPointF center,
|
||||
const Common::Input::ButtonStatus& left_pressed,
|
||||
const Common::Input::ButtonStatus& right_pressed);
|
||||
void DrawDualTriggers(QPainter& p, QPointF center,
|
||||
const Common::Input::ButtonStatus& left_pressed,
|
||||
const Common::Input::ButtonStatus& right_pressed);
|
||||
void DrawDualTriggersTopView(QPainter& p, QPointF center,
|
||||
const Common::Input::ButtonStatus& left_pressed,
|
||||
const Common::Input::ButtonStatus& right_pressed);
|
||||
void DrawDualZTriggersTopView(QPainter& p, QPointF center,
|
||||
const Common::Input::ButtonStatus& left_pressed,
|
||||
const Common::Input::ButtonStatus& right_pressed);
|
||||
void DrawLeftTriggers(QPainter& p, QPointF center,
|
||||
const Common::Input::ButtonStatus& left_pressed);
|
||||
void DrawLeftZTriggers(QPainter& p, QPointF center,
|
||||
const Common::Input::ButtonStatus& left_pressed);
|
||||
void DrawLeftTriggersTopView(QPainter& p, QPointF center,
|
||||
const Common::Input::ButtonStatus& left_pressed);
|
||||
void DrawLeftZTriggersTopView(QPainter& p, QPointF center,
|
||||
const Common::Input::ButtonStatus& left_pressed);
|
||||
void DrawRightTriggers(QPainter& p, QPointF center,
|
||||
const Common::Input::ButtonStatus& right_pressed);
|
||||
void DrawRightZTriggers(QPainter& p, QPointF center,
|
||||
const Common::Input::ButtonStatus& right_pressed);
|
||||
void DrawRightTriggersTopView(QPainter& p, QPointF center,
|
||||
const Common::Input::ButtonStatus& right_pressed);
|
||||
void DrawRightZTriggersTopView(QPainter& p, QPointF center,
|
||||
const Common::Input::ButtonStatus& right_pressed);
|
||||
|
||||
// Draw joystick functions
|
||||
void DrawJoystick(QPainter& p, QPointF center, float size, bool pressed);
|
||||
void DrawJoystickSideview(QPainter& p, QPointF center, float angle, float size, bool pressed);
|
||||
void DrawJoystick(QPainter& p, QPointF center, float size,
|
||||
const Common::Input::ButtonStatus& pressed);
|
||||
void DrawJoystickSideview(QPainter& p, QPointF center, float angle, float size,
|
||||
const Common::Input::ButtonStatus& pressed);
|
||||
void DrawRawJoystick(QPainter& p, QPointF center_left, QPointF center_right);
|
||||
void DrawJoystickProperties(QPainter& p, QPointF center,
|
||||
const Input::AnalogProperties& properties);
|
||||
void DrawJoystickDot(QPainter& p, QPointF center, QPointF value,
|
||||
const Input::AnalogProperties& properties);
|
||||
void DrawProJoystick(QPainter& p, QPointF center, QPointF offset, float scalar, bool pressed);
|
||||
void DrawGCJoystick(QPainter& p, QPointF center, bool pressed);
|
||||
const Common::Input::AnalogProperties& properties);
|
||||
void DrawJoystickDot(QPainter& p, QPointF center, const Common::Input::StickStatus& stick,
|
||||
bool raw);
|
||||
void DrawProJoystick(QPainter& p, QPointF center, QPointF offset, float scalar,
|
||||
const Common::Input::ButtonStatus& pressed);
|
||||
void DrawGCJoystick(QPainter& p, QPointF center, const Common::Input::ButtonStatus& pressed);
|
||||
|
||||
// Draw button functions
|
||||
void DrawCircleButton(QPainter& p, QPointF center, bool pressed, float button_size);
|
||||
void DrawRoundButton(QPainter& p, QPointF center, bool pressed, float width, float height,
|
||||
Direction direction = Direction::None, float radius = 2);
|
||||
void DrawMinusButton(QPainter& p, QPointF center, bool pressed, int button_size);
|
||||
void DrawPlusButton(QPainter& p, QPointF center, bool pressed, int button_size);
|
||||
void DrawGCButtonX(QPainter& p, QPointF center, bool pressed);
|
||||
void DrawGCButtonY(QPainter& p, QPointF center, bool pressed);
|
||||
void DrawGCButtonZ(QPainter& p, QPointF center, bool pressed);
|
||||
void DrawCircleButton(QPainter& p, QPointF center, const Common::Input::ButtonStatus& pressed,
|
||||
float button_size);
|
||||
void DrawRoundButton(QPainter& p, QPointF center, const Common::Input::ButtonStatus& pressed,
|
||||
float width, float height, Direction direction = Direction::None,
|
||||
float radius = 2);
|
||||
void DrawMinusButton(QPainter& p, QPointF center, const Common::Input::ButtonStatus& pressed,
|
||||
int button_size);
|
||||
void DrawPlusButton(QPainter& p, QPointF center, const Common::Input::ButtonStatus& pressed,
|
||||
int button_size);
|
||||
void DrawGCButtonX(QPainter& p, QPointF center, const Common::Input::ButtonStatus& pressed);
|
||||
void DrawGCButtonY(QPainter& p, QPointF center, const Common::Input::ButtonStatus& pressed);
|
||||
void DrawGCButtonZ(QPainter& p, QPointF center, const Common::Input::ButtonStatus& pressed);
|
||||
void DrawArrowButtonOutline(QPainter& p, const QPointF center, float size = 1.0f);
|
||||
void DrawArrowButton(QPainter& p, QPointF center, Direction direction, bool pressed,
|
||||
float size = 1.0f);
|
||||
void DrawTriggerButton(QPainter& p, QPointF center, Direction direction, bool pressed);
|
||||
void DrawArrowButton(QPainter& p, QPointF center, Direction direction,
|
||||
const Common::Input::ButtonStatus& pressed, float size = 1.0f);
|
||||
void DrawTriggerButton(QPainter& p, QPointF center, Direction direction,
|
||||
const Common::Input::ButtonStatus& pressed);
|
||||
|
||||
// Draw battery functions
|
||||
void DrawBattery(QPainter& p, QPointF center, Common::Input::BatteryLevel battery);
|
||||
|
||||
// Draw icon functions
|
||||
void DrawSymbol(QPainter& p, QPointF center, Symbol symbol, float icon_size);
|
||||
|
@ -178,24 +200,23 @@ private:
|
|||
void SetTextFont(QPainter& p, float text_size,
|
||||
const QString& font_family = QStringLiteral("sans-serif"));
|
||||
|
||||
using ButtonArray =
|
||||
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::BUTTON_NS_END>;
|
||||
using StickArray =
|
||||
std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>;
|
||||
bool is_controller_set{};
|
||||
bool is_connected{};
|
||||
bool needs_redraw{};
|
||||
Core::HID::NpadType controller_type;
|
||||
|
||||
ControllerCallback controller_callback;
|
||||
bool is_enabled{};
|
||||
bool mapping_active{};
|
||||
int blink_counter{};
|
||||
int callback_key;
|
||||
QColor button_color{};
|
||||
ColorMapping colors{};
|
||||
std::array<QColor, 4> led_color{};
|
||||
ButtonArray buttons{};
|
||||
StickArray sticks{};
|
||||
Core::HID::LedPattern led_pattern{0, 0, 0, 0};
|
||||
std::size_t player_index{};
|
||||
std::size_t button_mapping_index{Settings::NativeButton::BUTTON_NS_END};
|
||||
std::size_t analog_mapping_index{Settings::NativeAnalog::NUM_STICKS_HID};
|
||||
std::array<AxisValue, Settings::NativeAnalog::NUM_STICKS_HID> axis_values{};
|
||||
std::array<bool, Settings::NativeButton::NumButtons> button_values{};
|
||||
Settings::ControllerType controller_type{Settings::ControllerType::ProController};
|
||||
Core::HID::EmulatedController* controller;
|
||||
std::size_t button_mapping_index{Settings::NativeButton::NumButtons};
|
||||
std::size_t analog_mapping_index{Settings::NativeAnalog::NumAnalogs};
|
||||
Core::HID::ButtonValues button_values{};
|
||||
Core::HID::SticksValues stick_values{};
|
||||
Core::HID::TriggerValues trigger_values{};
|
||||
Core::HID::BatteryValues battery_values{};
|
||||
};
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "input_common/drivers/udp_client.h"
|
||||
#include "input_common/helpers/udp_protocol.h"
|
||||
#include "input_common/main.h"
|
||||
#include "input_common/udp/client.h"
|
||||
#include "input_common/udp/udp.h"
|
||||
#include "ui_configure_motion_touch.h"
|
||||
#include "yuzu/configuration/configure_motion_touch.h"
|
||||
#include "yuzu/configuration/configure_touch_from_button.h"
|
||||
|
|
|
@ -11,8 +11,11 @@
|
|||
|
||||
#include "common/assert.h"
|
||||
#include "common/param_package.h"
|
||||
#include "input_common/drivers/keyboard.h"
|
||||
#include "input_common/drivers/mouse.h"
|
||||
#include "input_common/main.h"
|
||||
#include "ui_configure_mouse_advanced.h"
|
||||
#include "yuzu/bootmanager.h"
|
||||
#include "yuzu/configuration/config.h"
|
||||
#include "yuzu/configuration/configure_mouse_advanced.h"
|
||||
|
||||
|
@ -101,7 +104,7 @@ ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent,
|
|||
[=, this](const Common::ParamPackage& params) {
|
||||
buttons_param[button_id] = params;
|
||||
},
|
||||
InputCommon::Polling::DeviceType::Button);
|
||||
InputCommon::Polling::InputType::Button);
|
||||
});
|
||||
connect(button, &QPushButton::customContextMenuRequested,
|
||||
[=, this](const QPoint& menu_location) {
|
||||
|
@ -127,14 +130,11 @@ ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent,
|
|||
connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
|
||||
|
||||
connect(poll_timer.get(), &QTimer::timeout, [this] {
|
||||
Common::ParamPackage params;
|
||||
for (auto& poller : device_pollers) {
|
||||
params = poller->GetNextInput();
|
||||
const auto& params = input_subsystem->GetNextInput();
|
||||
if (params.Has("engine")) {
|
||||
SetPollingResult(params, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
LoadConfiguration();
|
||||
|
@ -196,26 +196,13 @@ void ConfigureMouseAdvanced::UpdateButtonLabels() {
|
|||
|
||||
void ConfigureMouseAdvanced::HandleClick(
|
||||
QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
|
||||
InputCommon::Polling::DeviceType type) {
|
||||
InputCommon::Polling::InputType type) {
|
||||
button->setText(tr("[press key]"));
|
||||
button->setFocus();
|
||||
|
||||
// Keyboard keys or mouse buttons can only be used as button devices
|
||||
want_keyboard_mouse = type == InputCommon::Polling::DeviceType::Button;
|
||||
if (want_keyboard_mouse) {
|
||||
const auto iter = std::find(button_map.begin(), button_map.end(), button);
|
||||
ASSERT(iter != button_map.end());
|
||||
const auto index = std::distance(button_map.begin(), iter);
|
||||
ASSERT(index < Settings::NativeButton::NumButtons && index >= 0);
|
||||
}
|
||||
|
||||
input_setter = new_input_setter;
|
||||
|
||||
device_pollers = input_subsystem->GetPollers(type);
|
||||
|
||||
for (auto& poller : device_pollers) {
|
||||
poller->Start();
|
||||
}
|
||||
input_subsystem->BeginMapping(type);
|
||||
|
||||
QWidget::grabMouse();
|
||||
QWidget::grabKeyboard();
|
||||
|
@ -227,9 +214,7 @@ void ConfigureMouseAdvanced::HandleClick(
|
|||
void ConfigureMouseAdvanced::SetPollingResult(const Common::ParamPackage& params, bool abort) {
|
||||
timeout_timer->stop();
|
||||
poll_timer->stop();
|
||||
for (auto& poller : device_pollers) {
|
||||
poller->Stop();
|
||||
}
|
||||
input_subsystem->StopMapping();
|
||||
|
||||
QWidget::releaseMouse();
|
||||
QWidget::releaseKeyboard();
|
||||
|
@ -247,15 +232,8 @@ void ConfigureMouseAdvanced::mousePressEvent(QMouseEvent* event) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (want_keyboard_mouse) {
|
||||
SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->button())},
|
||||
false);
|
||||
} else {
|
||||
// We don't want any mouse buttons, so don't stop polling
|
||||
return;
|
||||
}
|
||||
|
||||
SetPollingResult({}, true);
|
||||
const auto button = GRenderWindow::QtButtonToMouseButton(event->button());
|
||||
input_subsystem->GetMouse()->PressButton(0, 0, 0, 0, button);
|
||||
}
|
||||
|
||||
void ConfigureMouseAdvanced::keyPressEvent(QKeyEvent* event) {
|
||||
|
@ -264,13 +242,6 @@ void ConfigureMouseAdvanced::keyPressEvent(QKeyEvent* event) {
|
|||
}
|
||||
|
||||
if (event->key() != Qt::Key_Escape) {
|
||||
if (want_keyboard_mouse) {
|
||||
SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
|
||||
false);
|
||||
} else {
|
||||
// Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling
|
||||
return;
|
||||
input_subsystem->GetKeyboard()->PressKey(event->key());
|
||||
}
|
||||
}
|
||||
SetPollingResult({}, true);
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue