mirror of
https://github.com/citra-emu/citra-canary.git
synced 2024-12-23 19:45:32 +00:00
Merge pull request #2497 from wwylele/input-2
Refactor input emulation & add SDL gamepad support
This commit is contained in:
commit
423ab5e2bc
|
@ -5,6 +5,7 @@ add_subdirectory(common)
|
|||
add_subdirectory(core)
|
||||
add_subdirectory(video_core)
|
||||
add_subdirectory(audio_core)
|
||||
add_subdirectory(input_common)
|
||||
add_subdirectory(tests)
|
||||
if (ENABLE_SDL2)
|
||||
add_subdirectory(citra)
|
||||
|
|
|
@ -18,7 +18,7 @@ create_directory_groups(${SRCS} ${HEADERS})
|
|||
include_directories(${SDL2_INCLUDE_DIR})
|
||||
|
||||
add_executable(citra ${SRCS} ${HEADERS})
|
||||
target_link_libraries(citra core video_core audio_core common)
|
||||
target_link_libraries(citra core video_core audio_core common input_common)
|
||||
target_link_libraries(citra ${SDL2_LIBRARY} ${OPENGL_gl_LIBRARY} inih glad)
|
||||
if (MSVC)
|
||||
target_link_libraries(citra getopt)
|
||||
|
|
|
@ -8,8 +8,10 @@
|
|||
#include "citra/default_ini.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/param_package.h"
|
||||
#include "config.h"
|
||||
#include "core/settings.h"
|
||||
#include "input_common/main.h"
|
||||
|
||||
Config::Config() {
|
||||
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
|
||||
|
@ -37,25 +39,40 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) {
|
|||
return true;
|
||||
}
|
||||
|
||||
static const std::array<int, Settings::NativeInput::NUM_INPUTS> defaults = {
|
||||
// directly mapped keys
|
||||
SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_Q, SDL_SCANCODE_W,
|
||||
SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_M, SDL_SCANCODE_N, SDL_SCANCODE_B, SDL_SCANCODE_T,
|
||||
SDL_SCANCODE_G, SDL_SCANCODE_F, SDL_SCANCODE_H, SDL_SCANCODE_I, SDL_SCANCODE_K, SDL_SCANCODE_J,
|
||||
SDL_SCANCODE_L,
|
||||
|
||||
// indirectly mapped keys
|
||||
SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_D,
|
||||
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons = {
|
||||
SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_T,
|
||||
SDL_SCANCODE_G, SDL_SCANCODE_F, SDL_SCANCODE_H, SDL_SCANCODE_Q, SDL_SCANCODE_W,
|
||||
SDL_SCANCODE_M, SDL_SCANCODE_N, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_B,
|
||||
};
|
||||
|
||||
static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs{{
|
||||
{
|
||||
SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_D,
|
||||
},
|
||||
{
|
||||
SDL_SCANCODE_I, SDL_SCANCODE_K, SDL_SCANCODE_J, SDL_SCANCODE_L, SDL_SCANCODE_D,
|
||||
},
|
||||
}};
|
||||
|
||||
void Config::ReadValues() {
|
||||
// Controls
|
||||
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
|
||||
Settings::values.input_mappings[Settings::NativeInput::All[i]] =
|
||||
sdl2_config->GetInteger("Controls", Settings::NativeInput::Mapping[i], defaults[i]);
|
||||
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
|
||||
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
|
||||
Settings::values.buttons[i] =
|
||||
sdl2_config->Get("Controls", Settings::NativeButton::mapping[i], default_param);
|
||||
if (Settings::values.buttons[i].empty())
|
||||
Settings::values.buttons[i] = default_param;
|
||||
}
|
||||
|
||||
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
|
||||
std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
|
||||
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
|
||||
default_analogs[i][3], default_analogs[i][4], 0.5f);
|
||||
Settings::values.analogs[i] =
|
||||
sdl2_config->Get("Controls", Settings::NativeAnalog::mapping[i], default_param);
|
||||
if (Settings::values.analogs[i].empty())
|
||||
Settings::values.analogs[i] = default_param;
|
||||
}
|
||||
Settings::values.pad_circle_modifier_scale =
|
||||
(float)sdl2_config->GetReal("Controls", "pad_circle_modifier_scale", 0.5);
|
||||
|
||||
// Core
|
||||
Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
|
||||
|
|
|
@ -8,34 +8,47 @@ namespace DefaultINI {
|
|||
|
||||
const char* sdl2_config_file = R"(
|
||||
[Controls]
|
||||
pad_start =
|
||||
pad_select =
|
||||
pad_home =
|
||||
pad_dup =
|
||||
pad_ddown =
|
||||
pad_dleft =
|
||||
pad_dright =
|
||||
pad_a =
|
||||
pad_b =
|
||||
pad_x =
|
||||
pad_y =
|
||||
pad_l =
|
||||
pad_r =
|
||||
pad_zl =
|
||||
pad_zr =
|
||||
pad_cup =
|
||||
pad_cdown =
|
||||
pad_cleft =
|
||||
pad_cright =
|
||||
pad_circle_up =
|
||||
pad_circle_down =
|
||||
pad_circle_left =
|
||||
pad_circle_right =
|
||||
pad_circle_modifier =
|
||||
# The input devices and parameters for each 3DS native input
|
||||
# It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..."
|
||||
# Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values
|
||||
|
||||
# The applied modifier scale to circle pad.
|
||||
# Must be in range of 0.0-1.0. Defaults to 0.5
|
||||
pad_circle_modifier_scale =
|
||||
# for button input, the following devices are avaible:
|
||||
# - "keyboard" (default) for keyboard input. Required parameters:
|
||||
# - "code": the code of the key to bind
|
||||
# - "sdl" for joystick input using SDL. Required parameters:
|
||||
# - "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"
|
||||
button_a=
|
||||
button_b=
|
||||
button_x=
|
||||
button_y=
|
||||
button_up=
|
||||
button_down=
|
||||
button_left=
|
||||
button_right=
|
||||
button_l=
|
||||
button_r=
|
||||
button_start=
|
||||
button_select=
|
||||
button_zl=
|
||||
button_zr=
|
||||
button_home=
|
||||
|
||||
# for analog input, the following devices are avaible:
|
||||
# - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters:
|
||||
# - "up", "down", "left", "right": sub-devices for each direction.
|
||||
# Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00"
|
||||
# - "modifier": sub-devices as a modifier.
|
||||
# - "modifier_scale": a float number representing the applied modifier scale to the analog input.
|
||||
# Must be in range of 0.0-1.0. Defaults to 0.5
|
||||
# - "sdl" for joystick input using SDL. Required parameters:
|
||||
# - "joystick": the index of the joystick to bind
|
||||
# - "axis_x": the index of the axis to bind as x-axis (default to 0)
|
||||
# - "axis_y": the index of the axis to bind as y-axis (default to 1)
|
||||
circle_pad=
|
||||
c_stick=
|
||||
|
||||
[Core]
|
||||
# Whether to use the Just-In-Time (JIT) compiler for CPU emulation
|
||||
|
|
|
@ -12,9 +12,9 @@
|
|||
#include "common/logging/log.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/frontend/key_map.h"
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
#include "core/settings.h"
|
||||
#include "input_common/keyboard.h"
|
||||
#include "input_common/main.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
|
||||
|
@ -40,9 +40,9 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
|
|||
|
||||
void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) {
|
||||
if (state == SDL_PRESSED) {
|
||||
KeyMap::PressKey(*this, {key, keyboard_id});
|
||||
InputCommon::GetKeyboard()->PressKey(key);
|
||||
} else if (state == SDL_RELEASED) {
|
||||
KeyMap::ReleaseKey(*this, {key, keyboard_id});
|
||||
InputCommon::GetKeyboard()->ReleaseKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,9 +57,8 @@ void EmuWindow_SDL2::OnResize() {
|
|||
}
|
||||
|
||||
EmuWindow_SDL2::EmuWindow_SDL2() {
|
||||
keyboard_id = KeyMap::NewDeviceId();
|
||||
InputCommon::Init();
|
||||
|
||||
ReloadSetKeymaps();
|
||||
motion_emu = std::make_unique<Motion::MotionEmu>(*this);
|
||||
|
||||
SDL_SetMainReady();
|
||||
|
@ -117,6 +116,7 @@ EmuWindow_SDL2::~EmuWindow_SDL2() {
|
|||
SDL_GL_DeleteContext(gl_context);
|
||||
SDL_Quit();
|
||||
motion_emu = nullptr;
|
||||
InputCommon::Shutdown();
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::SwapBuffers() {
|
||||
|
@ -169,15 +169,6 @@ void EmuWindow_SDL2::DoneCurrent() {
|
|||
SDL_GL_MakeCurrent(render_window, nullptr);
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::ReloadSetKeymaps() {
|
||||
KeyMap::ClearKeyMapping(keyboard_id);
|
||||
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
|
||||
KeyMap::SetKeyMapping(
|
||||
{Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id},
|
||||
KeyMap::mapping_targets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(
|
||||
const std::pair<unsigned, unsigned>& minimal_size) {
|
||||
|
||||
|
|
|
@ -31,9 +31,6 @@ public:
|
|||
/// Whether the window is still open, and a close request hasn't yet been sent
|
||||
bool IsOpen() const;
|
||||
|
||||
/// Load keymap from configuration
|
||||
void ReloadSetKeymaps() override;
|
||||
|
||||
private:
|
||||
/// Called by PollEvents when a key is pressed or released.
|
||||
void OnKeyEvent(int key, u8 state);
|
||||
|
@ -61,9 +58,6 @@ private:
|
|||
/// The OpenGL context associated with the window
|
||||
SDL_GLContext gl_context;
|
||||
|
||||
/// Device id of keyboard for use with KeyMap
|
||||
int keyboard_id;
|
||||
|
||||
/// Motion sensors emulation
|
||||
std::unique_ptr<Motion::MotionEmu> motion_emu;
|
||||
};
|
||||
|
|
|
@ -97,7 +97,7 @@ if (APPLE)
|
|||
else()
|
||||
add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS})
|
||||
endif()
|
||||
target_link_libraries(citra-qt core video_core audio_core common)
|
||||
target_link_libraries(citra-qt core video_core audio_core common input_common)
|
||||
target_link_libraries(citra-qt ${OPENGL_gl_LIBRARY} ${CITRA_QT_LIBS})
|
||||
target_link_libraries(citra-qt ${PLATFORM_LIBRARIES} Threads::Threads)
|
||||
|
||||
|
|
|
@ -13,7 +13,9 @@
|
|||
#include "common/scm_rev.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/key_map.h"
|
||||
#include "core/settings.h"
|
||||
#include "input_common/keyboard.h"
|
||||
#include "input_common/main.h"
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
|
@ -99,14 +101,17 @@ private:
|
|||
};
|
||||
|
||||
GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
|
||||
: QWidget(parent), child(nullptr), keyboard_id(0), emu_thread(emu_thread) {
|
||||
: QWidget(parent), child(nullptr), emu_thread(emu_thread) {
|
||||
|
||||
std::string window_title = Common::StringFromFormat("Citra %s| %s-%s", Common::g_build_name,
|
||||
Common::g_scm_branch, Common::g_scm_desc);
|
||||
setWindowTitle(QString::fromStdString(window_title));
|
||||
|
||||
keyboard_id = KeyMap::NewDeviceId();
|
||||
ReloadSetKeymaps();
|
||||
InputCommon::Init();
|
||||
}
|
||||
|
||||
GRenderWindow::~GRenderWindow() {
|
||||
InputCommon::Shutdown();
|
||||
}
|
||||
|
||||
void GRenderWindow::moveContext() {
|
||||
|
@ -197,11 +202,11 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
|
|||
}
|
||||
|
||||
void GRenderWindow::keyPressEvent(QKeyEvent* event) {
|
||||
KeyMap::PressKey(*this, {event->key(), keyboard_id});
|
||||
InputCommon::GetKeyboard()->PressKey(event->key());
|
||||
}
|
||||
|
||||
void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
|
||||
KeyMap::ReleaseKey(*this, {event->key(), keyboard_id});
|
||||
InputCommon::GetKeyboard()->ReleaseKey(event->key());
|
||||
}
|
||||
|
||||
void GRenderWindow::mousePressEvent(QMouseEvent* event) {
|
||||
|
@ -230,14 +235,7 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
|
|||
motion_emu->EndTilt();
|
||||
}
|
||||
|
||||
void GRenderWindow::ReloadSetKeymaps() {
|
||||
KeyMap::ClearKeyMapping(keyboard_id);
|
||||
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
|
||||
KeyMap::SetKeyMapping(
|
||||
{Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id},
|
||||
KeyMap::mapping_targets[i]);
|
||||
}
|
||||
}
|
||||
void GRenderWindow::ReloadSetKeymaps() {}
|
||||
|
||||
void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) {
|
||||
NotifyClientAreaSizeChanged(std::make_pair(width, height));
|
||||
|
|
|
@ -104,6 +104,7 @@ class GRenderWindow : public QWidget, public EmuWindow {
|
|||
|
||||
public:
|
||||
GRenderWindow(QWidget* parent, EmuThread* emu_thread);
|
||||
~GRenderWindow();
|
||||
|
||||
// EmuWindow implementation
|
||||
void SwapBuffers() override;
|
||||
|
@ -127,7 +128,7 @@ public:
|
|||
void mouseMoveEvent(QMouseEvent* event) override;
|
||||
void mouseReleaseEvent(QMouseEvent* event) override;
|
||||
|
||||
void ReloadSetKeymaps() override;
|
||||
void ReloadSetKeymaps();
|
||||
|
||||
void OnClientAreaResized(unsigned width, unsigned height);
|
||||
|
||||
|
@ -152,9 +153,6 @@ private:
|
|||
|
||||
QByteArray geometry;
|
||||
|
||||
/// Device id of keyboard for use with KeyMap
|
||||
int keyboard_id;
|
||||
|
||||
EmuThread* emu_thread;
|
||||
|
||||
/// Motion sensors emulation
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "citra_qt/config.h"
|
||||
#include "citra_qt/ui_settings.h"
|
||||
#include "common/file_util.h"
|
||||
#include "input_common/main.h"
|
||||
|
||||
Config::Config() {
|
||||
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
|
||||
|
@ -16,25 +17,46 @@ Config::Config() {
|
|||
Reload();
|
||||
}
|
||||
|
||||
const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> Config::defaults = {
|
||||
// directly mapped keys
|
||||
Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_Q, Qt::Key_W, Qt::Key_1, Qt::Key_2,
|
||||
Qt::Key_M, Qt::Key_N, Qt::Key_B, Qt::Key_T, Qt::Key_G, Qt::Key_F, Qt::Key_H, Qt::Key_I,
|
||||
Qt::Key_K, Qt::Key_J, Qt::Key_L,
|
||||
|
||||
// indirectly mapped keys
|
||||
Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right, Qt::Key_D,
|
||||
const std::array<int, Settings::NativeButton::NumButtons> Config::default_buttons = {
|
||||
Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_T, Qt::Key_G, Qt::Key_F, Qt::Key_H,
|
||||
Qt::Key_Q, Qt::Key_W, Qt::Key_M, Qt::Key_N, Qt::Key_1, Qt::Key_2, Qt::Key_B,
|
||||
};
|
||||
|
||||
const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{
|
||||
{
|
||||
Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right, Qt::Key_D,
|
||||
},
|
||||
{
|
||||
Qt::Key_I, Qt::Key_K, Qt::Key_J, Qt::Key_L, Qt::Key_D,
|
||||
},
|
||||
}};
|
||||
|
||||
void Config::ReadValues() {
|
||||
qt_config->beginGroup("Controls");
|
||||
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
|
||||
Settings::values.input_mappings[Settings::NativeInput::All[i]] =
|
||||
qt_config->value(QString::fromStdString(Settings::NativeInput::Mapping[i]), defaults[i])
|
||||
.toInt();
|
||||
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
|
||||
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
|
||||
Settings::values.buttons[i] =
|
||||
qt_config
|
||||
->value(Settings::NativeButton::mapping[i], QString::fromStdString(default_param))
|
||||
.toString()
|
||||
.toStdString();
|
||||
if (Settings::values.buttons[i].empty())
|
||||
Settings::values.buttons[i] = default_param;
|
||||
}
|
||||
Settings::values.pad_circle_modifier_scale =
|
||||
qt_config->value("pad_circle_modifier_scale", 0.5).toFloat();
|
||||
|
||||
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
|
||||
std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
|
||||
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
|
||||
default_analogs[i][3], default_analogs[i][4], 0.5f);
|
||||
Settings::values.analogs[i] =
|
||||
qt_config
|
||||
->value(Settings::NativeAnalog::mapping[i], QString::fromStdString(default_param))
|
||||
.toString()
|
||||
.toStdString();
|
||||
if (Settings::values.analogs[i].empty())
|
||||
Settings::values.analogs[i] = default_param;
|
||||
}
|
||||
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Core");
|
||||
|
@ -155,12 +177,14 @@ void Config::ReadValues() {
|
|||
|
||||
void Config::SaveValues() {
|
||||
qt_config->beginGroup("Controls");
|
||||
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
|
||||
qt_config->setValue(QString::fromStdString(Settings::NativeInput::Mapping[i]),
|
||||
Settings::values.input_mappings[Settings::NativeInput::All[i]]);
|
||||
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
|
||||
qt_config->setValue(QString::fromStdString(Settings::NativeButton::mapping[i]),
|
||||
QString::fromStdString(Settings::values.buttons[i]));
|
||||
}
|
||||
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
|
||||
qt_config->setValue(QString::fromStdString(Settings::NativeAnalog::mapping[i]),
|
||||
QString::fromStdString(Settings::values.analogs[i]));
|
||||
}
|
||||
qt_config->setValue("pad_circle_modifier_scale",
|
||||
(double)Settings::values.pad_circle_modifier_scale);
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Core");
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <QVariant>
|
||||
#include "core/settings.h"
|
||||
|
@ -23,5 +24,7 @@ public:
|
|||
|
||||
void Reload();
|
||||
void Save();
|
||||
static const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> defaults;
|
||||
|
||||
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
|
||||
static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs;
|
||||
};
|
||||
|
|
|
@ -2,13 +2,21 @@
|
|||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <QTimer>
|
||||
#include "citra_qt/config.h"
|
||||
#include "citra_qt/configure_input.h"
|
||||
#include "common/param_package.h"
|
||||
#include "input_common/main.h"
|
||||
|
||||
static QString getKeyName(Qt::Key key_code) {
|
||||
const std::array<std::string, ConfigureInput::ANALOG_SUB_BUTTONS_NUM>
|
||||
ConfigureInput::analog_sub_buttons{{
|
||||
"up", "down", "left", "right", "modifier",
|
||||
}};
|
||||
|
||||
static QString getKeyName(int key_code) {
|
||||
switch (key_code) {
|
||||
case Qt::Key_Shift:
|
||||
return QObject::tr("Shift");
|
||||
|
@ -23,6 +31,20 @@ static QString getKeyName(Qt::Key key_code) {
|
|||
}
|
||||
}
|
||||
|
||||
static void SetButtonKey(int key, Common::ParamPackage& button_param) {
|
||||
button_param = Common::ParamPackage{InputCommon::GenerateKeyboardParam(key)};
|
||||
}
|
||||
|
||||
static void SetAnalogKey(int key, Common::ParamPackage& analog_param,
|
||||
const std::string& button_name) {
|
||||
if (analog_param.Get("engine", "") != "analog_from_button") {
|
||||
analog_param = {
|
||||
{"engine", "analog_from_button"}, {"modifier_scale", "0.5"},
|
||||
};
|
||||
}
|
||||
analog_param.Set(button_name, InputCommon::GenerateKeyboardParam(key));
|
||||
}
|
||||
|
||||
ConfigureInput::ConfigureInput(QWidget* parent)
|
||||
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()),
|
||||
timer(std::make_unique<QTimer>()) {
|
||||
|
@ -31,36 +53,41 @@ ConfigureInput::ConfigureInput(QWidget* parent)
|
|||
setFocusPolicy(Qt::ClickFocus);
|
||||
|
||||
button_map = {
|
||||
{Settings::NativeInput::Values::A, ui->buttonA},
|
||||
{Settings::NativeInput::Values::B, ui->buttonB},
|
||||
{Settings::NativeInput::Values::X, ui->buttonX},
|
||||
{Settings::NativeInput::Values::Y, ui->buttonY},
|
||||
{Settings::NativeInput::Values::L, ui->buttonL},
|
||||
{Settings::NativeInput::Values::R, ui->buttonR},
|
||||
{Settings::NativeInput::Values::ZL, ui->buttonZL},
|
||||
{Settings::NativeInput::Values::ZR, ui->buttonZR},
|
||||
{Settings::NativeInput::Values::START, ui->buttonStart},
|
||||
{Settings::NativeInput::Values::SELECT, ui->buttonSelect},
|
||||
{Settings::NativeInput::Values::HOME, ui->buttonHome},
|
||||
{Settings::NativeInput::Values::DUP, ui->buttonDpadUp},
|
||||
{Settings::NativeInput::Values::DDOWN, ui->buttonDpadDown},
|
||||
{Settings::NativeInput::Values::DLEFT, ui->buttonDpadLeft},
|
||||
{Settings::NativeInput::Values::DRIGHT, ui->buttonDpadRight},
|
||||
{Settings::NativeInput::Values::CUP, ui->buttonCStickUp},
|
||||
{Settings::NativeInput::Values::CDOWN, ui->buttonCStickDown},
|
||||
{Settings::NativeInput::Values::CLEFT, ui->buttonCStickLeft},
|
||||
{Settings::NativeInput::Values::CRIGHT, ui->buttonCStickRight},
|
||||
{Settings::NativeInput::Values::CIRCLE_UP, ui->buttonCircleUp},
|
||||
{Settings::NativeInput::Values::CIRCLE_DOWN, ui->buttonCircleDown},
|
||||
{Settings::NativeInput::Values::CIRCLE_LEFT, ui->buttonCircleLeft},
|
||||
{Settings::NativeInput::Values::CIRCLE_RIGHT, ui->buttonCircleRight},
|
||||
{Settings::NativeInput::Values::CIRCLE_MODIFIER, ui->buttonCircleMod},
|
||||
ui->buttonA, ui->buttonB, ui->buttonX, ui->buttonY, ui->buttonDpadUp,
|
||||
ui->buttonDpadDown, ui->buttonDpadLeft, ui->buttonDpadRight, ui->buttonL, ui->buttonR,
|
||||
ui->buttonStart, ui->buttonSelect, ui->buttonZL, ui->buttonZR, ui->buttonHome,
|
||||
};
|
||||
|
||||
for (const auto& entry : button_map) {
|
||||
const Settings::NativeInput::Values input_id = entry.first;
|
||||
connect(entry.second, &QPushButton::released,
|
||||
[this, input_id]() { handleClick(input_id); });
|
||||
analog_map = {{
|
||||
{
|
||||
ui->buttonCircleUp, ui->buttonCircleDown, ui->buttonCircleLeft, ui->buttonCircleRight,
|
||||
ui->buttonCircleMod,
|
||||
},
|
||||
{
|
||||
ui->buttonCStickUp, ui->buttonCStickDown, ui->buttonCStickLeft, ui->buttonCStickRight,
|
||||
nullptr,
|
||||
},
|
||||
}};
|
||||
|
||||
for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
|
||||
if (button_map[button_id])
|
||||
connect(button_map[button_id], &QPushButton::released, [=]() {
|
||||
handleClick(button_map[button_id],
|
||||
[=](int key) { SetButtonKey(key, buttons_param[button_id]); });
|
||||
});
|
||||
}
|
||||
|
||||
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
|
||||
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
|
||||
if (analog_map[analog_id][sub_button_id] != nullptr) {
|
||||
connect(analog_map[analog_id][sub_button_id], &QPushButton::released, [=]() {
|
||||
handleClick(analog_map[analog_id][sub_button_id], [=](int key) {
|
||||
SetAnalogKey(key, analogs_param[analog_id],
|
||||
analog_sub_buttons[sub_button_id]);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); });
|
||||
|
@ -69,50 +96,93 @@ ConfigureInput::ConfigureInput(QWidget* parent)
|
|||
connect(timer.get(), &QTimer::timeout, [this]() {
|
||||
releaseKeyboard();
|
||||
releaseMouse();
|
||||
current_input_id = boost::none;
|
||||
key_setter = boost::none;
|
||||
updateButtonLabels();
|
||||
});
|
||||
|
||||
this->loadConfiguration();
|
||||
|
||||
// TODO(wwylele): enable these when the input emulation for them is implemented
|
||||
ui->buttonZL->setEnabled(false);
|
||||
ui->buttonZR->setEnabled(false);
|
||||
ui->buttonHome->setEnabled(false);
|
||||
ui->buttonCStickUp->setEnabled(false);
|
||||
ui->buttonCStickDown->setEnabled(false);
|
||||
ui->buttonCStickLeft->setEnabled(false);
|
||||
ui->buttonCStickRight->setEnabled(false);
|
||||
}
|
||||
|
||||
void ConfigureInput::applyConfiguration() {
|
||||
for (const auto& input_id : Settings::NativeInput::All) {
|
||||
const size_t index = static_cast<size_t>(input_id);
|
||||
Settings::values.input_mappings[index] = static_cast<int>(key_map[input_id]);
|
||||
}
|
||||
std::transform(buttons_param.begin(), buttons_param.end(), Settings::values.buttons.begin(),
|
||||
[](const Common::ParamPackage& param) { return param.Serialize(); });
|
||||
std::transform(analogs_param.begin(), analogs_param.end(), Settings::values.analogs.begin(),
|
||||
[](const Common::ParamPackage& param) { return param.Serialize(); });
|
||||
|
||||
Settings::Apply();
|
||||
}
|
||||
|
||||
void ConfigureInput::loadConfiguration() {
|
||||
for (const auto& input_id : Settings::NativeInput::All) {
|
||||
const size_t index = static_cast<size_t>(input_id);
|
||||
key_map[input_id] = static_cast<Qt::Key>(Settings::values.input_mappings[index]);
|
||||
}
|
||||
std::transform(Settings::values.buttons.begin(), Settings::values.buttons.end(),
|
||||
buttons_param.begin(),
|
||||
[](const std::string& str) { return Common::ParamPackage(str); });
|
||||
std::transform(Settings::values.analogs.begin(), Settings::values.analogs.end(),
|
||||
analogs_param.begin(),
|
||||
[](const std::string& str) { return Common::ParamPackage(str); });
|
||||
updateButtonLabels();
|
||||
}
|
||||
|
||||
void ConfigureInput::restoreDefaults() {
|
||||
for (const auto& input_id : Settings::NativeInput::All) {
|
||||
const size_t index = static_cast<size_t>(input_id);
|
||||
key_map[input_id] = static_cast<Qt::Key>(Config::defaults[index].toInt());
|
||||
for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
|
||||
SetButtonKey(Config::default_buttons[button_id], buttons_param[button_id]);
|
||||
}
|
||||
|
||||
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
|
||||
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
|
||||
SetAnalogKey(Config::default_analogs[analog_id][sub_button_id],
|
||||
analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
|
||||
}
|
||||
}
|
||||
updateButtonLabels();
|
||||
applyConfiguration();
|
||||
}
|
||||
|
||||
void ConfigureInput::updateButtonLabels() {
|
||||
for (const auto& input_id : Settings::NativeInput::All) {
|
||||
button_map[input_id]->setText(getKeyName(key_map[input_id]));
|
||||
QString non_keyboard(tr("[non-keyboard]"));
|
||||
|
||||
auto KeyToText = [&non_keyboard](const Common::ParamPackage& param) {
|
||||
if (param.Get("engine", "") != "keyboard") {
|
||||
return non_keyboard;
|
||||
} else {
|
||||
return getKeyName(param.Get("code", 0));
|
||||
}
|
||||
};
|
||||
|
||||
for (int button = 0; button < Settings::NativeButton::NumButtons; button++) {
|
||||
button_map[button]->setText(KeyToText(buttons_param[button]));
|
||||
}
|
||||
|
||||
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
|
||||
if (analogs_param[analog_id].Get("engine", "") != "analog_from_button") {
|
||||
for (QPushButton* button : analog_map[analog_id]) {
|
||||
if (button)
|
||||
button->setText(non_keyboard);
|
||||
}
|
||||
} else {
|
||||
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
|
||||
Common::ParamPackage param(
|
||||
analogs_param[analog_id].Get(analog_sub_buttons[sub_button_id], ""));
|
||||
if (analog_map[analog_id][sub_button_id])
|
||||
analog_map[analog_id][sub_button_id]->setText(KeyToText(param));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureInput::handleClick(Settings::NativeInput::Values input_id) {
|
||||
QPushButton* button = button_map[input_id];
|
||||
void ConfigureInput::handleClick(QPushButton* button, std::function<void(int)> new_key_setter) {
|
||||
button->setText(tr("[press key]"));
|
||||
button->setFocus();
|
||||
|
||||
current_input_id = input_id;
|
||||
key_setter = new_key_setter;
|
||||
|
||||
grabKeyboard();
|
||||
grabMouse();
|
||||
|
@ -123,23 +193,13 @@ void ConfigureInput::keyPressEvent(QKeyEvent* event) {
|
|||
releaseKeyboard();
|
||||
releaseMouse();
|
||||
|
||||
if (!current_input_id || !event)
|
||||
if (!key_setter || !event)
|
||||
return;
|
||||
|
||||
if (event->key() != Qt::Key_Escape)
|
||||
setInput(*current_input_id, static_cast<Qt::Key>(event->key()));
|
||||
(*key_setter)(event->key());
|
||||
|
||||
updateButtonLabels();
|
||||
current_input_id = boost::none;
|
||||
key_setter = boost::none;
|
||||
timer->stop();
|
||||
}
|
||||
|
||||
void ConfigureInput::setInput(Settings::NativeInput::Values input_id, Qt::Key key_pressed) {
|
||||
// Remove duplicates
|
||||
for (auto& pair : key_map) {
|
||||
if (pair.second == key_pressed)
|
||||
pair.second = Qt::Key_unknown;
|
||||
}
|
||||
|
||||
key_map[input_id] = key_pressed;
|
||||
}
|
||||
|
|
|
@ -4,10 +4,14 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <QKeyEvent>
|
||||
#include <QWidget>
|
||||
#include <boost/optional.hpp>
|
||||
#include "common/param_package.h"
|
||||
#include "core/settings.h"
|
||||
#include "ui_configure_input.h"
|
||||
|
||||
|
@ -31,15 +35,25 @@ public:
|
|||
private:
|
||||
std::unique_ptr<Ui::ConfigureInput> ui;
|
||||
|
||||
/// This input is currently awaiting configuration.
|
||||
/// (i.e.: its corresponding QPushButton has been pressed.)
|
||||
boost::optional<Settings::NativeInput::Values> current_input_id;
|
||||
std::unique_ptr<QTimer> timer;
|
||||
|
||||
/// Each input is represented by a QPushButton.
|
||||
std::map<Settings::NativeInput::Values, QPushButton*> button_map;
|
||||
/// Each input is configured to respond to the press of a Qt::Key.
|
||||
std::map<Settings::NativeInput::Values, Qt::Key> key_map;
|
||||
/// This will be the the setting function when an input is awaiting configuration.
|
||||
boost::optional<std::function<void(int)>> key_setter;
|
||||
|
||||
std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param;
|
||||
std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param;
|
||||
|
||||
static constexpr int ANALOG_SUB_BUTTONS_NUM = 5;
|
||||
|
||||
/// Each button input is represented by a QPushButton.
|
||||
std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map;
|
||||
|
||||
/// Each analog input is represented by five QPushButtons which represents up, down, left, right
|
||||
/// and modifier
|
||||
std::array<std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM>, Settings::NativeAnalog::NumAnalogs>
|
||||
analog_map;
|
||||
|
||||
static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons;
|
||||
|
||||
/// Load configuration settings.
|
||||
void loadConfiguration();
|
||||
|
@ -48,10 +62,8 @@ private:
|
|||
/// Update UI to reflect current configuration.
|
||||
void updateButtonLabels();
|
||||
|
||||
/// Called when the button corresponding to input_id was pressed.
|
||||
void handleClick(Settings::NativeInput::Values input_id);
|
||||
/// Called when the button was pressed.
|
||||
void handleClick(QPushButton* button, std::function<void(int)> new_key_setter);
|
||||
/// Handle key press events.
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
/// Configure input input_id to respond to key key_pressed.
|
||||
void setInput(Settings::NativeInput::Values input_id, Qt::Key key_pressed);
|
||||
};
|
||||
|
|
|
@ -35,6 +35,7 @@ set(SRCS
|
|||
memory_util.cpp
|
||||
microprofile.cpp
|
||||
misc.cpp
|
||||
param_package.cpp
|
||||
scm_rev.cpp
|
||||
string_util.cpp
|
||||
symbols.cpp
|
||||
|
@ -66,6 +67,7 @@ set(HEADERS
|
|||
memory_util.h
|
||||
microprofile.h
|
||||
microprofileui.h
|
||||
param_package.h
|
||||
platform.h
|
||||
quaternion.h
|
||||
scm_rev.h
|
||||
|
|
|
@ -71,6 +71,7 @@ namespace Log {
|
|||
CLS(Audio) \
|
||||
SUB(Audio, DSP) \
|
||||
SUB(Audio, Sink) \
|
||||
CLS(Input) \
|
||||
CLS(Loader)
|
||||
|
||||
// GetClassName is a macro defined by Windows.h, grrr...
|
||||
|
|
|
@ -89,6 +89,7 @@ enum class Class : ClassType {
|
|||
Audio_DSP, ///< The HLE implementation of the DSP
|
||||
Audio_Sink, ///< Emulator audio output backend
|
||||
Loader, ///< ROM loader
|
||||
Input, ///< Input emulation
|
||||
Count ///< Total number of logging classes
|
||||
};
|
||||
|
||||
|
|
120
src/common/param_package.cpp
Normal file
120
src/common/param_package.cpp
Normal file
|
@ -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
|
40
src/common/param_package.h
Normal file
40
src/common/param_package.h
Normal file
|
@ -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
|
|
@ -34,7 +34,6 @@ set(SRCS
|
|||
frontend/camera/factory.cpp
|
||||
frontend/camera/interface.cpp
|
||||
frontend/emu_window.cpp
|
||||
frontend/key_map.cpp
|
||||
frontend/motion_emu.cpp
|
||||
gdbstub/gdbstub.cpp
|
||||
hle/config_mem.cpp
|
||||
|
@ -218,7 +217,7 @@ set(HEADERS
|
|||
frontend/camera/factory.h
|
||||
frontend/camera/interface.h
|
||||
frontend/emu_window.h
|
||||
frontend/key_map.h
|
||||
frontend/input.h
|
||||
frontend/motion_emu.h
|
||||
gdbstub/gdbstub.h
|
||||
hle/config_mem.h
|
||||
|
|
|
@ -7,33 +7,9 @@
|
|||
#include "common/assert.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/frontend/key_map.h"
|
||||
#include "core/settings.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
void EmuWindow::ButtonPressed(Service::HID::PadState pad) {
|
||||
pad_state.hex |= pad.hex;
|
||||
}
|
||||
|
||||
void EmuWindow::ButtonReleased(Service::HID::PadState pad) {
|
||||
pad_state.hex &= ~pad.hex;
|
||||
}
|
||||
|
||||
void EmuWindow::CirclePadUpdated(float x, float y) {
|
||||
constexpr int MAX_CIRCLEPAD_POS = 0x9C; // Max value for a circle pad position
|
||||
|
||||
// Make sure the coordinates are in the unit circle,
|
||||
// otherwise normalize it.
|
||||
float r = x * x + y * y;
|
||||
if (r > 1) {
|
||||
r = std::sqrt(r);
|
||||
x /= r;
|
||||
y /= r;
|
||||
}
|
||||
|
||||
circle_pad_x = static_cast<s16>(x * MAX_CIRCLEPAD_POS);
|
||||
circle_pad_y = static_cast<s16>(y * MAX_CIRCLEPAD_POS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
#include "common/common_types.h"
|
||||
#include "common/framebuffer_layout.h"
|
||||
#include "common/math_util.h"
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
|
||||
/**
|
||||
* Abstraction class used to provide an interface between emulation code and the frontend
|
||||
|
@ -52,30 +51,6 @@ public:
|
|||
/// Releases (dunno if this is the "right" word) the GLFW context from the caller thread
|
||||
virtual void DoneCurrent() = 0;
|
||||
|
||||
virtual void ReloadSetKeymaps() = 0;
|
||||
|
||||
/**
|
||||
* Signals a button press action to the HID module.
|
||||
* @param pad_state indicates which button to press
|
||||
* @note only handles real buttons (A/B/X/Y/...), excluding analog inputs like the circle pad.
|
||||
*/
|
||||
void ButtonPressed(Service::HID::PadState pad_state);
|
||||
|
||||
/**
|
||||
* Signals a button release action to the HID module.
|
||||
* @param pad_state indicates which button to press
|
||||
* @note only handles real buttons (A/B/X/Y/...), excluding analog inputs like the circle pad.
|
||||
*/
|
||||
void ButtonReleased(Service::HID::PadState pad_state);
|
||||
|
||||
/**
|
||||
* Signals a circle pad change action to the HID module.
|
||||
* @param x new x-coordinate of the circle pad, in the range [-1.0, 1.0]
|
||||
* @param y new y-coordinate of the circle pad, in the range [-1.0, 1.0]
|
||||
* @note the coordinates will be normalized if the radius is larger than 1
|
||||
*/
|
||||
void CirclePadUpdated(float x, float y);
|
||||
|
||||
/**
|
||||
* Signal that a touch pressed event has occurred (e.g. mouse click pressed)
|
||||
* @param framebuffer_x Framebuffer x-coordinate that was pressed
|
||||
|
@ -114,27 +89,6 @@ public:
|
|||
*/
|
||||
void GyroscopeChanged(float x, float y, float z);
|
||||
|
||||
/**
|
||||
* Gets the current pad state (which buttons are pressed).
|
||||
* @note This should be called by the core emu thread to get a state set by the window thread.
|
||||
* @note This doesn't include analog input like circle pad direction
|
||||
* @todo Fix this function to be thread-safe.
|
||||
* @return PadState object indicating the current pad state
|
||||
*/
|
||||
Service::HID::PadState GetPadState() const {
|
||||
return pad_state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current circle pad state.
|
||||
* @note This should be called by the core emu thread to get a state set by the window thread.
|
||||
* @todo Fix this function to be thread-safe.
|
||||
* @return std::tuple of (x, y), where `x` and `y` are the circle pad coordinates
|
||||
*/
|
||||
std::tuple<s16, s16> GetCirclePadState() const {
|
||||
return std::make_tuple(circle_pad_x, circle_pad_y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current touch screen state (touch X/Y coordinates and whether or not it is pressed).
|
||||
* @note This should be called by the core emu thread to get a state set by the window thread.
|
||||
|
@ -230,11 +184,8 @@ protected:
|
|||
// TODO: Find a better place to set this.
|
||||
config.min_client_area_size = std::make_pair(400u, 480u);
|
||||
active_config = config;
|
||||
pad_state.hex = 0;
|
||||
touch_x = 0;
|
||||
touch_y = 0;
|
||||
circle_pad_x = 0;
|
||||
circle_pad_y = 0;
|
||||
touch_pressed = false;
|
||||
accel_x = 0;
|
||||
accel_y = -512;
|
||||
|
@ -304,9 +255,6 @@ private:
|
|||
u16 touch_x; ///< Touchpad X-position in native 3DS pixel coordinates (0-320)
|
||||
u16 touch_y; ///< Touchpad Y-position in native 3DS pixel coordinates (0-240)
|
||||
|
||||
s16 circle_pad_x; ///< Circle pad X-position in native 3DS pixel coordinates (-156 - 156)
|
||||
s16 circle_pad_y; ///< Circle pad Y-position in native 3DS pixel coordinates (-156 - 156)
|
||||
|
||||
std::mutex accel_mutex;
|
||||
s16 accel_x; ///< Accelerometer X-axis value in native 3DS units
|
||||
s16 accel_y; ///< Accelerometer Y-axis value in native 3DS units
|
||||
|
@ -321,6 +269,4 @@ private:
|
|||
* Clip the provided coordinates to be inside the touchscreen area.
|
||||
*/
|
||||
std::tuple<unsigned, unsigned> ClipToTouchScreen(unsigned new_x, unsigned new_y);
|
||||
|
||||
Service::HID::PadState pad_state;
|
||||
};
|
||||
|
|
110
src/core/frontend/input.h
Normal file
110
src/core/frontend/input.h
Normal file
|
@ -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);
|
||||
}
|
|
@ -2,10 +2,14 @@
|
|||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/frontend/input.h"
|
||||
#include "core/hle/kernel/event.h"
|
||||
#include "core/hle/kernel/shared_memory.h"
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
|
@ -44,6 +48,11 @@ constexpr u64 pad_update_ticks = BASE_CLOCK_RATE_ARM11 / 234;
|
|||
constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE_ARM11 / 104;
|
||||
constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101;
|
||||
|
||||
static std::atomic<bool> is_device_reload_pending;
|
||||
static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
|
||||
buttons;
|
||||
static std::unique_ptr<Input::AnalogDevice> circle_pad;
|
||||
|
||||
static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
|
||||
// 30 degree and 60 degree are angular thresholds for directions
|
||||
constexpr float TAN30 = 0.577350269f;
|
||||
|
@ -74,14 +83,48 @@ static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
|
|||
return state;
|
||||
}
|
||||
|
||||
static void LoadInputDevices() {
|
||||
std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
|
||||
Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
|
||||
buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
|
||||
circle_pad = Input::CreateDevice<Input::AnalogDevice>(
|
||||
Settings::values.analogs[Settings::NativeAnalog::CirclePad]);
|
||||
}
|
||||
|
||||
static void UnloadInputDevices() {
|
||||
for (auto& button : buttons) {
|
||||
button.reset();
|
||||
}
|
||||
circle_pad.reset();
|
||||
}
|
||||
|
||||
static void UpdatePadCallback(u64 userdata, int cycles_late) {
|
||||
SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer());
|
||||
|
||||
PadState state = VideoCore::g_emu_window->GetPadState();
|
||||
if (is_device_reload_pending.exchange(false))
|
||||
LoadInputDevices();
|
||||
|
||||
PadState state;
|
||||
using namespace Settings::NativeButton;
|
||||
state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.right.Assign(buttons[Right - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.left.Assign(buttons[Left - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.up.Assign(buttons[Up - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.down.Assign(buttons[Down - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.start.Assign(buttons[Start - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.select.Assign(buttons[Select - BUTTON_HID_BEGIN]->GetStatus());
|
||||
|
||||
// Get current circle pad position and update circle pad direction
|
||||
s16 circle_pad_x, circle_pad_y;
|
||||
std::tie(circle_pad_x, circle_pad_y) = VideoCore::g_emu_window->GetCirclePadState();
|
||||
float circle_pad_x_f, circle_pad_y_f;
|
||||
std::tie(circle_pad_x_f, circle_pad_y_f) = circle_pad->GetStatus();
|
||||
constexpr int MAX_CIRCLEPAD_POS = 0x9C; // Max value for a circle pad position
|
||||
s16 circle_pad_x = static_cast<s16>(circle_pad_x_f * MAX_CIRCLEPAD_POS);
|
||||
s16 circle_pad_y = static_cast<s16>(circle_pad_y_f * MAX_CIRCLEPAD_POS);
|
||||
state.hex |= GetCirclePadDirectionState(circle_pad_x, circle_pad_y).hex;
|
||||
|
||||
mem->pad.current_state.hex = state.hex;
|
||||
|
@ -313,6 +356,8 @@ void Init() {
|
|||
AddService(new HID_U_Interface);
|
||||
AddService(new HID_SPVR_Interface);
|
||||
|
||||
is_device_reload_pending.store(true);
|
||||
|
||||
using Kernel::MemoryPermission;
|
||||
shared_mem =
|
||||
SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, MemoryPermission::Read,
|
||||
|
@ -350,6 +395,11 @@ void Shutdown() {
|
|||
event_accelerometer = nullptr;
|
||||
event_gyroscope = nullptr;
|
||||
event_debug_pad = nullptr;
|
||||
UnloadInputDevices();
|
||||
}
|
||||
|
||||
void ReloadInputDevices() {
|
||||
is_device_reload_pending.store(true);
|
||||
}
|
||||
|
||||
} // namespace HID
|
||||
|
|
|
@ -39,13 +39,6 @@ struct PadState {
|
|||
BitField<10, 1, u32> x;
|
||||
BitField<11, 1, u32> y;
|
||||
|
||||
BitField<14, 1, u32> zl;
|
||||
BitField<15, 1, u32> zr;
|
||||
|
||||
BitField<24, 1, u32> c_right;
|
||||
BitField<25, 1, u32> c_left;
|
||||
BitField<26, 1, u32> c_up;
|
||||
BitField<27, 1, u32> c_down;
|
||||
BitField<28, 1, u32> circle_right;
|
||||
BitField<29, 1, u32> circle_left;
|
||||
BitField<30, 1, u32> circle_up;
|
||||
|
@ -183,33 +176,6 @@ ASSERT_REG_POSITION(touch.index_reset_ticks, 0x2A);
|
|||
#undef ASSERT_REG_POSITION
|
||||
#endif // !defined(_MSC_VER)
|
||||
|
||||
// Pre-defined PadStates for single button presses
|
||||
const PadState PAD_NONE = {{0}};
|
||||
const PadState PAD_A = {{1u << 0}};
|
||||
const PadState PAD_B = {{1u << 1}};
|
||||
const PadState PAD_SELECT = {{1u << 2}};
|
||||
const PadState PAD_START = {{1u << 3}};
|
||||
const PadState PAD_RIGHT = {{1u << 4}};
|
||||
const PadState PAD_LEFT = {{1u << 5}};
|
||||
const PadState PAD_UP = {{1u << 6}};
|
||||
const PadState PAD_DOWN = {{1u << 7}};
|
||||
const PadState PAD_R = {{1u << 8}};
|
||||
const PadState PAD_L = {{1u << 9}};
|
||||
const PadState PAD_X = {{1u << 10}};
|
||||
const PadState PAD_Y = {{1u << 11}};
|
||||
|
||||
const PadState PAD_ZL = {{1u << 14}};
|
||||
const PadState PAD_ZR = {{1u << 15}};
|
||||
|
||||
const PadState PAD_C_RIGHT = {{1u << 24}};
|
||||
const PadState PAD_C_LEFT = {{1u << 25}};
|
||||
const PadState PAD_C_UP = {{1u << 26}};
|
||||
const PadState PAD_C_DOWN = {{1u << 27}};
|
||||
const PadState PAD_CIRCLE_RIGHT = {{1u << 28}};
|
||||
const PadState PAD_CIRCLE_LEFT = {{1u << 29}};
|
||||
const PadState PAD_CIRCLE_UP = {{1u << 30}};
|
||||
const PadState PAD_CIRCLE_DOWN = {{1u << 31}};
|
||||
|
||||
/**
|
||||
* HID::GetIPCHandles service function
|
||||
* Inputs:
|
||||
|
@ -297,5 +263,8 @@ void Init();
|
|||
|
||||
/// Shutdown HID service
|
||||
void Shutdown();
|
||||
|
||||
/// Reload input devices. Used when input configuration changed
|
||||
void ReloadInputDevices();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "audio_core/audio_core.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
#include "settings.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
|
@ -29,6 +30,8 @@ void Apply() {
|
|||
|
||||
AudioCore::SelectSink(values.sink_id);
|
||||
AudioCore::EnableStretching(values.enable_audio_stretching);
|
||||
|
||||
Service::HID::ReloadInputDevices();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -18,64 +18,68 @@ enum class LayoutOption {
|
|||
Custom,
|
||||
};
|
||||
|
||||
namespace NativeInput {
|
||||
|
||||
namespace NativeButton {
|
||||
enum Values {
|
||||
// directly mapped keys
|
||||
A,
|
||||
B,
|
||||
X,
|
||||
Y,
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
L,
|
||||
R,
|
||||
Start,
|
||||
Select,
|
||||
|
||||
ZL,
|
||||
ZR,
|
||||
START,
|
||||
SELECT,
|
||||
HOME,
|
||||
DUP,
|
||||
DDOWN,
|
||||
DLEFT,
|
||||
DRIGHT,
|
||||
CUP,
|
||||
CDOWN,
|
||||
CLEFT,
|
||||
CRIGHT,
|
||||
|
||||
// indirectly mapped keys
|
||||
CIRCLE_UP,
|
||||
CIRCLE_DOWN,
|
||||
CIRCLE_LEFT,
|
||||
CIRCLE_RIGHT,
|
||||
CIRCLE_MODIFIER,
|
||||
Home,
|
||||
|
||||
NUM_INPUTS
|
||||
NumButtons,
|
||||
};
|
||||
|
||||
static const std::array<const char*, NUM_INPUTS> Mapping = {{
|
||||
// directly mapped keys
|
||||
"pad_a", "pad_b", "pad_x", "pad_y", "pad_l", "pad_r", "pad_zl", "pad_zr", "pad_start",
|
||||
"pad_select", "pad_home", "pad_dup", "pad_ddown", "pad_dleft", "pad_dright", "pad_cup",
|
||||
"pad_cdown", "pad_cleft", "pad_cright",
|
||||
constexpr int BUTTON_HID_BEGIN = A;
|
||||
constexpr int BUTTON_IR_BEGIN = ZL;
|
||||
constexpr int BUTTON_NS_BEGIN = Home;
|
||||
|
||||
// indirectly mapped keys
|
||||
"pad_circle_up", "pad_circle_down", "pad_circle_left", "pad_circle_right",
|
||||
"pad_circle_modifier",
|
||||
constexpr int BUTTON_HID_END = BUTTON_IR_BEGIN;
|
||||
constexpr int BUTTON_IR_END = BUTTON_NS_BEGIN;
|
||||
constexpr int BUTTON_NS_END = NumButtons;
|
||||
|
||||
constexpr int NUM_BUTTONS_HID = BUTTON_HID_END - BUTTON_HID_BEGIN;
|
||||
constexpr int NUM_BUTTONS_IR = BUTTON_IR_END - BUTTON_IR_BEGIN;
|
||||
constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN;
|
||||
|
||||
static const std::array<const char*, NumButtons> mapping = {{
|
||||
"button_a", "button_b", "button_x", "button_y", "button_up", "button_down", "button_left",
|
||||
"button_right", "button_l", "button_r", "button_start", "button_select", "button_zl",
|
||||
"button_zr", "button_home",
|
||||
}};
|
||||
static const std::array<Values, NUM_INPUTS> All = {{
|
||||
A, B, X, Y, L, R, ZL, ZR,
|
||||
START, SELECT, HOME, DUP, DDOWN, DLEFT, DRIGHT, CUP,
|
||||
CDOWN, CLEFT, CRIGHT, CIRCLE_UP, CIRCLE_DOWN, CIRCLE_LEFT, CIRCLE_RIGHT, CIRCLE_MODIFIER,
|
||||
} // namespace NativeButton
|
||||
|
||||
namespace NativeAnalog {
|
||||
enum Values {
|
||||
CirclePad,
|
||||
CStick,
|
||||
|
||||
NumAnalogs,
|
||||
};
|
||||
|
||||
static const std::array<const char*, NumAnalogs> mapping = {{
|
||||
"circle_pad", "c_stick",
|
||||
}};
|
||||
}
|
||||
} // namespace NumAnalog
|
||||
|
||||
struct Values {
|
||||
// CheckNew3DS
|
||||
bool is_new_3ds;
|
||||
|
||||
// Controls
|
||||
std::array<int, NativeInput::NUM_INPUTS> input_mappings;
|
||||
float pad_circle_modifier_scale;
|
||||
std::array<std::string, NativeButton::NumButtons> buttons;
|
||||
std::array<std::string, NativeAnalog::NumAnalogs> analogs;
|
||||
|
||||
// Core
|
||||
bool use_cpu_jit;
|
||||
|
|
27
src/input_common/CMakeLists.txt
Normal file
27
src/input_common/CMakeLists.txt
Normal file
|
@ -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()
|
58
src/input_common/analog_from_button.cpp
Executable file
58
src/input_common/analog_from_button.cpp
Executable file
|
@ -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
|
31
src/input_common/analog_from_button.h
Executable file
31
src/input_common/analog_from_button.h
Executable file
|
@ -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
|
82
src/input_common/keyboard.cpp
Normal file
82
src/input_common/keyboard.cpp
Normal file
|
@ -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
|
45
src/input_common/keyboard.h
Normal file
45
src/input_common/keyboard.h
Normal file
|
@ -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
|
63
src/input_common/main.cpp
Normal file
63
src/input_common/main.cpp
Normal file
|
@ -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
|
29
src/input_common/main.h
Normal file
29
src/input_common/main.h
Normal file
|
@ -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
|
202
src/input_common/sdl/sdl.cpp
Normal file
202
src/input_common/sdl/sdl.cpp
Normal file
|
@ -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
|
19
src/input_common/sdl/sdl.h
Normal file
19
src/input_common/sdl/sdl.h
Normal file
|
@ -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
|
|
@ -1,6 +1,7 @@
|
|||
set(SRCS
|
||||
glad.cpp
|
||||
tests.cpp
|
||||
common/param_package.cpp
|
||||
core/file_sys/path_parser.cpp
|
||||
)
|
||||
|
||||
|
|
25
src/tests/common/param_package.cpp
Normal file
25
src/tests/common/param_package.cpp
Normal file
|
@ -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
|
|
@ -17,6 +17,7 @@
|
|||
#include "common/vector_math.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/settings.h"
|
||||
#include "video_core/pica_state.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
|
|
Loading…
Reference in a new issue