Merge pull request #2497 from wwylele/input-2

Refactor input emulation & add SDL gamepad support
This commit is contained in:
bunnei 2017-03-17 14:59:39 -04:00 committed by GitHub
commit 423ab5e2bc
40 changed files with 1241 additions and 571 deletions

View file

@ -5,6 +5,7 @@ add_subdirectory(common)
add_subdirectory(core) add_subdirectory(core)
add_subdirectory(video_core) add_subdirectory(video_core)
add_subdirectory(audio_core) add_subdirectory(audio_core)
add_subdirectory(input_common)
add_subdirectory(tests) add_subdirectory(tests)
if (ENABLE_SDL2) if (ENABLE_SDL2)
add_subdirectory(citra) add_subdirectory(citra)

View file

@ -18,7 +18,7 @@ create_directory_groups(${SRCS} ${HEADERS})
include_directories(${SDL2_INCLUDE_DIR}) include_directories(${SDL2_INCLUDE_DIR})
add_executable(citra ${SRCS} ${HEADERS}) 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) target_link_libraries(citra ${SDL2_LIBRARY} ${OPENGL_gl_LIBRARY} inih glad)
if (MSVC) if (MSVC)
target_link_libraries(citra getopt) target_link_libraries(citra getopt)

View file

@ -8,8 +8,10 @@
#include "citra/default_ini.h" #include "citra/default_ini.h"
#include "common/file_util.h" #include "common/file_util.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/param_package.h"
#include "config.h" #include "config.h"
#include "core/settings.h" #include "core/settings.h"
#include "input_common/main.h"
Config::Config() { Config::Config() {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files. // 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; return true;
} }
static const std::array<int, Settings::NativeInput::NUM_INPUTS> defaults = { static const std::array<int, Settings::NativeButton::NumButtons> default_buttons = {
// directly mapped keys SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_T,
SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_Q, SDL_SCANCODE_W, SDL_SCANCODE_G, SDL_SCANCODE_F, SDL_SCANCODE_H, 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_M, SDL_SCANCODE_N, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_B,
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<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() { void Config::ReadValues() {
// Controls // Controls
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
Settings::values.input_mappings[Settings::NativeInput::All[i]] = std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
sdl2_config->GetInteger("Controls", Settings::NativeInput::Mapping[i], defaults[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 // Core
Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true); Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);

View file

@ -8,34 +8,47 @@ namespace DefaultINI {
const char* sdl2_config_file = R"( const char* sdl2_config_file = R"(
[Controls] [Controls]
pad_start = # The input devices and parameters for each 3DS native input
pad_select = # It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..."
pad_home = # Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values
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 applied modifier scale to circle pad. # for button input, the following devices are avaible:
# Must be in range of 0.0-1.0. Defaults to 0.5 # - "keyboard" (default) for keyboard input. Required parameters:
pad_circle_modifier_scale = # - "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] [Core]
# Whether to use the Just-In-Time (JIT) compiler for CPU emulation # Whether to use the Just-In-Time (JIT) compiler for CPU emulation

View file

@ -12,9 +12,9 @@
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/scm_rev.h" #include "common/scm_rev.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "core/frontend/key_map.h"
#include "core/hle/service/hid/hid.h"
#include "core/settings.h" #include "core/settings.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
#include "video_core/video_core.h" #include "video_core/video_core.h"
void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { 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) { void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) {
if (state == SDL_PRESSED) { if (state == SDL_PRESSED) {
KeyMap::PressKey(*this, {key, keyboard_id}); InputCommon::GetKeyboard()->PressKey(key);
} else if (state == SDL_RELEASED) { } 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() { EmuWindow_SDL2::EmuWindow_SDL2() {
keyboard_id = KeyMap::NewDeviceId(); InputCommon::Init();
ReloadSetKeymaps();
motion_emu = std::make_unique<Motion::MotionEmu>(*this); motion_emu = std::make_unique<Motion::MotionEmu>(*this);
SDL_SetMainReady(); SDL_SetMainReady();
@ -117,6 +116,7 @@ EmuWindow_SDL2::~EmuWindow_SDL2() {
SDL_GL_DeleteContext(gl_context); SDL_GL_DeleteContext(gl_context);
SDL_Quit(); SDL_Quit();
motion_emu = nullptr; motion_emu = nullptr;
InputCommon::Shutdown();
} }
void EmuWindow_SDL2::SwapBuffers() { void EmuWindow_SDL2::SwapBuffers() {
@ -169,15 +169,6 @@ void EmuWindow_SDL2::DoneCurrent() {
SDL_GL_MakeCurrent(render_window, nullptr); 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( void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(
const std::pair<unsigned, unsigned>& minimal_size) { const std::pair<unsigned, unsigned>& minimal_size) {

View file

@ -31,9 +31,6 @@ public:
/// Whether the window is still open, and a close request hasn't yet been sent /// Whether the window is still open, and a close request hasn't yet been sent
bool IsOpen() const; bool IsOpen() const;
/// Load keymap from configuration
void ReloadSetKeymaps() override;
private: private:
/// Called by PollEvents when a key is pressed or released. /// Called by PollEvents when a key is pressed or released.
void OnKeyEvent(int key, u8 state); void OnKeyEvent(int key, u8 state);
@ -61,9 +58,6 @@ private:
/// The OpenGL context associated with the window /// The OpenGL context associated with the window
SDL_GLContext gl_context; SDL_GLContext gl_context;
/// Device id of keyboard for use with KeyMap
int keyboard_id;
/// Motion sensors emulation /// Motion sensors emulation
std::unique_ptr<Motion::MotionEmu> motion_emu; std::unique_ptr<Motion::MotionEmu> motion_emu;
}; };

View file

@ -97,7 +97,7 @@ if (APPLE)
else() else()
add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS}) add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS})
endif() 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 ${OPENGL_gl_LIBRARY} ${CITRA_QT_LIBS})
target_link_libraries(citra-qt ${PLATFORM_LIBRARIES} Threads::Threads) target_link_libraries(citra-qt ${PLATFORM_LIBRARIES} Threads::Threads)

View file

@ -13,7 +13,9 @@
#include "common/scm_rev.h" #include "common/scm_rev.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "core/core.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/debug_utils/debug_utils.h"
#include "video_core/video_core.h" #include "video_core/video_core.h"
@ -99,14 +101,17 @@ private:
}; };
GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) 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, std::string window_title = Common::StringFromFormat("Citra %s| %s-%s", Common::g_build_name,
Common::g_scm_branch, Common::g_scm_desc); Common::g_scm_branch, Common::g_scm_desc);
setWindowTitle(QString::fromStdString(window_title)); setWindowTitle(QString::fromStdString(window_title));
keyboard_id = KeyMap::NewDeviceId(); InputCommon::Init();
ReloadSetKeymaps(); }
GRenderWindow::~GRenderWindow() {
InputCommon::Shutdown();
} }
void GRenderWindow::moveContext() { void GRenderWindow::moveContext() {
@ -197,11 +202,11 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
} }
void GRenderWindow::keyPressEvent(QKeyEvent* event) { void GRenderWindow::keyPressEvent(QKeyEvent* event) {
KeyMap::PressKey(*this, {event->key(), keyboard_id}); InputCommon::GetKeyboard()->PressKey(event->key());
} }
void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
KeyMap::ReleaseKey(*this, {event->key(), keyboard_id}); InputCommon::GetKeyboard()->ReleaseKey(event->key());
} }
void GRenderWindow::mousePressEvent(QMouseEvent* event) { void GRenderWindow::mousePressEvent(QMouseEvent* event) {
@ -230,14 +235,7 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
motion_emu->EndTilt(); motion_emu->EndTilt();
} }
void GRenderWindow::ReloadSetKeymaps() { 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::OnClientAreaResized(unsigned width, unsigned height) { void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) {
NotifyClientAreaSizeChanged(std::make_pair(width, height)); NotifyClientAreaSizeChanged(std::make_pair(width, height));

View file

@ -104,6 +104,7 @@ class GRenderWindow : public QWidget, public EmuWindow {
public: public:
GRenderWindow(QWidget* parent, EmuThread* emu_thread); GRenderWindow(QWidget* parent, EmuThread* emu_thread);
~GRenderWindow();
// EmuWindow implementation // EmuWindow implementation
void SwapBuffers() override; void SwapBuffers() override;
@ -127,7 +128,7 @@ public:
void mouseMoveEvent(QMouseEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override;
void ReloadSetKeymaps() override; void ReloadSetKeymaps();
void OnClientAreaResized(unsigned width, unsigned height); void OnClientAreaResized(unsigned width, unsigned height);
@ -152,9 +153,6 @@ private:
QByteArray geometry; QByteArray geometry;
/// Device id of keyboard for use with KeyMap
int keyboard_id;
EmuThread* emu_thread; EmuThread* emu_thread;
/// Motion sensors emulation /// Motion sensors emulation

View file

@ -6,6 +6,7 @@
#include "citra_qt/config.h" #include "citra_qt/config.h"
#include "citra_qt/ui_settings.h" #include "citra_qt/ui_settings.h"
#include "common/file_util.h" #include "common/file_util.h"
#include "input_common/main.h"
Config::Config() { Config::Config() {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files. // TODO: Don't hardcode the path; let the frontend decide where to put the config files.
@ -16,25 +17,46 @@ Config::Config() {
Reload(); Reload();
} }
const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> Config::defaults = { const std::array<int, Settings::NativeButton::NumButtons> Config::default_buttons = {
// directly mapped keys 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_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_Q, Qt::Key_W, Qt::Key_1, Qt::Key_2, Qt::Key_Q, Qt::Key_W, Qt::Key_M, Qt::Key_N, Qt::Key_1, Qt::Key_2, Qt::Key_B,
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<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() { void Config::ReadValues() {
qt_config->beginGroup("Controls"); qt_config->beginGroup("Controls");
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
Settings::values.input_mappings[Settings::NativeInput::All[i]] = std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
qt_config->value(QString::fromStdString(Settings::NativeInput::Mapping[i]), defaults[i]) Settings::values.buttons[i] =
.toInt(); 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->endGroup();
qt_config->beginGroup("Core"); qt_config->beginGroup("Core");
@ -155,12 +177,14 @@ void Config::ReadValues() {
void Config::SaveValues() { void Config::SaveValues() {
qt_config->beginGroup("Controls"); qt_config->beginGroup("Controls");
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
qt_config->setValue(QString::fromStdString(Settings::NativeInput::Mapping[i]), qt_config->setValue(QString::fromStdString(Settings::NativeButton::mapping[i]),
Settings::values.input_mappings[Settings::NativeInput::All[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->endGroup();
qt_config->beginGroup("Core"); qt_config->beginGroup("Core");

View file

@ -4,6 +4,7 @@
#pragma once #pragma once
#include <array>
#include <string> #include <string>
#include <QVariant> #include <QVariant>
#include "core/settings.h" #include "core/settings.h"
@ -23,5 +24,7 @@ public:
void Reload(); void Reload();
void Save(); 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;
}; };

View file

@ -2,13 +2,21 @@
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <algorithm>
#include <memory> #include <memory>
#include <utility> #include <utility>
#include <QTimer> #include <QTimer>
#include "citra_qt/config.h" #include "citra_qt/config.h"
#include "citra_qt/configure_input.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) { switch (key_code) {
case Qt::Key_Shift: case Qt::Key_Shift:
return QObject::tr("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) ConfigureInput::ConfigureInput(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()), : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()),
timer(std::make_unique<QTimer>()) { timer(std::make_unique<QTimer>()) {
@ -31,36 +53,41 @@ ConfigureInput::ConfigureInput(QWidget* parent)
setFocusPolicy(Qt::ClickFocus); setFocusPolicy(Qt::ClickFocus);
button_map = { button_map = {
{Settings::NativeInput::Values::A, ui->buttonA}, ui->buttonA, ui->buttonB, ui->buttonX, ui->buttonY, ui->buttonDpadUp,
{Settings::NativeInput::Values::B, ui->buttonB}, ui->buttonDpadDown, ui->buttonDpadLeft, ui->buttonDpadRight, ui->buttonL, ui->buttonR,
{Settings::NativeInput::Values::X, ui->buttonX}, ui->buttonStart, ui->buttonSelect, ui->buttonZL, ui->buttonZR, ui->buttonHome,
{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},
}; };
for (const auto& entry : button_map) { analog_map = {{
const Settings::NativeInput::Values input_id = entry.first; {
connect(entry.second, &QPushButton::released, ui->buttonCircleUp, ui->buttonCircleDown, ui->buttonCircleLeft, ui->buttonCircleRight,
[this, input_id]() { handleClick(input_id); }); 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(); }); connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); });
@ -69,50 +96,93 @@ ConfigureInput::ConfigureInput(QWidget* parent)
connect(timer.get(), &QTimer::timeout, [this]() { connect(timer.get(), &QTimer::timeout, [this]() {
releaseKeyboard(); releaseKeyboard();
releaseMouse(); releaseMouse();
current_input_id = boost::none; key_setter = boost::none;
updateButtonLabels(); updateButtonLabels();
}); });
this->loadConfiguration(); 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() { void ConfigureInput::applyConfiguration() {
for (const auto& input_id : Settings::NativeInput::All) { std::transform(buttons_param.begin(), buttons_param.end(), Settings::values.buttons.begin(),
const size_t index = static_cast<size_t>(input_id); [](const Common::ParamPackage& param) { return param.Serialize(); });
Settings::values.input_mappings[index] = static_cast<int>(key_map[input_id]); std::transform(analogs_param.begin(), analogs_param.end(), Settings::values.analogs.begin(),
} [](const Common::ParamPackage& param) { return param.Serialize(); });
Settings::Apply(); Settings::Apply();
} }
void ConfigureInput::loadConfiguration() { void ConfigureInput::loadConfiguration() {
for (const auto& input_id : Settings::NativeInput::All) { std::transform(Settings::values.buttons.begin(), Settings::values.buttons.end(),
const size_t index = static_cast<size_t>(input_id); buttons_param.begin(),
key_map[input_id] = static_cast<Qt::Key>(Settings::values.input_mappings[index]); [](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(); updateButtonLabels();
} }
void ConfigureInput::restoreDefaults() { void ConfigureInput::restoreDefaults() {
for (const auto& input_id : Settings::NativeInput::All) { for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
const size_t index = static_cast<size_t>(input_id); SetButtonKey(Config::default_buttons[button_id], buttons_param[button_id]);
key_map[input_id] = static_cast<Qt::Key>(Config::defaults[index].toInt()); }
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(); updateButtonLabels();
applyConfiguration(); applyConfiguration();
} }
void ConfigureInput::updateButtonLabels() { void ConfigureInput::updateButtonLabels() {
for (const auto& input_id : Settings::NativeInput::All) { QString non_keyboard(tr("[non-keyboard]"));
button_map[input_id]->setText(getKeyName(key_map[input_id]));
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) { void ConfigureInput::handleClick(QPushButton* button, std::function<void(int)> new_key_setter) {
QPushButton* button = button_map[input_id];
button->setText(tr("[press key]")); button->setText(tr("[press key]"));
button->setFocus(); button->setFocus();
current_input_id = input_id; key_setter = new_key_setter;
grabKeyboard(); grabKeyboard();
grabMouse(); grabMouse();
@ -123,23 +193,13 @@ void ConfigureInput::keyPressEvent(QKeyEvent* event) {
releaseKeyboard(); releaseKeyboard();
releaseMouse(); releaseMouse();
if (!current_input_id || !event) if (!key_setter || !event)
return; return;
if (event->key() != Qt::Key_Escape) if (event->key() != Qt::Key_Escape)
setInput(*current_input_id, static_cast<Qt::Key>(event->key())); (*key_setter)(event->key());
updateButtonLabels(); updateButtonLabels();
current_input_id = boost::none; key_setter = boost::none;
timer->stop(); 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;
}

View file

@ -4,10 +4,14 @@
#pragma once #pragma once
#include <array>
#include <functional>
#include <memory> #include <memory>
#include <string>
#include <QKeyEvent> #include <QKeyEvent>
#include <QWidget> #include <QWidget>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include "common/param_package.h"
#include "core/settings.h" #include "core/settings.h"
#include "ui_configure_input.h" #include "ui_configure_input.h"
@ -31,15 +35,25 @@ public:
private: private:
std::unique_ptr<Ui::ConfigureInput> ui; 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; std::unique_ptr<QTimer> timer;
/// Each input is represented by a QPushButton. /// This will be the the setting function when an input is awaiting configuration.
std::map<Settings::NativeInput::Values, QPushButton*> button_map; boost::optional<std::function<void(int)>> key_setter;
/// Each input is configured to respond to the press of a Qt::Key.
std::map<Settings::NativeInput::Values, Qt::Key> key_map; 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. /// Load configuration settings.
void loadConfiguration(); void loadConfiguration();
@ -48,10 +62,8 @@ private:
/// Update UI to reflect current configuration. /// Update UI to reflect current configuration.
void updateButtonLabels(); void updateButtonLabels();
/// Called when the button corresponding to input_id was pressed. /// Called when the button was pressed.
void handleClick(Settings::NativeInput::Values input_id); void handleClick(QPushButton* button, std::function<void(int)> new_key_setter);
/// Handle key press events. /// Handle key press events.
void keyPressEvent(QKeyEvent* event) override; 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);
}; };

View file

@ -35,6 +35,7 @@ set(SRCS
memory_util.cpp memory_util.cpp
microprofile.cpp microprofile.cpp
misc.cpp misc.cpp
param_package.cpp
scm_rev.cpp scm_rev.cpp
string_util.cpp string_util.cpp
symbols.cpp symbols.cpp
@ -66,6 +67,7 @@ set(HEADERS
memory_util.h memory_util.h
microprofile.h microprofile.h
microprofileui.h microprofileui.h
param_package.h
platform.h platform.h
quaternion.h quaternion.h
scm_rev.h scm_rev.h

View file

@ -71,6 +71,7 @@ namespace Log {
CLS(Audio) \ CLS(Audio) \
SUB(Audio, DSP) \ SUB(Audio, DSP) \
SUB(Audio, Sink) \ SUB(Audio, Sink) \
CLS(Input) \
CLS(Loader) CLS(Loader)
// GetClassName is a macro defined by Windows.h, grrr... // GetClassName is a macro defined by Windows.h, grrr...

View file

@ -89,6 +89,7 @@ enum class Class : ClassType {
Audio_DSP, ///< The HLE implementation of the DSP Audio_DSP, ///< The HLE implementation of the DSP
Audio_Sink, ///< Emulator audio output backend Audio_Sink, ///< Emulator audio output backend
Loader, ///< ROM loader Loader, ///< ROM loader
Input, ///< Input emulation
Count ///< Total number of logging classes Count ///< Total number of logging classes
}; };

View 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

View 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

View file

@ -34,7 +34,6 @@ set(SRCS
frontend/camera/factory.cpp frontend/camera/factory.cpp
frontend/camera/interface.cpp frontend/camera/interface.cpp
frontend/emu_window.cpp frontend/emu_window.cpp
frontend/key_map.cpp
frontend/motion_emu.cpp frontend/motion_emu.cpp
gdbstub/gdbstub.cpp gdbstub/gdbstub.cpp
hle/config_mem.cpp hle/config_mem.cpp
@ -218,7 +217,7 @@ set(HEADERS
frontend/camera/factory.h frontend/camera/factory.h
frontend/camera/interface.h frontend/camera/interface.h
frontend/emu_window.h frontend/emu_window.h
frontend/key_map.h frontend/input.h
frontend/motion_emu.h frontend/motion_emu.h
gdbstub/gdbstub.h gdbstub/gdbstub.h
hle/config_mem.h hle/config_mem.h

View file

@ -7,33 +7,9 @@
#include "common/assert.h" #include "common/assert.h"
#include "core/core.h" #include "core/core.h"
#include "core/frontend/emu_window.h" #include "core/frontend/emu_window.h"
#include "core/frontend/key_map.h" #include "core/settings.h"
#include "video_core/video_core.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 * Check if the given x/y coordinates are within the touchpad specified by the framebuffer layout
* @param layout FramebufferLayout object describing the framebuffer size and screen positions * @param layout FramebufferLayout object describing the framebuffer size and screen positions

View file

@ -10,7 +10,6 @@
#include "common/common_types.h" #include "common/common_types.h"
#include "common/framebuffer_layout.h" #include "common/framebuffer_layout.h"
#include "common/math_util.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 * 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 /// Releases (dunno if this is the "right" word) the GLFW context from the caller thread
virtual void DoneCurrent() = 0; 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) * Signal that a touch pressed event has occurred (e.g. mouse click pressed)
* @param framebuffer_x Framebuffer x-coordinate that was pressed * @param framebuffer_x Framebuffer x-coordinate that was pressed
@ -114,27 +89,6 @@ public:
*/ */
void GyroscopeChanged(float x, float y, float z); 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). * 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. * @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. // TODO: Find a better place to set this.
config.min_client_area_size = std::make_pair(400u, 480u); config.min_client_area_size = std::make_pair(400u, 480u);
active_config = config; active_config = config;
pad_state.hex = 0;
touch_x = 0; touch_x = 0;
touch_y = 0; touch_y = 0;
circle_pad_x = 0;
circle_pad_y = 0;
touch_pressed = false; touch_pressed = false;
accel_x = 0; accel_x = 0;
accel_y = -512; accel_y = -512;
@ -304,9 +255,6 @@ private:
u16 touch_x; ///< Touchpad X-position in native 3DS pixel coordinates (0-320) 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) 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; std::mutex accel_mutex;
s16 accel_x; ///< Accelerometer X-axis value in native 3DS units s16 accel_x; ///< Accelerometer X-axis value in native 3DS units
s16 accel_y; ///< Accelerometer Y-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. * Clip the provided coordinates to be inside the touchscreen area.
*/ */
std::tuple<unsigned, unsigned> ClipToTouchScreen(unsigned new_x, unsigned new_y); std::tuple<unsigned, unsigned> ClipToTouchScreen(unsigned new_x, unsigned new_y);
Service::HID::PadState pad_state;
}; };

110
src/core/frontend/input.h Normal file
View 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

View file

@ -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;
}
}
}
}

View file

@ -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);
}

View file

@ -2,10 +2,14 @@
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <algorithm>
#include <atomic>
#include <cmath> #include <cmath>
#include <memory>
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/frontend/emu_window.h" #include "core/frontend/emu_window.h"
#include "core/frontend/input.h"
#include "core/hle/kernel/event.h" #include "core/hle/kernel/event.h"
#include "core/hle/kernel/shared_memory.h" #include "core/hle/kernel/shared_memory.h"
#include "core/hle/service/hid/hid.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 accelerometer_update_ticks = BASE_CLOCK_RATE_ARM11 / 104;
constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101; 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) { static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
// 30 degree and 60 degree are angular thresholds for directions // 30 degree and 60 degree are angular thresholds for directions
constexpr float TAN30 = 0.577350269f; constexpr float TAN30 = 0.577350269f;
@ -74,14 +83,48 @@ static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
return state; 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) { static void UpdatePadCallback(u64 userdata, int cycles_late) {
SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer()); 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 // Get current circle pad position and update circle pad direction
s16 circle_pad_x, circle_pad_y; float circle_pad_x_f, circle_pad_y_f;
std::tie(circle_pad_x, circle_pad_y) = VideoCore::g_emu_window->GetCirclePadState(); 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; state.hex |= GetCirclePadDirectionState(circle_pad_x, circle_pad_y).hex;
mem->pad.current_state.hex = state.hex; mem->pad.current_state.hex = state.hex;
@ -313,6 +356,8 @@ void Init() {
AddService(new HID_U_Interface); AddService(new HID_U_Interface);
AddService(new HID_SPVR_Interface); AddService(new HID_SPVR_Interface);
is_device_reload_pending.store(true);
using Kernel::MemoryPermission; using Kernel::MemoryPermission;
shared_mem = shared_mem =
SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, MemoryPermission::Read, SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, MemoryPermission::Read,
@ -350,6 +395,11 @@ void Shutdown() {
event_accelerometer = nullptr; event_accelerometer = nullptr;
event_gyroscope = nullptr; event_gyroscope = nullptr;
event_debug_pad = nullptr; event_debug_pad = nullptr;
UnloadInputDevices();
}
void ReloadInputDevices() {
is_device_reload_pending.store(true);
} }
} // namespace HID } // namespace HID

View file

@ -39,13 +39,6 @@ struct PadState {
BitField<10, 1, u32> x; BitField<10, 1, u32> x;
BitField<11, 1, u32> y; 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<28, 1, u32> circle_right;
BitField<29, 1, u32> circle_left; BitField<29, 1, u32> circle_left;
BitField<30, 1, u32> circle_up; BitField<30, 1, u32> circle_up;
@ -183,33 +176,6 @@ ASSERT_REG_POSITION(touch.index_reset_ticks, 0x2A);
#undef ASSERT_REG_POSITION #undef ASSERT_REG_POSITION
#endif // !defined(_MSC_VER) #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 * HID::GetIPCHandles service function
* Inputs: * Inputs:
@ -297,5 +263,8 @@ void Init();
/// Shutdown HID service /// Shutdown HID service
void Shutdown(); void Shutdown();
/// Reload input devices. Used when input configuration changed
void ReloadInputDevices();
} }
} }

View file

@ -4,6 +4,7 @@
#include "audio_core/audio_core.h" #include "audio_core/audio_core.h"
#include "core/gdbstub/gdbstub.h" #include "core/gdbstub/gdbstub.h"
#include "core/hle/service/hid/hid.h"
#include "settings.h" #include "settings.h"
#include "video_core/video_core.h" #include "video_core/video_core.h"
@ -29,6 +30,8 @@ void Apply() {
AudioCore::SelectSink(values.sink_id); AudioCore::SelectSink(values.sink_id);
AudioCore::EnableStretching(values.enable_audio_stretching); AudioCore::EnableStretching(values.enable_audio_stretching);
Service::HID::ReloadInputDevices();
} }
} // namespace } // namespace

View file

@ -18,64 +18,68 @@ enum class LayoutOption {
Custom, Custom,
}; };
namespace NativeInput { namespace NativeButton {
enum Values { enum Values {
// directly mapped keys
A, A,
B, B,
X, X,
Y, Y,
Up,
Down,
Left,
Right,
L, L,
R, R,
Start,
Select,
ZL, ZL,
ZR, ZR,
START,
SELECT,
HOME,
DUP,
DDOWN,
DLEFT,
DRIGHT,
CUP,
CDOWN,
CLEFT,
CRIGHT,
// indirectly mapped keys Home,
CIRCLE_UP,
CIRCLE_DOWN,
CIRCLE_LEFT,
CIRCLE_RIGHT,
CIRCLE_MODIFIER,
NUM_INPUTS NumButtons,
}; };
static const std::array<const char*, NUM_INPUTS> Mapping = {{ constexpr int BUTTON_HID_BEGIN = A;
// directly mapped keys constexpr int BUTTON_IR_BEGIN = ZL;
"pad_a", "pad_b", "pad_x", "pad_y", "pad_l", "pad_r", "pad_zl", "pad_zr", "pad_start", constexpr int BUTTON_NS_BEGIN = Home;
"pad_select", "pad_home", "pad_dup", "pad_ddown", "pad_dleft", "pad_dright", "pad_cup",
"pad_cdown", "pad_cleft", "pad_cright",
// indirectly mapped keys constexpr int BUTTON_HID_END = BUTTON_IR_BEGIN;
"pad_circle_up", "pad_circle_down", "pad_circle_left", "pad_circle_right", constexpr int BUTTON_IR_END = BUTTON_NS_BEGIN;
"pad_circle_modifier", 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 = {{ } // namespace NativeButton
A, B, X, Y, L, R, ZL, ZR,
START, SELECT, HOME, DUP, DDOWN, DLEFT, DRIGHT, CUP, namespace NativeAnalog {
CDOWN, CLEFT, CRIGHT, CIRCLE_UP, CIRCLE_DOWN, CIRCLE_LEFT, CIRCLE_RIGHT, CIRCLE_MODIFIER, enum Values {
CirclePad,
CStick,
NumAnalogs,
};
static const std::array<const char*, NumAnalogs> mapping = {{
"circle_pad", "c_stick",
}}; }};
} } // namespace NumAnalog
struct Values { struct Values {
// CheckNew3DS // CheckNew3DS
bool is_new_3ds; bool is_new_3ds;
// Controls // Controls
std::array<int, NativeInput::NUM_INPUTS> input_mappings; std::array<std::string, NativeButton::NumButtons> buttons;
float pad_circle_modifier_scale; std::array<std::string, NativeAnalog::NumAnalogs> analogs;
// Core // Core
bool use_cpu_jit; bool use_cpu_jit;

View 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()

View 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

View 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

View 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

View 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
View 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
View 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

View 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

View 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

View file

@ -1,6 +1,7 @@
set(SRCS set(SRCS
glad.cpp glad.cpp
tests.cpp tests.cpp
common/param_package.cpp
core/file_sys/path_parser.cpp core/file_sys/path_parser.cpp
) )

View 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

View file

@ -17,6 +17,7 @@
#include "common/vector_math.h" #include "common/vector_math.h"
#include "core/frontend/emu_window.h" #include "core/frontend/emu_window.h"
#include "core/memory.h" #include "core/memory.h"
#include "core/settings.h"
#include "video_core/pica_state.h" #include "video_core/pica_state.h"
#include "video_core/renderer_opengl/gl_rasterizer_cache.h" #include "video_core/renderer_opengl/gl_rasterizer_cache.h"
#include "video_core/renderer_opengl/gl_state.h" #include "video_core/renderer_opengl/gl_state.h"