mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	Merge pull request #2497 from wwylele/input-2
Refactor input emulation & add SDL gamepad support
This commit is contained in:
		
						commit
						423ab5e2bc
					
				
					 40 changed files with 1241 additions and 571 deletions
				
			
		|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue