Merge pull request #2497 from wwylele/input-2
Refactor input emulation & add SDL gamepad supportmaster
commit
423ab5e2bc
@ -0,0 +1,120 @@
|
|||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <vector>
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/param_package.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
constexpr char KEY_VALUE_SEPARATOR = ':';
|
||||||
|
constexpr char PARAM_SEPARATOR = ',';
|
||||||
|
constexpr char ESCAPE_CHARACTER = '$';
|
||||||
|
const std::string KEY_VALUE_SEPARATOR_ESCAPE{ESCAPE_CHARACTER, '0'};
|
||||||
|
const std::string PARAM_SEPARATOR_ESCAPE{ESCAPE_CHARACTER, '1'};
|
||||||
|
const std::string ESCAPE_CHARACTER_ESCAPE{ESCAPE_CHARACTER, '2'};
|
||||||
|
|
||||||
|
ParamPackage::ParamPackage(const std::string& serialized) {
|
||||||
|
std::vector<std::string> pairs;
|
||||||
|
Common::SplitString(serialized, PARAM_SEPARATOR, pairs);
|
||||||
|
|
||||||
|
for (const std::string& pair : pairs) {
|
||||||
|
std::vector<std::string> key_value;
|
||||||
|
Common::SplitString(pair, KEY_VALUE_SEPARATOR, key_value);
|
||||||
|
if (key_value.size() != 2) {
|
||||||
|
LOG_ERROR(Common, "invalid key pair %s", pair.c_str());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::string& part : key_value) {
|
||||||
|
part = Common::ReplaceAll(part, KEY_VALUE_SEPARATOR_ESCAPE, {KEY_VALUE_SEPARATOR});
|
||||||
|
part = Common::ReplaceAll(part, PARAM_SEPARATOR_ESCAPE, {PARAM_SEPARATOR});
|
||||||
|
part = Common::ReplaceAll(part, ESCAPE_CHARACTER_ESCAPE, {ESCAPE_CHARACTER});
|
||||||
|
}
|
||||||
|
|
||||||
|
Set(key_value[0], key_value[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ParamPackage::ParamPackage(std::initializer_list<DataType::value_type> list) : data(list) {}
|
||||||
|
|
||||||
|
std::string ParamPackage::Serialize() const {
|
||||||
|
if (data.empty())
|
||||||
|
return "";
|
||||||
|
|
||||||
|
std::string result;
|
||||||
|
|
||||||
|
for (const auto& pair : data) {
|
||||||
|
std::array<std::string, 2> key_value{{pair.first, pair.second}};
|
||||||
|
for (std::string& part : key_value) {
|
||||||
|
part = Common::ReplaceAll(part, {ESCAPE_CHARACTER}, ESCAPE_CHARACTER_ESCAPE);
|
||||||
|
part = Common::ReplaceAll(part, {PARAM_SEPARATOR}, PARAM_SEPARATOR_ESCAPE);
|
||||||
|
part = Common::ReplaceAll(part, {KEY_VALUE_SEPARATOR}, KEY_VALUE_SEPARATOR_ESCAPE);
|
||||||
|
}
|
||||||
|
result += key_value[0] + KEY_VALUE_SEPARATOR + key_value[1] + PARAM_SEPARATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.pop_back(); // discard the trailing PARAM_SEPARATOR
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ParamPackage::Get(const std::string& key, const std::string& default_value) const {
|
||||||
|
auto pair = data.find(key);
|
||||||
|
if (pair == data.end()) {
|
||||||
|
LOG_DEBUG(Common, "key %s not found", key.c_str());
|
||||||
|
return default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pair->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ParamPackage::Get(const std::string& key, int default_value) const {
|
||||||
|
auto pair = data.find(key);
|
||||||
|
if (pair == data.end()) {
|
||||||
|
LOG_DEBUG(Common, "key %s not found", key.c_str());
|
||||||
|
return default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return std::stoi(pair->second);
|
||||||
|
} catch (const std::logic_error&) {
|
||||||
|
LOG_ERROR(Common, "failed to convert %s to int", pair->second.c_str());
|
||||||
|
return default_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float ParamPackage::Get(const std::string& key, float default_value) const {
|
||||||
|
auto pair = data.find(key);
|
||||||
|
if (pair == data.end()) {
|
||||||
|
LOG_DEBUG(Common, "key %s not found", key.c_str());
|
||||||
|
return default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return std::stof(pair->second);
|
||||||
|
} catch (const std::logic_error&) {
|
||||||
|
LOG_ERROR(Common, "failed to convert %s to float", pair->second.c_str());
|
||||||
|
return default_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ParamPackage::Set(const std::string& key, const std::string& value) {
|
||||||
|
data[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ParamPackage::Set(const std::string& key, int value) {
|
||||||
|
data[key] = std::to_string(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ParamPackage::Set(const std::string& key, float value) {
|
||||||
|
data[key] = std::to_string(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ParamPackage::Has(const std::string& key) const {
|
||||||
|
return data.find(key) != data.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common
|
@ -0,0 +1,40 @@
|
|||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
/// A string-based key-value container supporting serializing to and deserializing from a string
|
||||||
|
class ParamPackage {
|
||||||
|
public:
|
||||||
|
using DataType = std::unordered_map<std::string, std::string>;
|
||||||
|
|
||||||
|
ParamPackage() = default;
|
||||||
|
explicit ParamPackage(const std::string& serialized);
|
||||||
|
ParamPackage(std::initializer_list<DataType::value_type> list);
|
||||||
|
ParamPackage(const ParamPackage& other) = default;
|
||||||
|
ParamPackage(ParamPackage&& other) = default;
|
||||||
|
|
||||||
|
ParamPackage& operator=(const ParamPackage& other) = default;
|
||||||
|
ParamPackage& operator=(ParamPackage&& other) = default;
|
||||||
|
|
||||||
|
std::string Serialize() const;
|
||||||
|
std::string Get(const std::string& key, const std::string& default_value) const;
|
||||||
|
int Get(const std::string& key, int default_value) const;
|
||||||
|
float Get(const std::string& key, float default_value) const;
|
||||||
|
void Set(const std::string& key, const std::string& value);
|
||||||
|
void Set(const std::string& key, int value);
|
||||||
|
void Set(const std::string& key, float value);
|
||||||
|
bool Has(const std::string& key) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DataType data;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Common
|
@ -0,0 +1,110 @@
|
|||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/param_package.h"
|
||||||
|
|
||||||
|
namespace Input {
|
||||||
|
|
||||||
|
/// An abstract class template for an input device (a button, an analog input, etc.).
|
||||||
|
template <typename StatusType>
|
||||||
|
class InputDevice {
|
||||||
|
public:
|
||||||
|
virtual ~InputDevice() = default;
|
||||||
|
virtual StatusType GetStatus() const {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 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 %s already registered", name.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 %s not registered", name.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an input device from given paramters.
|
||||||
|
* @tparam InputDeviceType the type of input devices to create
|
||||||
|
* @param params a serialized ParamPackage string contains all parameters for creating the device
|
||||||
|
*/
|
||||||
|
template <typename InputDeviceType>
|
||||||
|
std::unique_ptr<InputDeviceType> CreateDevice(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: %s", engine.c_str());
|
||||||
|
}
|
||||||
|
return std::make_unique<InputDeviceType>();
|
||||||
|
}
|
||||||
|
return pair->second->Create(package);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A button device is an input device that returns bool as status.
|
||||||
|
* true for pressed; false for released.
|
||||||
|
*/
|
||||||
|
using ButtonDevice = InputDevice<bool>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An analog device is an input device that returns a tuple of x and y coordinates as status. The
|
||||||
|
* coordinates are within the unit circle. x+ is defined as right direction, and y+ is defined as up
|
||||||
|
* direction
|
||||||
|
*/
|
||||||
|
using AnalogDevice = InputDevice<std::tuple<float, float>>;
|
||||||
|
|
||||||
|
} // namespace Input
|
@ -1,152 +0,0 @@
|
|||||||
// Copyright 2014 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include "core/frontend/emu_window.h"
|
|
||||||
#include "core/frontend/key_map.h"
|
|
||||||
|
|
||||||
namespace KeyMap {
|
|
||||||
|
|
||||||
// TODO (wwylele): currently we treat c-stick as four direction buttons
|
|
||||||
// and map it directly to EmuWindow::ButtonPressed.
|
|
||||||
// It should go the analog input way like circle pad does.
|
|
||||||
const std::array<KeyTarget, Settings::NativeInput::NUM_INPUTS> mapping_targets = {{
|
|
||||||
Service::HID::PAD_A,
|
|
||||||
Service::HID::PAD_B,
|
|
||||||
Service::HID::PAD_X,
|
|
||||||
Service::HID::PAD_Y,
|
|
||||||
Service::HID::PAD_L,
|
|
||||||
Service::HID::PAD_R,
|
|
||||||
Service::HID::PAD_ZL,
|
|
||||||
Service::HID::PAD_ZR,
|
|
||||||
Service::HID::PAD_START,
|
|
||||||
Service::HID::PAD_SELECT,
|
|
||||||
Service::HID::PAD_NONE,
|
|
||||||
Service::HID::PAD_UP,
|
|
||||||
Service::HID::PAD_DOWN,
|
|
||||||
Service::HID::PAD_LEFT,
|
|
||||||
Service::HID::PAD_RIGHT,
|
|
||||||
Service::HID::PAD_C_UP,
|
|
||||||
Service::HID::PAD_C_DOWN,
|
|
||||||
Service::HID::PAD_C_LEFT,
|
|
||||||
Service::HID::PAD_C_RIGHT,
|
|
||||||
|
|
||||||
IndirectTarget::CirclePadUp,
|
|
||||||
IndirectTarget::CirclePadDown,
|
|
||||||
IndirectTarget::CirclePadLeft,
|
|
||||||
IndirectTarget::CirclePadRight,
|
|
||||||
IndirectTarget::CirclePadModifier,
|
|
||||||
}};
|
|
||||||
|
|
||||||
static std::map<HostDeviceKey, KeyTarget> key_map;
|
|
||||||
static int next_device_id = 0;
|
|
||||||
|
|
||||||
static bool circle_pad_up = false;
|
|
||||||
static bool circle_pad_down = false;
|
|
||||||
static bool circle_pad_left = false;
|
|
||||||
static bool circle_pad_right = false;
|
|
||||||
static bool circle_pad_modifier = false;
|
|
||||||
|
|
||||||
static void UpdateCirclePad(EmuWindow& emu_window) {
|
|
||||||
constexpr float SQRT_HALF = 0.707106781f;
|
|
||||||
int x = 0, y = 0;
|
|
||||||
|
|
||||||
if (circle_pad_right)
|
|
||||||
++x;
|
|
||||||
if (circle_pad_left)
|
|
||||||
--x;
|
|
||||||
if (circle_pad_up)
|
|
||||||
++y;
|
|
||||||
if (circle_pad_down)
|
|
||||||
--y;
|
|
||||||
|
|
||||||
float modifier = circle_pad_modifier ? Settings::values.pad_circle_modifier_scale : 1.0f;
|
|
||||||
emu_window.CirclePadUpdated(x * modifier * (y == 0 ? 1.0f : SQRT_HALF),
|
|
||||||
y * modifier * (x == 0 ? 1.0f : SQRT_HALF));
|
|
||||||
}
|
|
||||||
|
|
||||||
int NewDeviceId() {
|
|
||||||
return next_device_id++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetKeyMapping(HostDeviceKey key, KeyTarget target) {
|
|
||||||
key_map[key] = target;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClearKeyMapping(int device_id) {
|
|
||||||
auto iter = key_map.begin();
|
|
||||||
while (iter != key_map.end()) {
|
|
||||||
if (iter->first.device_id == device_id)
|
|
||||||
key_map.erase(iter++);
|
|
||||||
else
|
|
||||||
++iter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PressKey(EmuWindow& emu_window, HostDeviceKey key) {
|
|
||||||
auto target = key_map.find(key);
|
|
||||||
if (target == key_map.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (target->second.direct) {
|
|
||||||
emu_window.ButtonPressed({{target->second.target.direct_target_hex}});
|
|
||||||
} else {
|
|
||||||
switch (target->second.target.indirect_target) {
|
|
||||||
case IndirectTarget::CirclePadUp:
|
|
||||||
circle_pad_up = true;
|
|
||||||
UpdateCirclePad(emu_window);
|
|
||||||
break;
|
|
||||||
case IndirectTarget::CirclePadDown:
|
|
||||||
circle_pad_down = true;
|
|
||||||
UpdateCirclePad(emu_window);
|
|
||||||
break;
|
|
||||||
case IndirectTarget::CirclePadLeft:
|
|
||||||
circle_pad_left = true;
|
|
||||||
UpdateCirclePad(emu_window);
|
|
||||||
break;
|
|
||||||
case IndirectTarget::CirclePadRight:
|
|
||||||
circle_pad_right = true;
|
|
||||||
UpdateCirclePad(emu_window);
|
|
||||||
break;
|
|
||||||
case IndirectTarget::CirclePadModifier:
|
|
||||||
circle_pad_modifier = true;
|
|
||||||
UpdateCirclePad(emu_window);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReleaseKey(EmuWindow& emu_window, HostDeviceKey key) {
|
|
||||||
auto target = key_map.find(key);
|
|
||||||
if (target == key_map.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (target->second.direct) {
|
|
||||||
emu_window.ButtonReleased({{target->second.target.direct_target_hex}});
|
|
||||||
} else {
|
|
||||||
switch (target->second.target.indirect_target) {
|
|
||||||
case IndirectTarget::CirclePadUp:
|
|
||||||
circle_pad_up = false;
|
|
||||||
UpdateCirclePad(emu_window);
|
|
||||||
break;
|
|
||||||
case IndirectTarget::CirclePadDown:
|
|
||||||
circle_pad_down = false;
|
|
||||||
UpdateCirclePad(emu_window);
|
|
||||||
break;
|
|
||||||
case IndirectTarget::CirclePadLeft:
|
|
||||||
circle_pad_left = false;
|
|
||||||
UpdateCirclePad(emu_window);
|
|
||||||
break;
|
|
||||||
case IndirectTarget::CirclePadRight:
|
|
||||||
circle_pad_right = false;
|
|
||||||
UpdateCirclePad(emu_window);
|
|
||||||
break;
|
|
||||||
case IndirectTarget::CirclePadModifier:
|
|
||||||
circle_pad_modifier = false;
|
|
||||||
UpdateCirclePad(emu_window);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,93 +0,0 @@
|
|||||||
// Copyright 2014 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <tuple>
|
|
||||||
#include "core/hle/service/hid/hid.h"
|
|
||||||
|
|
||||||
class EmuWindow;
|
|
||||||
|
|
||||||
namespace KeyMap {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents key mapping targets that are not real 3DS buttons.
|
|
||||||
* They will be handled by KeyMap and translated to 3DS input.
|
|
||||||
*/
|
|
||||||
enum class IndirectTarget {
|
|
||||||
CirclePadUp,
|
|
||||||
CirclePadDown,
|
|
||||||
CirclePadLeft,
|
|
||||||
CirclePadRight,
|
|
||||||
CirclePadModifier,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a key mapping target. It can be a PadState that represents real 3DS buttons,
|
|
||||||
* or an IndirectTarget.
|
|
||||||
*/
|
|
||||||
struct KeyTarget {
|
|
||||||
bool direct;
|
|
||||||
union {
|
|
||||||
u32 direct_target_hex;
|
|
||||||
IndirectTarget indirect_target;
|
|
||||||
} target;
|
|
||||||
|
|
||||||
KeyTarget() : direct(true) {
|
|
||||||
target.direct_target_hex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyTarget(Service::HID::PadState pad) : direct(true) {
|
|
||||||
target.direct_target_hex = pad.hex;
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyTarget(IndirectTarget i) : direct(false) {
|
|
||||||
target.indirect_target = i;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a key for a specific host device.
|
|
||||||
*/
|
|
||||||
struct HostDeviceKey {
|
|
||||||
int key_code;
|
|
||||||
int device_id; ///< Uniquely identifies a host device
|
|
||||||
|
|
||||||
bool operator<(const HostDeviceKey& other) const {
|
|
||||||
return std::tie(key_code, device_id) < std::tie(other.key_code, other.device_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(const HostDeviceKey& other) const {
|
|
||||||
return std::tie(key_code, device_id) == std::tie(other.key_code, other.device_id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
extern const std::array<KeyTarget, Settings::NativeInput::NUM_INPUTS> mapping_targets;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a new device id, which uniquely identifies a host device within KeyMap.
|
|
||||||
*/
|
|
||||||
int NewDeviceId();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps a device-specific key to a target (a PadState or an IndirectTarget).
|
|
||||||
*/
|
|
||||||
void SetKeyMapping(HostDeviceKey key, KeyTarget target);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears all key mappings belonging to one device.
|
|
||||||
*/
|
|
||||||
void ClearKeyMapping(int device_id);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps a key press action and call the corresponding function in EmuWindow
|
|
||||||
*/
|
|
||||||
void PressKey(EmuWindow& emu_window, HostDeviceKey key);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps a key release action and call the corresponding function in EmuWindow
|
|
||||||
*/
|
|
||||||
void ReleaseKey(EmuWindow& emu_window, HostDeviceKey key);
|
|
||||||
}
|
|
@ -0,0 +1,27 @@
|
|||||||
|
set(SRCS
|
||||||
|
analog_from_button.cpp
|
||||||
|
keyboard.cpp
|
||||||
|
main.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(HEADERS
|
||||||
|
analog_from_button.h
|
||||||
|
keyboard.h
|
||||||
|
main.h
|
||||||
|
)
|
||||||
|
|
||||||
|
if(SDL2_FOUND)
|
||||||
|
set(SRCS ${SRCS} sdl/sdl.cpp)
|
||||||
|
set(HEADERS ${HEADERS} sdl/sdl.h)
|
||||||
|
include_directories(${SDL2_INCLUDE_DIR})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
create_directory_groups(${SRCS} ${HEADERS})
|
||||||
|
|
||||||
|
add_library(input_common STATIC ${SRCS} ${HEADERS})
|
||||||
|
target_link_libraries(input_common common core)
|
||||||
|
|
||||||
|
if(SDL2_FOUND)
|
||||||
|
target_link_libraries(input_common ${SDL2_LIBRARY})
|
||||||
|
set_property(TARGET input_common APPEND PROPERTY COMPILE_DEFINITIONS HAVE_SDL2)
|
||||||
|
endif()
|
@ -0,0 +1,58 @@
|
|||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "input_common/analog_from_button.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
class Analog final : public Input::AnalogDevice {
|
||||||
|
public:
|
||||||
|
using Button = std::unique_ptr<Input::ButtonDevice>;
|
||||||
|
|
||||||
|
Analog(Button up_, Button down_, Button left_, Button right_, Button modifier_,
|
||||||
|
float modifier_scale_)
|
||||||
|
: up(std::move(up_)), down(std::move(down_)), left(std::move(left_)),
|
||||||
|
right(std::move(right_)), modifier(std::move(modifier_)),
|
||||||
|
modifier_scale(modifier_scale_) {}
|
||||||
|
|
||||||
|
std::tuple<float, float> GetStatus() const override {
|
||||||
|
constexpr float SQRT_HALF = 0.707106781f;
|
||||||
|
int x = 0, y = 0;
|
||||||
|
|
||||||
|
if (right->GetStatus())
|
||||||
|
++x;
|
||||||
|
if (left->GetStatus())
|
||||||
|
--x;
|
||||||
|
if (up->GetStatus())
|
||||||
|
++y;
|
||||||
|
if (down->GetStatus())
|
||||||
|
--y;
|
||||||
|
|
||||||
|
float coef = modifier->GetStatus() ? modifier_scale : 1.0f;
|
||||||
|
return std::make_tuple(x * coef * (y == 0 ? 1.0f : SQRT_HALF),
|
||||||
|
y * coef * (x == 0 ? 1.0f : SQRT_HALF));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Button up;
|
||||||
|
Button down;
|
||||||
|
Button left;
|
||||||
|
Button right;
|
||||||
|
Button modifier;
|
||||||
|
float modifier_scale;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<Input::AnalogDevice> AnalogFromButton::Create(const Common::ParamPackage& params) {
|
||||||
|
const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
|
||||||
|
auto up = Input::CreateDevice<Input::ButtonDevice>(params.Get("up", null_engine));
|
||||||
|
auto down = Input::CreateDevice<Input::ButtonDevice>(params.Get("down", null_engine));
|
||||||
|
auto left = Input::CreateDevice<Input::ButtonDevice>(params.Get("left", null_engine));
|
||||||
|
auto right = Input::CreateDevice<Input::ButtonDevice>(params.Get("right", null_engine));
|
||||||
|
auto modifier = Input::CreateDevice<Input::ButtonDevice>(params.Get("modifier", null_engine));
|
||||||
|
auto modifier_scale = params.Get("modifier_scale", 0.5f);
|
||||||
|
return std::make_unique<Analog>(std::move(up), std::move(down), std::move(left),
|
||||||
|
std::move(right), std::move(modifier), modifier_scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include "core/frontend/input.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An analog device factory that takes direction button devices and combines them into a analog
|
||||||
|
* device.
|
||||||
|
*/
|
||||||
|
class AnalogFromButton final : public Input::Factory<Input::AnalogDevice> {
|
||||||
|
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<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
@ -0,0 +1,82 @@
|
|||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <list>
|
||||||
|
#include <mutex>
|
||||||
|
#include "input_common/keyboard.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
class KeyButton final : public Input::ButtonDevice {
|
||||||
|
public:
|
||||||
|
explicit KeyButton(std::shared_ptr<KeyButtonList> key_button_list_)
|
||||||
|
: key_button_list(key_button_list_) {}
|
||||||
|
|
||||||
|
~KeyButton();
|
||||||
|
|
||||||
|
bool GetStatus() const override {
|
||||||
|
return status.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
friend class KeyButtonList;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<KeyButtonList> key_button_list;
|
||||||
|
std::atomic<bool> status{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct KeyButtonPair {
|
||||||
|
int key_code;
|
||||||
|
KeyButton* key_button;
|
||||||
|
};
|
||||||
|
|
||||||
|
class KeyButtonList {
|
||||||
|
public:
|
||||||
|
void AddKeyButton(int key_code, KeyButton* key_button) {
|
||||||
|
std::lock_guard<std::mutex> guard(mutex);
|
||||||
|
list.push_back(KeyButtonPair{key_code, key_button});
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoveKeyButton(const KeyButton* key_button) {
|
||||||
|
std::lock_guard<std::mutex> guard(mutex);
|
||||||
|
list.remove_if(
|
||||||
|
[key_button](const KeyButtonPair& pair) { return pair.key_button == key_button; });
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChangeKeyStatus(int key_code, bool pressed) {
|
||||||
|
std::lock_guard<std::mutex> guard(mutex);
|
||||||
|
for (const KeyButtonPair& pair : list) {
|
||||||
|
if (pair.key_code == key_code)
|
||||||
|
pair.key_button->status.store(pressed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::mutex mutex;
|
||||||
|
std::list<KeyButtonPair> list;
|
||||||
|
};
|
||||||
|
|
||||||
|
Keyboard::Keyboard() : key_button_list{std::make_shared<KeyButtonList>()} {}
|
||||||
|
|
||||||
|
KeyButton::~KeyButton() {
|
||||||
|
key_button_list->RemoveKeyButton(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Input::ButtonDevice> Keyboard::Create(const Common::ParamPackage& params) {
|
||||||
|
int key_code = params.Get("code", 0);
|
||||||
|
std::unique_ptr<KeyButton> button = std::make_unique<KeyButton>(key_button_list);
|
||||||
|
key_button_list->AddKeyButton(key_code, button.get());
|
||||||
|
return std::move(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Keyboard::PressKey(int key_code) {
|
||||||
|
key_button_list->ChangeKeyStatus(key_code, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Keyboard::ReleaseKey(int key_code) {
|
||||||
|
key_button_list->ChangeKeyStatus(key_code, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include "core/frontend/input.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
class KeyButtonList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A button device factory representing a keyboard. It receives keyboard events and forward them
|
||||||
|
* to all button devices it created.
|
||||||
|
*/
|
||||||
|
class Keyboard final : public Input::Factory<Input::ButtonDevice> {
|
||||||
|
public:
|
||||||
|
Keyboard();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a button device from a keyboard key
|
||||||
|
* @param params contains parameters for creating the device:
|
||||||
|
* - "code": the code of the key to bind with the button
|
||||||
|
*/
|
||||||
|
std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<KeyButtonList> key_button_list;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include "common/param_package.h"
|
||||||
|
#include "input_common/analog_from_button.h"
|
||||||
|
#include "input_common/keyboard.h"
|
||||||
|
#include "input_common/main.h"
|
||||||
|
#ifdef HAVE_SDL2
|
||||||
|
#include "input_common/sdl/sdl.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
static std::shared_ptr<Keyboard> keyboard;
|
||||||
|
|
||||||
|
void Init() {
|
||||||
|
keyboard = std::make_shared<InputCommon::Keyboard>();
|
||||||
|
Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
|
||||||
|
Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
|
||||||
|
std::make_shared<InputCommon::AnalogFromButton>());
|
||||||
|
#ifdef HAVE_SDL2
|
||||||
|
SDL::Init();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shutdown() {
|
||||||
|
Input::UnregisterFactory<Input::ButtonDevice>("keyboard");
|
||||||
|
keyboard.reset();
|
||||||
|
Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
|
||||||
|
|
||||||
|
#ifdef HAVE_SDL2
|
||||||
|
SDL::Shutdown();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
Keyboard* GetKeyboard() {
|
||||||
|
return keyboard.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GenerateKeyboardParam(int key_code) {
|
||||||
|
Common::ParamPackage param{
|
||||||
|
{"engine", "keyboard"}, {"code", std::to_string(key_code)},
|
||||||
|
};
|
||||||
|
return param.Serialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
|
||||||
|
int key_modifier, float modifier_scale) {
|
||||||
|
Common::ParamPackage circle_pad_param{
|
||||||
|
{"engine", "analog_from_button"},
|
||||||
|
{"up", GenerateKeyboardParam(key_up)},
|
||||||
|
{"down", GenerateKeyboardParam(key_down)},
|
||||||
|
{"left", GenerateKeyboardParam(key_left)},
|
||||||
|
{"right", GenerateKeyboardParam(key_right)},
|
||||||
|
{"modifier", GenerateKeyboardParam(key_modifier)},
|
||||||
|
{"modifier_scale", std::to_string(modifier_scale)},
|
||||||
|
};
|
||||||
|
return circle_pad_param.Serialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
/// Initializes and registers all built-in input device factories.
|
||||||
|
void Init();
|
||||||
|
|
||||||
|
/// Unresisters all build-in input device factories and shut them down.
|
||||||
|
void Shutdown();
|
||||||
|
|
||||||
|
class Keyboard;
|
||||||
|
|
||||||
|
/// Gets the keyboard button device factory.
|
||||||
|
Keyboard* GetKeyboard();
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
|
||||||
|
int key_modifier, float modifier_scale);
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
@ -0,0 +1,202 @@
|
|||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <SDL.h>
|
||||||
|
#include "common/math_util.h"
|
||||||
|
#include "input_common/sdl/sdl.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
namespace SDL {
|
||||||
|
|
||||||
|
class SDLJoystick;
|
||||||
|
class SDLButtonFactory;
|
||||||
|
class SDLAnalogFactory;
|
||||||
|
static std::unordered_map<int, std::weak_ptr<SDLJoystick>> joystick_list;
|
||||||
|
static std::shared_ptr<SDLButtonFactory> button_factory;
|
||||||
|
static std::shared_ptr<SDLAnalogFactory> analog_factory;
|
||||||
|
|
||||||
|
static bool initialized = false;
|
||||||
|
|
||||||
|
class SDLJoystick {
|
||||||
|
public:
|
||||||
|
explicit SDLJoystick(int joystick_index)
|
||||||
|
: joystick{SDL_JoystickOpen(joystick_index), SDL_JoystickClose} {
|
||||||
|
if (!joystick) {
|
||||||
|
LOG_ERROR(Input, "failed to open joystick %d", joystick_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetButton(int button) const {
|
||||||
|
if (!joystick)
|
||||||
|
return {};
|
||||||
|
SDL_JoystickUpdate();
|
||||||
|
return SDL_JoystickGetButton(joystick.get(), button) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const {
|
||||||
|
if (!joystick)
|
||||||
|
return {};
|
||||||
|
SDL_JoystickUpdate();
|
||||||
|
float x = SDL_JoystickGetAxis(joystick.get(), axis_x) / 32767.0f;
|
||||||
|
float y = SDL_JoystickGetAxis(joystick.get(), axis_y) / 32767.0f;
|
||||||
|
y = -y; // 3DS uses an y-axis inverse from SDL
|
||||||
|
|
||||||
|
// Make sure the coordinates are in the unit circle,
|
||||||
|
// otherwise normalize it.
|
||||||
|
float r = x * x + y * y;
|
||||||
|
if (r > 1.0f) {
|
||||||
|
r = std::sqrt(r);
|
||||||
|
x /= r;
|
||||||
|
y /= r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_tuple(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetHatDirection(int hat, Uint8 direction) const {
|
||||||
|
return (SDL_JoystickGetHat(joystick.get(), hat) & direction) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> joystick;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SDLButton final : public Input::ButtonDevice {
|
||||||
|
public:
|
||||||
|
explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_)
|
||||||
|
: joystick(joystick_), button(button_) {}
|
||||||
|
|
||||||
|
bool GetStatus() const override {
|
||||||
|
return joystick->GetButton(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<SDLJoystick> joystick;
|
||||||
|
int button;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SDLDirectionButton final : public Input::ButtonDevice {
|
||||||
|
public:
|
||||||
|
explicit SDLDirectionButton(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
|
||||||
|
: joystick(joystick_), hat(hat_), direction(direction_) {}
|
||||||
|
|
||||||
|
bool GetStatus() const override {
|
||||||
|
return joystick->GetHatDirection(hat, direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<SDLJoystick> joystick;
|
||||||
|
int hat;
|
||||||
|
Uint8 direction;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SDLAnalog final : public Input::AnalogDevice {
|
||||||
|
public:
|
||||||
|
SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_)
|
||||||
|
: joystick(joystick_), axis_x(axis_x_), axis_y(axis_y_) {}
|
||||||
|
|
||||||
|
std::tuple<float, float> GetStatus() const override {
|
||||||
|
return joystick->GetAnalog(axis_x, axis_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<SDLJoystick> joystick;
|
||||||
|
int axis_x;
|
||||||
|
int axis_y;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::shared_ptr<SDLJoystick> GetJoystick(int joystick_index) {
|
||||||
|
std::shared_ptr<SDLJoystick> joystick = joystick_list[joystick_index].lock();
|
||||||
|
if (!joystick) {
|
||||||
|
joystick = std::make_shared<SDLJoystick>(joystick_index);
|
||||||
|
joystick_list[joystick_index] = joystick;
|
||||||
|
}
|
||||||
|
return joystick;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A button device factory that creates button devices from SDL joystick
|
||||||
|
class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Creates a button device from a joystick button
|
||||||
|
* @param params contains parameters for creating the device:
|
||||||
|
* - "joystick": the index of the joystick to bind
|
||||||
|
* - "button"(optional): the index of the button to bind
|
||||||
|
* - "hat"(optional): the index of the hat to bind as direction buttons
|
||||||
|
* - "direction"(only used for hat): the direction name of the hat to bind. Can be "up",
|
||||||
|
* "down", "left" or "right"
|
||||||
|
*/
|
||||||
|
std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override {
|
||||||
|
const int joystick_index = params.Get("joystick", 0);
|
||||||
|
|
||||||
|
if (params.Has("hat")) {
|
||||||
|
const int hat = params.Get("hat", 0);
|
||||||
|
const std::string direction_name = params.Get("direction", "");
|
||||||
|
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 std::make_unique<SDLDirectionButton>(GetJoystick(joystick_index), hat,
|
||||||
|
direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int button = params.Get("button", 0);
|
||||||
|
return std::make_unique<SDLButton>(GetJoystick(joystick_index), button);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An analog device factory that creates analog devices from SDL joystick
|
||||||
|
class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Creates analog device from joystick axes
|
||||||
|
* @param params contains parameters for creating the device:
|
||||||
|
* - "joystick": the index of the joystick to bind
|
||||||
|
* - "axis_x": the index of the axis to be bind as x-axis
|
||||||
|
* - "axis_y": the index of the axis to be bind as y-axis
|
||||||
|
*/
|
||||||
|
std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override {
|
||||||
|
const int joystick_index = params.Get("joystick", 0);
|
||||||
|
const int axis_x = params.Get("axis_x", 0);
|
||||||
|
const int axis_y = params.Get("axis_y", 1);
|
||||||
|
return std::make_unique<SDLAnalog>(GetJoystick(joystick_index), axis_x, axis_y);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void Init() {
|
||||||
|
if (SDL_Init(SDL_INIT_JOYSTICK) < 0) {
|
||||||
|
LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: %s", SDL_GetError());
|
||||||
|
} else {
|
||||||
|
using namespace Input;
|
||||||
|
RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>());
|
||||||
|
RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>());
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shutdown() {
|
||||||
|
if (initialized) {
|
||||||
|
using namespace Input;
|
||||||
|
UnregisterFactory<ButtonDevice>("sdl");
|
||||||
|
UnregisterFactory<AnalogDevice>("sdl");
|
||||||
|
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace SDL
|
||||||
|
} // namespace InputCommon
|
@ -0,0 +1,19 @@
|
|||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/frontend/input.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
namespace SDL {
|
||||||
|
|
||||||
|
/// Initializes and registers SDL device factories
|
||||||
|
void Init();
|
||||||
|
|
||||||
|
/// Unresisters SDL device factories and shut them down.
|
||||||
|
void Shutdown();
|
||||||
|
|
||||||
|
} // namespace SDL
|
||||||
|
} // namespace InputCommon
|
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <catch.hpp>
|
||||||
|
#include <math.h>
|
||||||
|
#include "common/param_package.h"
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
TEST_CASE("ParamPackage", "[common]") {
|
||||||
|
ParamPackage original{
|
||||||
|
{"abc", "xyz"}, {"def", "42"}, {"jkl", "$$:1:$2$,3"},
|
||||||
|
};
|
||||||
|
original.Set("ghi", 3.14f);
|
||||||
|
ParamPackage copy(original.Serialize());
|
||||||
|
REQUIRE(copy.Get("abc", "") == "xyz");
|
||||||
|
REQUIRE(copy.Get("def", 0) == 42);
|
||||||
|
REQUIRE(std::abs(copy.Get("ghi", 0.0f) - 3.14f) < 0.01f);
|
||||||
|
REQUIRE(copy.Get("jkl", "") == "$$:1:$2$,3");
|
||||||
|
REQUIRE(copy.Get("mno", "uvw") == "uvw");
|
||||||
|
REQUIRE(copy.Get("abc", 42) == 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common
|
Loading…
Reference in New Issue