Browse Source
Merge pull request #2497 from wwylele/input-2
Merge pull request #2497 from wwylele/input-2
Refactor input emulation & add SDL gamepad supportnce_cpp
committed by
GitHub
40 changed files with 1244 additions and 574 deletions
-
1src/CMakeLists.txt
-
2src/citra/CMakeLists.txt
-
45src/citra/config.cpp
-
69src/citra/default_ini.h
-
21src/citra/emu_window/emu_window_sdl2.cpp
-
6src/citra/emu_window/emu_window_sdl2.h
-
2src/citra_qt/CMakeLists.txt
-
26src/citra_qt/bootmanager.cpp
-
6src/citra_qt/bootmanager.h
-
62src/citra_qt/config.cpp
-
5src/citra_qt/config.h
-
178src/citra_qt/configure_input.cpp
-
34src/citra_qt/configure_input.h
-
2src/common/CMakeLists.txt
-
1src/common/logging/backend.cpp
-
1src/common/logging/log.h
-
120src/common/param_package.cpp
-
40src/common/param_package.h
-
3src/core/CMakeLists.txt
-
26src/core/frontend/emu_window.cpp
-
54src/core/frontend/emu_window.h
-
110src/core/frontend/input.h
-
152src/core/frontend/key_map.cpp
-
93src/core/frontend/key_map.h
-
56src/core/hle/service/hid/hid.cpp
-
37src/core/hle/service/hid/hid.h
-
3src/core/settings.cpp
-
80src/core/settings.h
-
27src/input_common/CMakeLists.txt
-
58src/input_common/analog_from_button.cpp
-
31src/input_common/analog_from_button.h
-
82src/input_common/keyboard.cpp
-
45src/input_common/keyboard.h
-
63src/input_common/main.cpp
-
29src/input_common/main.h
-
202src/input_common/sdl/sdl.cpp
-
19src/input_common/sdl/sdl.h
-
1src/tests/CMakeLists.txt
-
25src/tests/common/param_package.cpp
-
1src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@ -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
|
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue