summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar bunnei2017-03-17 14:59:39 -0400
committerGravatar GitHub2017-03-17 14:59:39 -0400
commit423ab5e2bcf5a522e5f412447c05f648df57a14c (patch)
tree1e60eaeffa59229254a47f885d2fe2cbbdc1a5c0 /src
parentMerge pull request #2618 from wwylele/log-less-filename (diff)
parentqt/config_input: don't connect for null button (diff)
downloadyuzu-423ab5e2bcf5a522e5f412447c05f648df57a14c.tar.gz
yuzu-423ab5e2bcf5a522e5f412447c05f648df57a14c.tar.xz
yuzu-423ab5e2bcf5a522e5f412447c05f648df57a14c.zip
Merge pull request #2497 from wwylele/input-2
Refactor input emulation & add SDL gamepad support
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/citra/CMakeLists.txt2
-rw-r--r--src/citra/config.cpp45
-rw-r--r--src/citra/default_ini.h69
-rw-r--r--src/citra/emu_window/emu_window_sdl2.cpp21
-rw-r--r--src/citra/emu_window/emu_window_sdl2.h6
-rw-r--r--src/citra_qt/CMakeLists.txt2
-rw-r--r--src/citra_qt/bootmanager.cpp26
-rw-r--r--src/citra_qt/bootmanager.h6
-rw-r--r--src/citra_qt/config.cpp62
-rw-r--r--src/citra_qt/config.h5
-rw-r--r--src/citra_qt/configure_input.cpp178
-rw-r--r--src/citra_qt/configure_input.h34
-rw-r--r--src/common/CMakeLists.txt2
-rw-r--r--src/common/logging/backend.cpp1
-rw-r--r--src/common/logging/log.h1
-rw-r--r--src/common/param_package.cpp120
-rw-r--r--src/common/param_package.h40
-rw-r--r--src/core/CMakeLists.txt3
-rw-r--r--src/core/frontend/emu_window.cpp26
-rw-r--r--src/core/frontend/emu_window.h54
-rw-r--r--src/core/frontend/input.h110
-rw-r--r--src/core/frontend/key_map.cpp152
-rw-r--r--src/core/frontend/key_map.h93
-rw-r--r--src/core/hle/service/hid/hid.cpp56
-rw-r--r--src/core/hle/service/hid/hid.h37
-rw-r--r--src/core/settings.cpp3
-rw-r--r--src/core/settings.h80
-rw-r--r--src/input_common/CMakeLists.txt27
-rwxr-xr-xsrc/input_common/analog_from_button.cpp58
-rwxr-xr-xsrc/input_common/analog_from_button.h31
-rw-r--r--src/input_common/keyboard.cpp82
-rw-r--r--src/input_common/keyboard.h45
-rw-r--r--src/input_common/main.cpp63
-rw-r--r--src/input_common/main.h29
-rw-r--r--src/input_common/sdl/sdl.cpp202
-rw-r--r--src/input_common/sdl/sdl.h19
-rw-r--r--src/tests/CMakeLists.txt1
-rw-r--r--src/tests/common/param_package.cpp25
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.cpp1
40 files changed, 1244 insertions, 574 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 1e1245160..a45439481 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -5,6 +5,7 @@ add_subdirectory(common)
5add_subdirectory(core) 5add_subdirectory(core)
6add_subdirectory(video_core) 6add_subdirectory(video_core)
7add_subdirectory(audio_core) 7add_subdirectory(audio_core)
8add_subdirectory(input_common)
8add_subdirectory(tests) 9add_subdirectory(tests)
9if (ENABLE_SDL2) 10if (ENABLE_SDL2)
10 add_subdirectory(citra) 11 add_subdirectory(citra)
diff --git a/src/citra/CMakeLists.txt b/src/citra/CMakeLists.txt
index ecb5d2dfe..47231ba71 100644
--- a/src/citra/CMakeLists.txt
+++ b/src/citra/CMakeLists.txt
@@ -18,7 +18,7 @@ create_directory_groups(${SRCS} ${HEADERS})
18include_directories(${SDL2_INCLUDE_DIR}) 18include_directories(${SDL2_INCLUDE_DIR})
19 19
20add_executable(citra ${SRCS} ${HEADERS}) 20add_executable(citra ${SRCS} ${HEADERS})
21target_link_libraries(citra core video_core audio_core common) 21target_link_libraries(citra core video_core audio_core common input_common)
22target_link_libraries(citra ${SDL2_LIBRARY} ${OPENGL_gl_LIBRARY} inih glad) 22target_link_libraries(citra ${SDL2_LIBRARY} ${OPENGL_gl_LIBRARY} inih glad)
23if (MSVC) 23if (MSVC)
24 target_link_libraries(citra getopt) 24 target_link_libraries(citra getopt)
diff --git a/src/citra/config.cpp b/src/citra/config.cpp
index fac1c9a0e..ef1229912 100644
--- a/src/citra/config.cpp
+++ b/src/citra/config.cpp
@@ -8,8 +8,10 @@
8#include "citra/default_ini.h" 8#include "citra/default_ini.h"
9#include "common/file_util.h" 9#include "common/file_util.h"
10#include "common/logging/log.h" 10#include "common/logging/log.h"
11#include "common/param_package.h"
11#include "config.h" 12#include "config.h"
12#include "core/settings.h" 13#include "core/settings.h"
14#include "input_common/main.h"
13 15
14Config::Config() { 16Config::Config() {
15 // TODO: Don't hardcode the path; let the frontend decide where to put the config files. 17 // 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) {
37 return true; 39 return true;
38} 40}
39 41
40static const std::array<int, Settings::NativeInput::NUM_INPUTS> defaults = { 42static const std::array<int, Settings::NativeButton::NumButtons> default_buttons = {
41 // directly mapped keys 43 SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_T,
42 SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_Q, SDL_SCANCODE_W, 44 SDL_SCANCODE_G, SDL_SCANCODE_F, SDL_SCANCODE_H, SDL_SCANCODE_Q, SDL_SCANCODE_W,
43 SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_M, SDL_SCANCODE_N, SDL_SCANCODE_B, SDL_SCANCODE_T, 45 SDL_SCANCODE_M, SDL_SCANCODE_N, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_B,
44 SDL_SCANCODE_G, SDL_SCANCODE_F, SDL_SCANCODE_H, SDL_SCANCODE_I, SDL_SCANCODE_K, SDL_SCANCODE_J,
45 SDL_SCANCODE_L,
46
47 // indirectly mapped keys
48 SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_D,
49}; 46};
50 47
48static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs{{
49 {
50 SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_D,
51 },
52 {
53 SDL_SCANCODE_I, SDL_SCANCODE_K, SDL_SCANCODE_J, SDL_SCANCODE_L, SDL_SCANCODE_D,
54 },
55}};
56
51void Config::ReadValues() { 57void Config::ReadValues() {
52 // Controls 58 // Controls
53 for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { 59 for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
54 Settings::values.input_mappings[Settings::NativeInput::All[i]] = 60 std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
55 sdl2_config->GetInteger("Controls", Settings::NativeInput::Mapping[i], defaults[i]); 61 Settings::values.buttons[i] =
62 sdl2_config->Get("Controls", Settings::NativeButton::mapping[i], default_param);
63 if (Settings::values.buttons[i].empty())
64 Settings::values.buttons[i] = default_param;
65 }
66
67 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
68 std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
69 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
70 default_analogs[i][3], default_analogs[i][4], 0.5f);
71 Settings::values.analogs[i] =
72 sdl2_config->Get("Controls", Settings::NativeAnalog::mapping[i], default_param);
73 if (Settings::values.analogs[i].empty())
74 Settings::values.analogs[i] = default_param;
56 } 75 }
57 Settings::values.pad_circle_modifier_scale =
58 (float)sdl2_config->GetReal("Controls", "pad_circle_modifier_scale", 0.5);
59 76
60 // Core 77 // Core
61 Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true); 78 Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h
index 435ba6f00..af9f7aa2a 100644
--- a/src/citra/default_ini.h
+++ b/src/citra/default_ini.h
@@ -8,34 +8,47 @@ namespace DefaultINI {
8 8
9const char* sdl2_config_file = R"( 9const char* sdl2_config_file = R"(
10[Controls] 10[Controls]
11pad_start = 11# The input devices and parameters for each 3DS native input
12pad_select = 12# It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..."
13pad_home = 13# Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values
14pad_dup = 14
15pad_ddown = 15# for button input, the following devices are avaible:
16pad_dleft = 16# - "keyboard" (default) for keyboard input. Required parameters:
17pad_dright = 17# - "code": the code of the key to bind
18pad_a = 18# - "sdl" for joystick input using SDL. Required parameters:
19pad_b = 19# - "joystick": the index of the joystick to bind
20pad_x = 20# - "button"(optional): the index of the button to bind
21pad_y = 21# - "hat"(optional): the index of the hat to bind as direction buttons
22pad_l = 22# - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right"
23pad_r = 23button_a=
24pad_zl = 24button_b=
25pad_zr = 25button_x=
26pad_cup = 26button_y=
27pad_cdown = 27button_up=
28pad_cleft = 28button_down=
29pad_cright = 29button_left=
30pad_circle_up = 30button_right=
31pad_circle_down = 31button_l=
32pad_circle_left = 32button_r=
33pad_circle_right = 33button_start=
34pad_circle_modifier = 34button_select=
35 35button_zl=
36# The applied modifier scale to circle pad. 36button_zr=
37# Must be in range of 0.0-1.0. Defaults to 0.5 37button_home=
38pad_circle_modifier_scale = 38
39# for analog input, the following devices are avaible:
40# - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters:
41# - "up", "down", "left", "right": sub-devices for each direction.
42# Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00"
43# - "modifier": sub-devices as a modifier.
44# - "modifier_scale": a float number representing the applied modifier scale to the analog input.
45# Must be in range of 0.0-1.0. Defaults to 0.5
46# - "sdl" for joystick input using SDL. Required parameters:
47# - "joystick": the index of the joystick to bind
48# - "axis_x": the index of the axis to bind as x-axis (default to 0)
49# - "axis_y": the index of the axis to bind as y-axis (default to 1)
50circle_pad=
51c_stick=
39 52
40[Core] 53[Core]
41# Whether to use the Just-In-Time (JIT) compiler for CPU emulation 54# Whether to use the Just-In-Time (JIT) compiler for CPU emulation
diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp
index 00d00905a..6bc0b0d00 100644
--- a/src/citra/emu_window/emu_window_sdl2.cpp
+++ b/src/citra/emu_window/emu_window_sdl2.cpp
@@ -12,9 +12,9 @@
12#include "common/logging/log.h" 12#include "common/logging/log.h"
13#include "common/scm_rev.h" 13#include "common/scm_rev.h"
14#include "common/string_util.h" 14#include "common/string_util.h"
15#include "core/frontend/key_map.h"
16#include "core/hle/service/hid/hid.h"
17#include "core/settings.h" 15#include "core/settings.h"
16#include "input_common/keyboard.h"
17#include "input_common/main.h"
18#include "video_core/video_core.h" 18#include "video_core/video_core.h"
19 19
20void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { 20void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
@@ -40,9 +40,9 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
40 40
41void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) { 41void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) {
42 if (state == SDL_PRESSED) { 42 if (state == SDL_PRESSED) {
43 KeyMap::PressKey(*this, {key, keyboard_id}); 43 InputCommon::GetKeyboard()->PressKey(key);
44 } else if (state == SDL_RELEASED) { 44 } else if (state == SDL_RELEASED) {
45 KeyMap::ReleaseKey(*this, {key, keyboard_id}); 45 InputCommon::GetKeyboard()->ReleaseKey(key);
46 } 46 }
47} 47}
48 48
@@ -57,9 +57,8 @@ void EmuWindow_SDL2::OnResize() {
57} 57}
58 58
59EmuWindow_SDL2::EmuWindow_SDL2() { 59EmuWindow_SDL2::EmuWindow_SDL2() {
60 keyboard_id = KeyMap::NewDeviceId(); 60 InputCommon::Init();
61 61
62 ReloadSetKeymaps();
63 motion_emu = std::make_unique<Motion::MotionEmu>(*this); 62 motion_emu = std::make_unique<Motion::MotionEmu>(*this);
64 63
65 SDL_SetMainReady(); 64 SDL_SetMainReady();
@@ -117,6 +116,7 @@ EmuWindow_SDL2::~EmuWindow_SDL2() {
117 SDL_GL_DeleteContext(gl_context); 116 SDL_GL_DeleteContext(gl_context);
118 SDL_Quit(); 117 SDL_Quit();
119 motion_emu = nullptr; 118 motion_emu = nullptr;
119 InputCommon::Shutdown();
120} 120}
121 121
122void EmuWindow_SDL2::SwapBuffers() { 122void EmuWindow_SDL2::SwapBuffers() {
@@ -169,15 +169,6 @@ void EmuWindow_SDL2::DoneCurrent() {
169 SDL_GL_MakeCurrent(render_window, nullptr); 169 SDL_GL_MakeCurrent(render_window, nullptr);
170} 170}
171 171
172void EmuWindow_SDL2::ReloadSetKeymaps() {
173 KeyMap::ClearKeyMapping(keyboard_id);
174 for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
175 KeyMap::SetKeyMapping(
176 {Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id},
177 KeyMap::mapping_targets[i]);
178 }
179}
180
181void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest( 172void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(
182 const std::pair<unsigned, unsigned>& minimal_size) { 173 const std::pair<unsigned, unsigned>& minimal_size) {
183 174
diff --git a/src/citra/emu_window/emu_window_sdl2.h b/src/citra/emu_window/emu_window_sdl2.h
index b1cbf16d7..1ce2991f7 100644
--- a/src/citra/emu_window/emu_window_sdl2.h
+++ b/src/citra/emu_window/emu_window_sdl2.h
@@ -31,9 +31,6 @@ public:
31 /// Whether the window is still open, and a close request hasn't yet been sent 31 /// Whether the window is still open, and a close request hasn't yet been sent
32 bool IsOpen() const; 32 bool IsOpen() const;
33 33
34 /// Load keymap from configuration
35 void ReloadSetKeymaps() override;
36
37private: 34private:
38 /// Called by PollEvents when a key is pressed or released. 35 /// Called by PollEvents when a key is pressed or released.
39 void OnKeyEvent(int key, u8 state); 36 void OnKeyEvent(int key, u8 state);
@@ -61,9 +58,6 @@ private:
61 /// The OpenGL context associated with the window 58 /// The OpenGL context associated with the window
62 SDL_GLContext gl_context; 59 SDL_GLContext gl_context;
63 60
64 /// Device id of keyboard for use with KeyMap
65 int keyboard_id;
66
67 /// Motion sensors emulation 61 /// Motion sensors emulation
68 std::unique_ptr<Motion::MotionEmu> motion_emu; 62 std::unique_ptr<Motion::MotionEmu> motion_emu;
69}; 63};
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index 15a6ccf9a..2b1c59a92 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -97,7 +97,7 @@ if (APPLE)
97else() 97else()
98 add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS}) 98 add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS})
99endif() 99endif()
100target_link_libraries(citra-qt core video_core audio_core common) 100target_link_libraries(citra-qt core video_core audio_core common input_common)
101target_link_libraries(citra-qt ${OPENGL_gl_LIBRARY} ${CITRA_QT_LIBS}) 101target_link_libraries(citra-qt ${OPENGL_gl_LIBRARY} ${CITRA_QT_LIBS})
102target_link_libraries(citra-qt ${PLATFORM_LIBRARIES} Threads::Threads) 102target_link_libraries(citra-qt ${PLATFORM_LIBRARIES} Threads::Threads)
103 103
diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp
index 69d18cf0c..28264df9a 100644
--- a/src/citra_qt/bootmanager.cpp
+++ b/src/citra_qt/bootmanager.cpp
@@ -13,7 +13,9 @@
13#include "common/scm_rev.h" 13#include "common/scm_rev.h"
14#include "common/string_util.h" 14#include "common/string_util.h"
15#include "core/core.h" 15#include "core/core.h"
16#include "core/frontend/key_map.h" 16#include "core/settings.h"
17#include "input_common/keyboard.h"
18#include "input_common/main.h"
17#include "video_core/debug_utils/debug_utils.h" 19#include "video_core/debug_utils/debug_utils.h"
18#include "video_core/video_core.h" 20#include "video_core/video_core.h"
19 21
@@ -99,14 +101,17 @@ private:
99}; 101};
100 102
101GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) 103GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
102 : QWidget(parent), child(nullptr), keyboard_id(0), emu_thread(emu_thread) { 104 : QWidget(parent), child(nullptr), emu_thread(emu_thread) {
103 105
104 std::string window_title = Common::StringFromFormat("Citra %s| %s-%s", Common::g_build_name, 106 std::string window_title = Common::StringFromFormat("Citra %s| %s-%s", Common::g_build_name,
105 Common::g_scm_branch, Common::g_scm_desc); 107 Common::g_scm_branch, Common::g_scm_desc);
106 setWindowTitle(QString::fromStdString(window_title)); 108 setWindowTitle(QString::fromStdString(window_title));
107 109
108 keyboard_id = KeyMap::NewDeviceId(); 110 InputCommon::Init();
109 ReloadSetKeymaps(); 111}
112
113GRenderWindow::~GRenderWindow() {
114 InputCommon::Shutdown();
110} 115}
111 116
112void GRenderWindow::moveContext() { 117void GRenderWindow::moveContext() {
@@ -197,11 +202,11 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
197} 202}
198 203
199void GRenderWindow::keyPressEvent(QKeyEvent* event) { 204void GRenderWindow::keyPressEvent(QKeyEvent* event) {
200 KeyMap::PressKey(*this, {event->key(), keyboard_id}); 205 InputCommon::GetKeyboard()->PressKey(event->key());
201} 206}
202 207
203void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { 208void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
204 KeyMap::ReleaseKey(*this, {event->key(), keyboard_id}); 209 InputCommon::GetKeyboard()->ReleaseKey(event->key());
205} 210}
206 211
207void GRenderWindow::mousePressEvent(QMouseEvent* event) { 212void GRenderWindow::mousePressEvent(QMouseEvent* event) {
@@ -230,14 +235,7 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
230 motion_emu->EndTilt(); 235 motion_emu->EndTilt();
231} 236}
232 237
233void GRenderWindow::ReloadSetKeymaps() { 238void GRenderWindow::ReloadSetKeymaps() {}
234 KeyMap::ClearKeyMapping(keyboard_id);
235 for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
236 KeyMap::SetKeyMapping(
237 {Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id},
238 KeyMap::mapping_targets[i]);
239 }
240}
241 239
242void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) { 240void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) {
243 NotifyClientAreaSizeChanged(std::make_pair(width, height)); 241 NotifyClientAreaSizeChanged(std::make_pair(width, height));
diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h
index 7dac1c480..923a5b456 100644
--- a/src/citra_qt/bootmanager.h
+++ b/src/citra_qt/bootmanager.h
@@ -104,6 +104,7 @@ class GRenderWindow : public QWidget, public EmuWindow {
104 104
105public: 105public:
106 GRenderWindow(QWidget* parent, EmuThread* emu_thread); 106 GRenderWindow(QWidget* parent, EmuThread* emu_thread);
107 ~GRenderWindow();
107 108
108 // EmuWindow implementation 109 // EmuWindow implementation
109 void SwapBuffers() override; 110 void SwapBuffers() override;
@@ -127,7 +128,7 @@ public:
127 void mouseMoveEvent(QMouseEvent* event) override; 128 void mouseMoveEvent(QMouseEvent* event) override;
128 void mouseReleaseEvent(QMouseEvent* event) override; 129 void mouseReleaseEvent(QMouseEvent* event) override;
129 130
130 void ReloadSetKeymaps() override; 131 void ReloadSetKeymaps();
131 132
132 void OnClientAreaResized(unsigned width, unsigned height); 133 void OnClientAreaResized(unsigned width, unsigned height);
133 134
@@ -152,9 +153,6 @@ private:
152 153
153 QByteArray geometry; 154 QByteArray geometry;
154 155
155 /// Device id of keyboard for use with KeyMap
156 int keyboard_id;
157
158 EmuThread* emu_thread; 156 EmuThread* emu_thread;
159 157
160 /// Motion sensors emulation 158 /// Motion sensors emulation
diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp
index 5fe57dfa2..6ccfa1577 100644
--- a/src/citra_qt/config.cpp
+++ b/src/citra_qt/config.cpp
@@ -6,6 +6,7 @@
6#include "citra_qt/config.h" 6#include "citra_qt/config.h"
7#include "citra_qt/ui_settings.h" 7#include "citra_qt/ui_settings.h"
8#include "common/file_util.h" 8#include "common/file_util.h"
9#include "input_common/main.h"
9 10
10Config::Config() { 11Config::Config() {
11 // TODO: Don't hardcode the path; let the frontend decide where to put the config files. 12 // TODO: Don't hardcode the path; let the frontend decide where to put the config files.
@@ -16,25 +17,46 @@ Config::Config() {
16 Reload(); 17 Reload();
17} 18}
18 19
19const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> Config::defaults = { 20const std::array<int, Settings::NativeButton::NumButtons> Config::default_buttons = {
20 // directly mapped keys 21 Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_T, Qt::Key_G, Qt::Key_F, Qt::Key_H,
21 Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_Q, Qt::Key_W, Qt::Key_1, Qt::Key_2, 22 Qt::Key_Q, Qt::Key_W, Qt::Key_M, Qt::Key_N, Qt::Key_1, Qt::Key_2, Qt::Key_B,
22 Qt::Key_M, Qt::Key_N, Qt::Key_B, Qt::Key_T, Qt::Key_G, Qt::Key_F, Qt::Key_H, Qt::Key_I,
23 Qt::Key_K, Qt::Key_J, Qt::Key_L,
24
25 // indirectly mapped keys
26 Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right, Qt::Key_D,
27}; 23};
28 24
25const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{
26 {
27 Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right, Qt::Key_D,
28 },
29 {
30 Qt::Key_I, Qt::Key_K, Qt::Key_J, Qt::Key_L, Qt::Key_D,
31 },
32}};
33
29void Config::ReadValues() { 34void Config::ReadValues() {
30 qt_config->beginGroup("Controls"); 35 qt_config->beginGroup("Controls");
31 for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { 36 for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
32 Settings::values.input_mappings[Settings::NativeInput::All[i]] = 37 std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
33 qt_config->value(QString::fromStdString(Settings::NativeInput::Mapping[i]), defaults[i]) 38 Settings::values.buttons[i] =
34 .toInt(); 39 qt_config
40 ->value(Settings::NativeButton::mapping[i], QString::fromStdString(default_param))
41 .toString()
42 .toStdString();
43 if (Settings::values.buttons[i].empty())
44 Settings::values.buttons[i] = default_param;
45 }
46
47 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
48 std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
49 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
50 default_analogs[i][3], default_analogs[i][4], 0.5f);
51 Settings::values.analogs[i] =
52 qt_config
53 ->value(Settings::NativeAnalog::mapping[i], QString::fromStdString(default_param))
54 .toString()
55 .toStdString();
56 if (Settings::values.analogs[i].empty())
57 Settings::values.analogs[i] = default_param;
35 } 58 }
36 Settings::values.pad_circle_modifier_scale = 59
37 qt_config->value("pad_circle_modifier_scale", 0.5).toFloat();
38 qt_config->endGroup(); 60 qt_config->endGroup();
39 61
40 qt_config->beginGroup("Core"); 62 qt_config->beginGroup("Core");
@@ -155,12 +177,14 @@ void Config::ReadValues() {
155 177
156void Config::SaveValues() { 178void Config::SaveValues() {
157 qt_config->beginGroup("Controls"); 179 qt_config->beginGroup("Controls");
158 for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { 180 for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
159 qt_config->setValue(QString::fromStdString(Settings::NativeInput::Mapping[i]), 181 qt_config->setValue(QString::fromStdString(Settings::NativeButton::mapping[i]),
160 Settings::values.input_mappings[Settings::NativeInput::All[i]]); 182 QString::fromStdString(Settings::values.buttons[i]));
183 }
184 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
185 qt_config->setValue(QString::fromStdString(Settings::NativeAnalog::mapping[i]),
186 QString::fromStdString(Settings::values.analogs[i]));
161 } 187 }
162 qt_config->setValue("pad_circle_modifier_scale",
163 (double)Settings::values.pad_circle_modifier_scale);
164 qt_config->endGroup(); 188 qt_config->endGroup();
165 189
166 qt_config->beginGroup("Core"); 190 qt_config->beginGroup("Core");
diff --git a/src/citra_qt/config.h b/src/citra_qt/config.h
index 79c901804..cbf745ea2 100644
--- a/src/citra_qt/config.h
+++ b/src/citra_qt/config.h
@@ -4,6 +4,7 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <array>
7#include <string> 8#include <string>
8#include <QVariant> 9#include <QVariant>
9#include "core/settings.h" 10#include "core/settings.h"
@@ -23,5 +24,7 @@ public:
23 24
24 void Reload(); 25 void Reload();
25 void Save(); 26 void Save();
26 static const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> defaults; 27
28 static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
29 static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs;
27}; 30};
diff --git a/src/citra_qt/configure_input.cpp b/src/citra_qt/configure_input.cpp
index c29652f32..b59713e2c 100644
--- a/src/citra_qt/configure_input.cpp
+++ b/src/citra_qt/configure_input.cpp
@@ -2,13 +2,21 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <algorithm>
5#include <memory> 6#include <memory>
6#include <utility> 7#include <utility>
7#include <QTimer> 8#include <QTimer>
8#include "citra_qt/config.h" 9#include "citra_qt/config.h"
9#include "citra_qt/configure_input.h" 10#include "citra_qt/configure_input.h"
11#include "common/param_package.h"
12#include "input_common/main.h"
10 13
11static QString getKeyName(Qt::Key key_code) { 14const std::array<std::string, ConfigureInput::ANALOG_SUB_BUTTONS_NUM>
15 ConfigureInput::analog_sub_buttons{{
16 "up", "down", "left", "right", "modifier",
17 }};
18
19static QString getKeyName(int key_code) {
12 switch (key_code) { 20 switch (key_code) {
13 case Qt::Key_Shift: 21 case Qt::Key_Shift:
14 return QObject::tr("Shift"); 22 return QObject::tr("Shift");
@@ -23,6 +31,20 @@ static QString getKeyName(Qt::Key key_code) {
23 } 31 }
24} 32}
25 33
34static void SetButtonKey(int key, Common::ParamPackage& button_param) {
35 button_param = Common::ParamPackage{InputCommon::GenerateKeyboardParam(key)};
36}
37
38static void SetAnalogKey(int key, Common::ParamPackage& analog_param,
39 const std::string& button_name) {
40 if (analog_param.Get("engine", "") != "analog_from_button") {
41 analog_param = {
42 {"engine", "analog_from_button"}, {"modifier_scale", "0.5"},
43 };
44 }
45 analog_param.Set(button_name, InputCommon::GenerateKeyboardParam(key));
46}
47
26ConfigureInput::ConfigureInput(QWidget* parent) 48ConfigureInput::ConfigureInput(QWidget* parent)
27 : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()), 49 : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()),
28 timer(std::make_unique<QTimer>()) { 50 timer(std::make_unique<QTimer>()) {
@@ -31,36 +53,41 @@ ConfigureInput::ConfigureInput(QWidget* parent)
31 setFocusPolicy(Qt::ClickFocus); 53 setFocusPolicy(Qt::ClickFocus);
32 54
33 button_map = { 55 button_map = {
34 {Settings::NativeInput::Values::A, ui->buttonA}, 56 ui->buttonA, ui->buttonB, ui->buttonX, ui->buttonY, ui->buttonDpadUp,
35 {Settings::NativeInput::Values::B, ui->buttonB}, 57 ui->buttonDpadDown, ui->buttonDpadLeft, ui->buttonDpadRight, ui->buttonL, ui->buttonR,
36 {Settings::NativeInput::Values::X, ui->buttonX}, 58 ui->buttonStart, ui->buttonSelect, ui->buttonZL, ui->buttonZR, ui->buttonHome,
37 {Settings::NativeInput::Values::Y, ui->buttonY},
38 {Settings::NativeInput::Values::L, ui->buttonL},
39 {Settings::NativeInput::Values::R, ui->buttonR},
40 {Settings::NativeInput::Values::ZL, ui->buttonZL},
41 {Settings::NativeInput::Values::ZR, ui->buttonZR},
42 {Settings::NativeInput::Values::START, ui->buttonStart},
43 {Settings::NativeInput::Values::SELECT, ui->buttonSelect},
44 {Settings::NativeInput::Values::HOME, ui->buttonHome},
45 {Settings::NativeInput::Values::DUP, ui->buttonDpadUp},
46 {Settings::NativeInput::Values::DDOWN, ui->buttonDpadDown},
47 {Settings::NativeInput::Values::DLEFT, ui->buttonDpadLeft},
48 {Settings::NativeInput::Values::DRIGHT, ui->buttonDpadRight},
49 {Settings::NativeInput::Values::CUP, ui->buttonCStickUp},
50 {Settings::NativeInput::Values::CDOWN, ui->buttonCStickDown},
51 {Settings::NativeInput::Values::CLEFT, ui->buttonCStickLeft},
52 {Settings::NativeInput::Values::CRIGHT, ui->buttonCStickRight},
53 {Settings::NativeInput::Values::CIRCLE_UP, ui->buttonCircleUp},
54 {Settings::NativeInput::Values::CIRCLE_DOWN, ui->buttonCircleDown},
55 {Settings::NativeInput::Values::CIRCLE_LEFT, ui->buttonCircleLeft},
56 {Settings::NativeInput::Values::CIRCLE_RIGHT, ui->buttonCircleRight},
57 {Settings::NativeInput::Values::CIRCLE_MODIFIER, ui->buttonCircleMod},
58 }; 59 };
59 60
60 for (const auto& entry : button_map) { 61 analog_map = {{
61 const Settings::NativeInput::Values input_id = entry.first; 62 {
62 connect(entry.second, &QPushButton::released, 63 ui->buttonCircleUp, ui->buttonCircleDown, ui->buttonCircleLeft, ui->buttonCircleRight,
63 [this, input_id]() { handleClick(input_id); }); 64 ui->buttonCircleMod,
65 },
66 {
67 ui->buttonCStickUp, ui->buttonCStickDown, ui->buttonCStickLeft, ui->buttonCStickRight,
68 nullptr,
69 },
70 }};
71
72 for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
73 if (button_map[button_id])
74 connect(button_map[button_id], &QPushButton::released, [=]() {
75 handleClick(button_map[button_id],
76 [=](int key) { SetButtonKey(key, buttons_param[button_id]); });
77 });
78 }
79
80 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
81 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
82 if (analog_map[analog_id][sub_button_id] != nullptr) {
83 connect(analog_map[analog_id][sub_button_id], &QPushButton::released, [=]() {
84 handleClick(analog_map[analog_id][sub_button_id], [=](int key) {
85 SetAnalogKey(key, analogs_param[analog_id],
86 analog_sub_buttons[sub_button_id]);
87 });
88 });
89 }
90 }
64 } 91 }
65 92
66 connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); }); 93 connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); });
@@ -69,50 +96,93 @@ ConfigureInput::ConfigureInput(QWidget* parent)
69 connect(timer.get(), &QTimer::timeout, [this]() { 96 connect(timer.get(), &QTimer::timeout, [this]() {
70 releaseKeyboard(); 97 releaseKeyboard();
71 releaseMouse(); 98 releaseMouse();
72 current_input_id = boost::none; 99 key_setter = boost::none;
73 updateButtonLabels(); 100 updateButtonLabels();
74 }); 101 });
75 102
76 this->loadConfiguration(); 103 this->loadConfiguration();
104
105 // TODO(wwylele): enable these when the input emulation for them is implemented
106 ui->buttonZL->setEnabled(false);
107 ui->buttonZR->setEnabled(false);
108 ui->buttonHome->setEnabled(false);
109 ui->buttonCStickUp->setEnabled(false);
110 ui->buttonCStickDown->setEnabled(false);
111 ui->buttonCStickLeft->setEnabled(false);
112 ui->buttonCStickRight->setEnabled(false);
77} 113}
78 114
79void ConfigureInput::applyConfiguration() { 115void ConfigureInput::applyConfiguration() {
80 for (const auto& input_id : Settings::NativeInput::All) { 116 std::transform(buttons_param.begin(), buttons_param.end(), Settings::values.buttons.begin(),
81 const size_t index = static_cast<size_t>(input_id); 117 [](const Common::ParamPackage& param) { return param.Serialize(); });
82 Settings::values.input_mappings[index] = static_cast<int>(key_map[input_id]); 118 std::transform(analogs_param.begin(), analogs_param.end(), Settings::values.analogs.begin(),
83 } 119 [](const Common::ParamPackage& param) { return param.Serialize(); });
120
84 Settings::Apply(); 121 Settings::Apply();
85} 122}
86 123
87void ConfigureInput::loadConfiguration() { 124void ConfigureInput::loadConfiguration() {
88 for (const auto& input_id : Settings::NativeInput::All) { 125 std::transform(Settings::values.buttons.begin(), Settings::values.buttons.end(),
89 const size_t index = static_cast<size_t>(input_id); 126 buttons_param.begin(),
90 key_map[input_id] = static_cast<Qt::Key>(Settings::values.input_mappings[index]); 127 [](const std::string& str) { return Common::ParamPackage(str); });
91 } 128 std::transform(Settings::values.analogs.begin(), Settings::values.analogs.end(),
129 analogs_param.begin(),
130 [](const std::string& str) { return Common::ParamPackage(str); });
92 updateButtonLabels(); 131 updateButtonLabels();
93} 132}
94 133
95void ConfigureInput::restoreDefaults() { 134void ConfigureInput::restoreDefaults() {
96 for (const auto& input_id : Settings::NativeInput::All) { 135 for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
97 const size_t index = static_cast<size_t>(input_id); 136 SetButtonKey(Config::default_buttons[button_id], buttons_param[button_id]);
98 key_map[input_id] = static_cast<Qt::Key>(Config::defaults[index].toInt()); 137 }
138
139 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
140 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
141 SetAnalogKey(Config::default_analogs[analog_id][sub_button_id],
142 analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
143 }
99 } 144 }
100 updateButtonLabels(); 145 updateButtonLabels();
101 applyConfiguration(); 146 applyConfiguration();
102} 147}
103 148
104void ConfigureInput::updateButtonLabels() { 149void ConfigureInput::updateButtonLabels() {
105 for (const auto& input_id : Settings::NativeInput::All) { 150 QString non_keyboard(tr("[non-keyboard]"));
106 button_map[input_id]->setText(getKeyName(key_map[input_id])); 151
152 auto KeyToText = [&non_keyboard](const Common::ParamPackage& param) {
153 if (param.Get("engine", "") != "keyboard") {
154 return non_keyboard;
155 } else {
156 return getKeyName(param.Get("code", 0));
157 }
158 };
159
160 for (int button = 0; button < Settings::NativeButton::NumButtons; button++) {
161 button_map[button]->setText(KeyToText(buttons_param[button]));
162 }
163
164 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
165 if (analogs_param[analog_id].Get("engine", "") != "analog_from_button") {
166 for (QPushButton* button : analog_map[analog_id]) {
167 if (button)
168 button->setText(non_keyboard);
169 }
170 } else {
171 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
172 Common::ParamPackage param(
173 analogs_param[analog_id].Get(analog_sub_buttons[sub_button_id], ""));
174 if (analog_map[analog_id][sub_button_id])
175 analog_map[analog_id][sub_button_id]->setText(KeyToText(param));
176 }
177 }
107 } 178 }
108} 179}
109 180
110void ConfigureInput::handleClick(Settings::NativeInput::Values input_id) { 181void ConfigureInput::handleClick(QPushButton* button, std::function<void(int)> new_key_setter) {
111 QPushButton* button = button_map[input_id];
112 button->setText(tr("[press key]")); 182 button->setText(tr("[press key]"));
113 button->setFocus(); 183 button->setFocus();
114 184
115 current_input_id = input_id; 185 key_setter = new_key_setter;
116 186
117 grabKeyboard(); 187 grabKeyboard();
118 grabMouse(); 188 grabMouse();
@@ -123,23 +193,13 @@ void ConfigureInput::keyPressEvent(QKeyEvent* event) {
123 releaseKeyboard(); 193 releaseKeyboard();
124 releaseMouse(); 194 releaseMouse();
125 195
126 if (!current_input_id || !event) 196 if (!key_setter || !event)
127 return; 197 return;
128 198
129 if (event->key() != Qt::Key_Escape) 199 if (event->key() != Qt::Key_Escape)
130 setInput(*current_input_id, static_cast<Qt::Key>(event->key())); 200 (*key_setter)(event->key());
131 201
132 updateButtonLabels(); 202 updateButtonLabels();
133 current_input_id = boost::none; 203 key_setter = boost::none;
134 timer->stop(); 204 timer->stop();
135} 205}
136
137void ConfigureInput::setInput(Settings::NativeInput::Values input_id, Qt::Key key_pressed) {
138 // Remove duplicates
139 for (auto& pair : key_map) {
140 if (pair.second == key_pressed)
141 pair.second = Qt::Key_unknown;
142 }
143
144 key_map[input_id] = key_pressed;
145}
diff --git a/src/citra_qt/configure_input.h b/src/citra_qt/configure_input.h
index bc343db83..c950fbcb4 100644
--- a/src/citra_qt/configure_input.h
+++ b/src/citra_qt/configure_input.h
@@ -4,10 +4,14 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <array>
8#include <functional>
7#include <memory> 9#include <memory>
10#include <string>
8#include <QKeyEvent> 11#include <QKeyEvent>
9#include <QWidget> 12#include <QWidget>
10#include <boost/optional.hpp> 13#include <boost/optional.hpp>
14#include "common/param_package.h"
11#include "core/settings.h" 15#include "core/settings.h"
12#include "ui_configure_input.h" 16#include "ui_configure_input.h"
13 17
@@ -31,15 +35,25 @@ public:
31private: 35private:
32 std::unique_ptr<Ui::ConfigureInput> ui; 36 std::unique_ptr<Ui::ConfigureInput> ui;
33 37
34 /// This input is currently awaiting configuration.
35 /// (i.e.: its corresponding QPushButton has been pressed.)
36 boost::optional<Settings::NativeInput::Values> current_input_id;
37 std::unique_ptr<QTimer> timer; 38 std::unique_ptr<QTimer> timer;
38 39
39 /// Each input is represented by a QPushButton. 40 /// This will be the the setting function when an input is awaiting configuration.
40 std::map<Settings::NativeInput::Values, QPushButton*> button_map; 41 boost::optional<std::function<void(int)>> key_setter;
41 /// Each input is configured to respond to the press of a Qt::Key. 42
42 std::map<Settings::NativeInput::Values, Qt::Key> key_map; 43 std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param;
44 std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param;
45
46 static constexpr int ANALOG_SUB_BUTTONS_NUM = 5;
47
48 /// Each button input is represented by a QPushButton.
49 std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map;
50
51 /// Each analog input is represented by five QPushButtons which represents up, down, left, right
52 /// and modifier
53 std::array<std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM>, Settings::NativeAnalog::NumAnalogs>
54 analog_map;
55
56 static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons;
43 57
44 /// Load configuration settings. 58 /// Load configuration settings.
45 void loadConfiguration(); 59 void loadConfiguration();
@@ -48,10 +62,8 @@ private:
48 /// Update UI to reflect current configuration. 62 /// Update UI to reflect current configuration.
49 void updateButtonLabels(); 63 void updateButtonLabels();
50 64
51 /// Called when the button corresponding to input_id was pressed. 65 /// Called when the button was pressed.
52 void handleClick(Settings::NativeInput::Values input_id); 66 void handleClick(QPushButton* button, std::function<void(int)> new_key_setter);
53 /// Handle key press events. 67 /// Handle key press events.
54 void keyPressEvent(QKeyEvent* event) override; 68 void keyPressEvent(QKeyEvent* event) override;
55 /// Configure input input_id to respond to key key_pressed.
56 void setInput(Settings::NativeInput::Values input_id, Qt::Key key_pressed);
57}; 69};
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 8a6170257..13277a5c2 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -35,6 +35,7 @@ set(SRCS
35 memory_util.cpp 35 memory_util.cpp
36 microprofile.cpp 36 microprofile.cpp
37 misc.cpp 37 misc.cpp
38 param_package.cpp
38 scm_rev.cpp 39 scm_rev.cpp
39 string_util.cpp 40 string_util.cpp
40 symbols.cpp 41 symbols.cpp
@@ -66,6 +67,7 @@ set(HEADERS
66 memory_util.h 67 memory_util.h
67 microprofile.h 68 microprofile.h
68 microprofileui.h 69 microprofileui.h
70 param_package.h
69 platform.h 71 platform.h
70 quaternion.h 72 quaternion.h
71 scm_rev.h 73 scm_rev.h
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 737e1d57f..42f6a9918 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -71,6 +71,7 @@ namespace Log {
71 CLS(Audio) \ 71 CLS(Audio) \
72 SUB(Audio, DSP) \ 72 SUB(Audio, DSP) \
73 SUB(Audio, Sink) \ 73 SUB(Audio, Sink) \
74 CLS(Input) \
74 CLS(Loader) 75 CLS(Loader)
75 76
76// GetClassName is a macro defined by Windows.h, grrr... 77// GetClassName is a macro defined by Windows.h, grrr...
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 4b0f8ff03..1b905f66c 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -89,6 +89,7 @@ enum class Class : ClassType {
89 Audio_DSP, ///< The HLE implementation of the DSP 89 Audio_DSP, ///< The HLE implementation of the DSP
90 Audio_Sink, ///< Emulator audio output backend 90 Audio_Sink, ///< Emulator audio output backend
91 Loader, ///< ROM loader 91 Loader, ///< ROM loader
92 Input, ///< Input emulation
92 Count ///< Total number of logging classes 93 Count ///< Total number of logging classes
93}; 94};
94 95
diff --git a/src/common/param_package.cpp b/src/common/param_package.cpp
new file mode 100644
index 000000000..3a6ef8c27
--- /dev/null
+++ b/src/common/param_package.cpp
@@ -0,0 +1,120 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <array>
6#include <vector>
7#include "common/logging/log.h"
8#include "common/param_package.h"
9#include "common/string_util.h"
10
11namespace Common {
12
13constexpr char KEY_VALUE_SEPARATOR = ':';
14constexpr char PARAM_SEPARATOR = ',';
15constexpr char ESCAPE_CHARACTER = '$';
16const std::string KEY_VALUE_SEPARATOR_ESCAPE{ESCAPE_CHARACTER, '0'};
17const std::string PARAM_SEPARATOR_ESCAPE{ESCAPE_CHARACTER, '1'};
18const std::string ESCAPE_CHARACTER_ESCAPE{ESCAPE_CHARACTER, '2'};
19
20ParamPackage::ParamPackage(const std::string& serialized) {
21 std::vector<std::string> pairs;
22 Common::SplitString(serialized, PARAM_SEPARATOR, pairs);
23
24 for (const std::string& pair : pairs) {
25 std::vector<std::string> key_value;
26 Common::SplitString(pair, KEY_VALUE_SEPARATOR, key_value);
27 if (key_value.size() != 2) {
28 LOG_ERROR(Common, "invalid key pair %s", pair.c_str());
29 continue;
30 }
31
32 for (std::string& part : key_value) {
33 part = Common::ReplaceAll(part, KEY_VALUE_SEPARATOR_ESCAPE, {KEY_VALUE_SEPARATOR});
34 part = Common::ReplaceAll(part, PARAM_SEPARATOR_ESCAPE, {PARAM_SEPARATOR});
35 part = Common::ReplaceAll(part, ESCAPE_CHARACTER_ESCAPE, {ESCAPE_CHARACTER});
36 }
37
38 Set(key_value[0], key_value[1]);
39 }
40}
41
42ParamPackage::ParamPackage(std::initializer_list<DataType::value_type> list) : data(list) {}
43
44std::string ParamPackage::Serialize() const {
45 if (data.empty())
46 return "";
47
48 std::string result;
49
50 for (const auto& pair : data) {
51 std::array<std::string, 2> key_value{{pair.first, pair.second}};
52 for (std::string& part : key_value) {
53 part = Common::ReplaceAll(part, {ESCAPE_CHARACTER}, ESCAPE_CHARACTER_ESCAPE);
54 part = Common::ReplaceAll(part, {PARAM_SEPARATOR}, PARAM_SEPARATOR_ESCAPE);
55 part = Common::ReplaceAll(part, {KEY_VALUE_SEPARATOR}, KEY_VALUE_SEPARATOR_ESCAPE);
56 }
57 result += key_value[0] + KEY_VALUE_SEPARATOR + key_value[1] + PARAM_SEPARATOR;
58 }
59
60 result.pop_back(); // discard the trailing PARAM_SEPARATOR
61 return result;
62}
63
64std::string ParamPackage::Get(const std::string& key, const std::string& default_value) const {
65 auto pair = data.find(key);
66 if (pair == data.end()) {
67 LOG_DEBUG(Common, "key %s not found", key.c_str());
68 return default_value;
69 }
70
71 return pair->second;
72}
73
74int ParamPackage::Get(const std::string& key, int default_value) const {
75 auto pair = data.find(key);
76 if (pair == data.end()) {
77 LOG_DEBUG(Common, "key %s not found", key.c_str());
78 return default_value;
79 }
80
81 try {
82 return std::stoi(pair->second);
83 } catch (const std::logic_error&) {
84 LOG_ERROR(Common, "failed to convert %s to int", pair->second.c_str());
85 return default_value;
86 }
87}
88
89float ParamPackage::Get(const std::string& key, float default_value) const {
90 auto pair = data.find(key);
91 if (pair == data.end()) {
92 LOG_DEBUG(Common, "key %s not found", key.c_str());
93 return default_value;
94 }
95
96 try {
97 return std::stof(pair->second);
98 } catch (const std::logic_error&) {
99 LOG_ERROR(Common, "failed to convert %s to float", pair->second.c_str());
100 return default_value;
101 }
102}
103
104void ParamPackage::Set(const std::string& key, const std::string& value) {
105 data[key] = value;
106}
107
108void ParamPackage::Set(const std::string& key, int value) {
109 data[key] = std::to_string(value);
110}
111
112void ParamPackage::Set(const std::string& key, float value) {
113 data[key] = std::to_string(value);
114}
115
116bool ParamPackage::Has(const std::string& key) const {
117 return data.find(key) != data.end();
118}
119
120} // namespace Common
diff --git a/src/common/param_package.h b/src/common/param_package.h
new file mode 100644
index 000000000..c4c11b221
--- /dev/null
+++ b/src/common/param_package.h
@@ -0,0 +1,40 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <initializer_list>
8#include <string>
9#include <unordered_map>
10
11namespace Common {
12
13/// A string-based key-value container supporting serializing to and deserializing from a string
14class ParamPackage {
15public:
16 using DataType = std::unordered_map<std::string, std::string>;
17
18 ParamPackage() = default;
19 explicit ParamPackage(const std::string& serialized);
20 ParamPackage(std::initializer_list<DataType::value_type> list);
21 ParamPackage(const ParamPackage& other) = default;
22 ParamPackage(ParamPackage&& other) = default;
23
24 ParamPackage& operator=(const ParamPackage& other) = default;
25 ParamPackage& operator=(ParamPackage&& other) = default;
26
27 std::string Serialize() const;
28 std::string Get(const std::string& key, const std::string& default_value) const;
29 int Get(const std::string& key, int default_value) const;
30 float Get(const std::string& key, float default_value) const;
31 void Set(const std::string& key, const std::string& value);
32 void Set(const std::string& key, int value);
33 void Set(const std::string& key, float value);
34 bool Has(const std::string& key) const;
35
36private:
37 DataType data;
38};
39
40} // namespace Common
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index ffd67f074..61a0b1cc3 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -34,7 +34,6 @@ set(SRCS
34 frontend/camera/factory.cpp 34 frontend/camera/factory.cpp
35 frontend/camera/interface.cpp 35 frontend/camera/interface.cpp
36 frontend/emu_window.cpp 36 frontend/emu_window.cpp
37 frontend/key_map.cpp
38 frontend/motion_emu.cpp 37 frontend/motion_emu.cpp
39 gdbstub/gdbstub.cpp 38 gdbstub/gdbstub.cpp
40 hle/config_mem.cpp 39 hle/config_mem.cpp
@@ -218,7 +217,7 @@ set(HEADERS
218 frontend/camera/factory.h 217 frontend/camera/factory.h
219 frontend/camera/interface.h 218 frontend/camera/interface.h
220 frontend/emu_window.h 219 frontend/emu_window.h
221 frontend/key_map.h 220 frontend/input.h
222 frontend/motion_emu.h 221 frontend/motion_emu.h
223 gdbstub/gdbstub.h 222 gdbstub/gdbstub.h
224 hle/config_mem.h 223 hle/config_mem.h
diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp
index a155b657d..73a44bfe7 100644
--- a/src/core/frontend/emu_window.cpp
+++ b/src/core/frontend/emu_window.cpp
@@ -7,33 +7,9 @@
7#include "common/assert.h" 7#include "common/assert.h"
8#include "core/core.h" 8#include "core/core.h"
9#include "core/frontend/emu_window.h" 9#include "core/frontend/emu_window.h"
10#include "core/frontend/key_map.h" 10#include "core/settings.h"
11#include "video_core/video_core.h" 11#include "video_core/video_core.h"
12 12
13void EmuWindow::ButtonPressed(Service::HID::PadState pad) {
14 pad_state.hex |= pad.hex;
15}
16
17void EmuWindow::ButtonReleased(Service::HID::PadState pad) {
18 pad_state.hex &= ~pad.hex;
19}
20
21void EmuWindow::CirclePadUpdated(float x, float y) {
22 constexpr int MAX_CIRCLEPAD_POS = 0x9C; // Max value for a circle pad position
23
24 // Make sure the coordinates are in the unit circle,
25 // otherwise normalize it.
26 float r = x * x + y * y;
27 if (r > 1) {
28 r = std::sqrt(r);
29 x /= r;
30 y /= r;
31 }
32
33 circle_pad_x = static_cast<s16>(x * MAX_CIRCLEPAD_POS);
34 circle_pad_y = static_cast<s16>(y * MAX_CIRCLEPAD_POS);
35}
36
37/** 13/**
38 * Check if the given x/y coordinates are within the touchpad specified by the framebuffer layout 14 * Check if the given x/y coordinates are within the touchpad specified by the framebuffer layout
39 * @param layout FramebufferLayout object describing the framebuffer size and screen positions 15 * @param layout FramebufferLayout object describing the framebuffer size and screen positions
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index 1ba64c92b..36f2667fa 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -10,7 +10,6 @@
10#include "common/common_types.h" 10#include "common/common_types.h"
11#include "common/framebuffer_layout.h" 11#include "common/framebuffer_layout.h"
12#include "common/math_util.h" 12#include "common/math_util.h"
13#include "core/hle/service/hid/hid.h"
14 13
15/** 14/**
16 * Abstraction class used to provide an interface between emulation code and the frontend 15 * Abstraction class used to provide an interface between emulation code and the frontend
@@ -52,30 +51,6 @@ public:
52 /// Releases (dunno if this is the "right" word) the GLFW context from the caller thread 51 /// Releases (dunno if this is the "right" word) the GLFW context from the caller thread
53 virtual void DoneCurrent() = 0; 52 virtual void DoneCurrent() = 0;
54 53
55 virtual void ReloadSetKeymaps() = 0;
56
57 /**
58 * Signals a button press action to the HID module.
59 * @param pad_state indicates which button to press
60 * @note only handles real buttons (A/B/X/Y/...), excluding analog inputs like the circle pad.
61 */
62 void ButtonPressed(Service::HID::PadState pad_state);
63
64 /**
65 * Signals a button release action to the HID module.
66 * @param pad_state indicates which button to press
67 * @note only handles real buttons (A/B/X/Y/...), excluding analog inputs like the circle pad.
68 */
69 void ButtonReleased(Service::HID::PadState pad_state);
70
71 /**
72 * Signals a circle pad change action to the HID module.
73 * @param x new x-coordinate of the circle pad, in the range [-1.0, 1.0]
74 * @param y new y-coordinate of the circle pad, in the range [-1.0, 1.0]
75 * @note the coordinates will be normalized if the radius is larger than 1
76 */
77 void CirclePadUpdated(float x, float y);
78
79 /** 54 /**
80 * Signal that a touch pressed event has occurred (e.g. mouse click pressed) 55 * Signal that a touch pressed event has occurred (e.g. mouse click pressed)
81 * @param framebuffer_x Framebuffer x-coordinate that was pressed 56 * @param framebuffer_x Framebuffer x-coordinate that was pressed
@@ -115,27 +90,6 @@ public:
115 void GyroscopeChanged(float x, float y, float z); 90 void GyroscopeChanged(float x, float y, float z);
116 91
117 /** 92 /**
118 * Gets the current pad state (which buttons are pressed).
119 * @note This should be called by the core emu thread to get a state set by the window thread.
120 * @note This doesn't include analog input like circle pad direction
121 * @todo Fix this function to be thread-safe.
122 * @return PadState object indicating the current pad state
123 */
124 Service::HID::PadState GetPadState() const {
125 return pad_state;
126 }
127
128 /**
129 * Gets the current circle pad state.
130 * @note This should be called by the core emu thread to get a state set by the window thread.
131 * @todo Fix this function to be thread-safe.
132 * @return std::tuple of (x, y), where `x` and `y` are the circle pad coordinates
133 */
134 std::tuple<s16, s16> GetCirclePadState() const {
135 return std::make_tuple(circle_pad_x, circle_pad_y);
136 }
137
138 /**
139 * Gets the current touch screen state (touch X/Y coordinates and whether or not it is pressed). 93 * Gets the current touch screen state (touch X/Y coordinates and whether or not it is pressed).
140 * @note This should be called by the core emu thread to get a state set by the window thread. 94 * @note This should be called by the core emu thread to get a state set by the window thread.
141 * @todo Fix this function to be thread-safe. 95 * @todo Fix this function to be thread-safe.
@@ -230,11 +184,8 @@ protected:
230 // TODO: Find a better place to set this. 184 // TODO: Find a better place to set this.
231 config.min_client_area_size = std::make_pair(400u, 480u); 185 config.min_client_area_size = std::make_pair(400u, 480u);
232 active_config = config; 186 active_config = config;
233 pad_state.hex = 0;
234 touch_x = 0; 187 touch_x = 0;
235 touch_y = 0; 188 touch_y = 0;
236 circle_pad_x = 0;
237 circle_pad_y = 0;
238 touch_pressed = false; 189 touch_pressed = false;
239 accel_x = 0; 190 accel_x = 0;
240 accel_y = -512; 191 accel_y = -512;
@@ -304,9 +255,6 @@ private:
304 u16 touch_x; ///< Touchpad X-position in native 3DS pixel coordinates (0-320) 255 u16 touch_x; ///< Touchpad X-position in native 3DS pixel coordinates (0-320)
305 u16 touch_y; ///< Touchpad Y-position in native 3DS pixel coordinates (0-240) 256 u16 touch_y; ///< Touchpad Y-position in native 3DS pixel coordinates (0-240)
306 257
307 s16 circle_pad_x; ///< Circle pad X-position in native 3DS pixel coordinates (-156 - 156)
308 s16 circle_pad_y; ///< Circle pad Y-position in native 3DS pixel coordinates (-156 - 156)
309
310 std::mutex accel_mutex; 258 std::mutex accel_mutex;
311 s16 accel_x; ///< Accelerometer X-axis value in native 3DS units 259 s16 accel_x; ///< Accelerometer X-axis value in native 3DS units
312 s16 accel_y; ///< Accelerometer Y-axis value in native 3DS units 260 s16 accel_y; ///< Accelerometer Y-axis value in native 3DS units
@@ -321,6 +269,4 @@ private:
321 * Clip the provided coordinates to be inside the touchscreen area. 269 * Clip the provided coordinates to be inside the touchscreen area.
322 */ 270 */
323 std::tuple<unsigned, unsigned> ClipToTouchScreen(unsigned new_x, unsigned new_y); 271 std::tuple<unsigned, unsigned> ClipToTouchScreen(unsigned new_x, unsigned new_y);
324
325 Service::HID::PadState pad_state;
326}; 272};
diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h
new file mode 100644
index 000000000..0a5713dc0
--- /dev/null
+++ b/src/core/frontend/input.h
@@ -0,0 +1,110 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <string>
9#include <tuple>
10#include <unordered_map>
11#include <utility>
12#include "common/logging/log.h"
13#include "common/param_package.h"
14
15namespace Input {
16
17/// An abstract class template for an input device (a button, an analog input, etc.).
18template <typename StatusType>
19class InputDevice {
20public:
21 virtual ~InputDevice() = default;
22 virtual StatusType GetStatus() const {
23 return {};
24 }
25};
26
27/// An abstract class template for a factory that can create input devices.
28template <typename InputDeviceType>
29class Factory {
30public:
31 virtual ~Factory() = default;
32 virtual std::unique_ptr<InputDeviceType> Create(const Common::ParamPackage&) = 0;
33};
34
35namespace Impl {
36
37template <typename InputDeviceType>
38using FactoryListType = std::unordered_map<std::string, std::shared_ptr<Factory<InputDeviceType>>>;
39
40template <typename InputDeviceType>
41struct FactoryList {
42 static FactoryListType<InputDeviceType> list;
43};
44
45template <typename InputDeviceType>
46FactoryListType<InputDeviceType> FactoryList<InputDeviceType>::list;
47
48} // namespace Impl
49
50/**
51 * Registers an input device factory.
52 * @tparam InputDeviceType the type of input devices the factory can create
53 * @param name the name of the factory. Will be used to match the "engine" parameter when creating
54 * a device
55 * @param factory the factory object to register
56 */
57template <typename InputDeviceType>
58void RegisterFactory(const std::string& name, std::shared_ptr<Factory<InputDeviceType>> factory) {
59 auto pair = std::make_pair(name, std::move(factory));
60 if (!Impl::FactoryList<InputDeviceType>::list.insert(std::move(pair)).second) {
61 LOG_ERROR(Input, "Factory %s already registered", name.c_str());
62 }
63}
64
65/**
66 * Unregisters an input device factory.
67 * @tparam InputDeviceType the type of input devices the factory can create
68 * @param name the name of the factory to unregister
69 */
70template <typename InputDeviceType>
71void UnregisterFactory(const std::string& name) {
72 if (Impl::FactoryList<InputDeviceType>::list.erase(name) == 0) {
73 LOG_ERROR(Input, "Factory %s not registered", name.c_str());
74 }
75}
76
77/**
78 * Create an input device from given paramters.
79 * @tparam InputDeviceType the type of input devices to create
80 * @param params a serialized ParamPackage string contains all parameters for creating the device
81 */
82template <typename InputDeviceType>
83std::unique_ptr<InputDeviceType> CreateDevice(const std::string& params) {
84 const Common::ParamPackage package(params);
85 const std::string engine = package.Get("engine", "null");
86 const auto& factory_list = Impl::FactoryList<InputDeviceType>::list;
87 const auto pair = factory_list.find(engine);
88 if (pair == factory_list.end()) {
89 if (engine != "null") {
90 LOG_ERROR(Input, "Unknown engine name: %s", engine.c_str());
91 }
92 return std::make_unique<InputDeviceType>();
93 }
94 return pair->second->Create(package);
95}
96
97/**
98 * A button device is an input device that returns bool as status.
99 * true for pressed; false for released.
100 */
101using ButtonDevice = InputDevice<bool>;
102
103/**
104 * An analog device is an input device that returns a tuple of x and y coordinates as status. The
105 * coordinates are within the unit circle. x+ is defined as right direction, and y+ is defined as up
106 * direction
107 */
108using AnalogDevice = InputDevice<std::tuple<float, float>>;
109
110} // namespace Input
diff --git a/src/core/frontend/key_map.cpp b/src/core/frontend/key_map.cpp
deleted file mode 100644
index 15f0e079c..000000000
--- a/src/core/frontend/key_map.cpp
+++ /dev/null
@@ -1,152 +0,0 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <map>
6#include "core/frontend/emu_window.h"
7#include "core/frontend/key_map.h"
8
9namespace KeyMap {
10
11// TODO (wwylele): currently we treat c-stick as four direction buttons
12// and map it directly to EmuWindow::ButtonPressed.
13// It should go the analog input way like circle pad does.
14const std::array<KeyTarget, Settings::NativeInput::NUM_INPUTS> mapping_targets = {{
15 Service::HID::PAD_A,
16 Service::HID::PAD_B,
17 Service::HID::PAD_X,
18 Service::HID::PAD_Y,
19 Service::HID::PAD_L,
20 Service::HID::PAD_R,
21 Service::HID::PAD_ZL,
22 Service::HID::PAD_ZR,
23 Service::HID::PAD_START,
24 Service::HID::PAD_SELECT,
25 Service::HID::PAD_NONE,
26 Service::HID::PAD_UP,
27 Service::HID::PAD_DOWN,
28 Service::HID::PAD_LEFT,
29 Service::HID::PAD_RIGHT,
30 Service::HID::PAD_C_UP,
31 Service::HID::PAD_C_DOWN,
32 Service::HID::PAD_C_LEFT,
33 Service::HID::PAD_C_RIGHT,
34
35 IndirectTarget::CirclePadUp,
36 IndirectTarget::CirclePadDown,
37 IndirectTarget::CirclePadLeft,
38 IndirectTarget::CirclePadRight,
39 IndirectTarget::CirclePadModifier,
40}};
41
42static std::map<HostDeviceKey, KeyTarget> key_map;
43static int next_device_id = 0;
44
45static bool circle_pad_up = false;
46static bool circle_pad_down = false;
47static bool circle_pad_left = false;
48static bool circle_pad_right = false;
49static bool circle_pad_modifier = false;
50
51static void UpdateCirclePad(EmuWindow& emu_window) {
52 constexpr float SQRT_HALF = 0.707106781f;
53 int x = 0, y = 0;
54
55 if (circle_pad_right)
56 ++x;
57 if (circle_pad_left)
58 --x;
59 if (circle_pad_up)
60 ++y;
61 if (circle_pad_down)
62 --y;
63
64 float modifier = circle_pad_modifier ? Settings::values.pad_circle_modifier_scale : 1.0f;
65 emu_window.CirclePadUpdated(x * modifier * (y == 0 ? 1.0f : SQRT_HALF),
66 y * modifier * (x == 0 ? 1.0f : SQRT_HALF));
67}
68
69int NewDeviceId() {
70 return next_device_id++;
71}
72
73void SetKeyMapping(HostDeviceKey key, KeyTarget target) {
74 key_map[key] = target;
75}
76
77void ClearKeyMapping(int device_id) {
78 auto iter = key_map.begin();
79 while (iter != key_map.end()) {
80 if (iter->first.device_id == device_id)
81 key_map.erase(iter++);
82 else
83 ++iter;
84 }
85}
86
87void PressKey(EmuWindow& emu_window, HostDeviceKey key) {
88 auto target = key_map.find(key);
89 if (target == key_map.end())
90 return;
91
92 if (target->second.direct) {
93 emu_window.ButtonPressed({{target->second.target.direct_target_hex}});
94 } else {
95 switch (target->second.target.indirect_target) {
96 case IndirectTarget::CirclePadUp:
97 circle_pad_up = true;
98 UpdateCirclePad(emu_window);
99 break;
100 case IndirectTarget::CirclePadDown:
101 circle_pad_down = true;
102 UpdateCirclePad(emu_window);
103 break;
104 case IndirectTarget::CirclePadLeft:
105 circle_pad_left = true;
106 UpdateCirclePad(emu_window);
107 break;
108 case IndirectTarget::CirclePadRight:
109 circle_pad_right = true;
110 UpdateCirclePad(emu_window);
111 break;
112 case IndirectTarget::CirclePadModifier:
113 circle_pad_modifier = true;
114 UpdateCirclePad(emu_window);
115 break;
116 }
117 }
118}
119
120void ReleaseKey(EmuWindow& emu_window, HostDeviceKey key) {
121 auto target = key_map.find(key);
122 if (target == key_map.end())
123 return;
124
125 if (target->second.direct) {
126 emu_window.ButtonReleased({{target->second.target.direct_target_hex}});
127 } else {
128 switch (target->second.target.indirect_target) {
129 case IndirectTarget::CirclePadUp:
130 circle_pad_up = false;
131 UpdateCirclePad(emu_window);
132 break;
133 case IndirectTarget::CirclePadDown:
134 circle_pad_down = false;
135 UpdateCirclePad(emu_window);
136 break;
137 case IndirectTarget::CirclePadLeft:
138 circle_pad_left = false;
139 UpdateCirclePad(emu_window);
140 break;
141 case IndirectTarget::CirclePadRight:
142 circle_pad_right = false;
143 UpdateCirclePad(emu_window);
144 break;
145 case IndirectTarget::CirclePadModifier:
146 circle_pad_modifier = false;
147 UpdateCirclePad(emu_window);
148 break;
149 }
150 }
151}
152}
diff --git a/src/core/frontend/key_map.h b/src/core/frontend/key_map.h
deleted file mode 100644
index 040794578..000000000
--- a/src/core/frontend/key_map.h
+++ /dev/null
@@ -1,93 +0,0 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <tuple>
9#include "core/hle/service/hid/hid.h"
10
11class EmuWindow;
12
13namespace KeyMap {
14
15/**
16 * Represents key mapping targets that are not real 3DS buttons.
17 * They will be handled by KeyMap and translated to 3DS input.
18 */
19enum class IndirectTarget {
20 CirclePadUp,
21 CirclePadDown,
22 CirclePadLeft,
23 CirclePadRight,
24 CirclePadModifier,
25};
26
27/**
28 * Represents a key mapping target. It can be a PadState that represents real 3DS buttons,
29 * or an IndirectTarget.
30 */
31struct KeyTarget {
32 bool direct;
33 union {
34 u32 direct_target_hex;
35 IndirectTarget indirect_target;
36 } target;
37
38 KeyTarget() : direct(true) {
39 target.direct_target_hex = 0;
40 }
41
42 KeyTarget(Service::HID::PadState pad) : direct(true) {
43 target.direct_target_hex = pad.hex;
44 }
45
46 KeyTarget(IndirectTarget i) : direct(false) {
47 target.indirect_target = i;
48 }
49};
50
51/**
52 * Represents a key for a specific host device.
53 */
54struct HostDeviceKey {
55 int key_code;
56 int device_id; ///< Uniquely identifies a host device
57
58 bool operator<(const HostDeviceKey& other) const {
59 return std::tie(key_code, device_id) < std::tie(other.key_code, other.device_id);
60 }
61
62 bool operator==(const HostDeviceKey& other) const {
63 return std::tie(key_code, device_id) == std::tie(other.key_code, other.device_id);
64 }
65};
66
67extern const std::array<KeyTarget, Settings::NativeInput::NUM_INPUTS> mapping_targets;
68
69/**
70 * Generates a new device id, which uniquely identifies a host device within KeyMap.
71 */
72int NewDeviceId();
73
74/**
75 * Maps a device-specific key to a target (a PadState or an IndirectTarget).
76 */
77void SetKeyMapping(HostDeviceKey key, KeyTarget target);
78
79/**
80 * Clears all key mappings belonging to one device.
81 */
82void ClearKeyMapping(int device_id);
83
84/**
85 * Maps a key press action and call the corresponding function in EmuWindow
86 */
87void PressKey(EmuWindow& emu_window, HostDeviceKey key);
88
89/**
90 * Maps a key release action and call the corresponding function in EmuWindow
91 */
92void ReleaseKey(EmuWindow& emu_window, HostDeviceKey key);
93}
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index fb3acb507..b19e831fe 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -2,10 +2,14 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <algorithm>
6#include <atomic>
5#include <cmath> 7#include <cmath>
8#include <memory>
6#include "common/logging/log.h" 9#include "common/logging/log.h"
7#include "core/core_timing.h" 10#include "core/core_timing.h"
8#include "core/frontend/emu_window.h" 11#include "core/frontend/emu_window.h"
12#include "core/frontend/input.h"
9#include "core/hle/kernel/event.h" 13#include "core/hle/kernel/event.h"
10#include "core/hle/kernel/shared_memory.h" 14#include "core/hle/kernel/shared_memory.h"
11#include "core/hle/service/hid/hid.h" 15#include "core/hle/service/hid/hid.h"
@@ -44,6 +48,11 @@ constexpr u64 pad_update_ticks = BASE_CLOCK_RATE_ARM11 / 234;
44constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE_ARM11 / 104; 48constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE_ARM11 / 104;
45constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101; 49constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101;
46 50
51static std::atomic<bool> is_device_reload_pending;
52static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
53 buttons;
54static std::unique_ptr<Input::AnalogDevice> circle_pad;
55
47static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) { 56static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
48 // 30 degree and 60 degree are angular thresholds for directions 57 // 30 degree and 60 degree are angular thresholds for directions
49 constexpr float TAN30 = 0.577350269f; 58 constexpr float TAN30 = 0.577350269f;
@@ -74,14 +83,48 @@ static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
74 return state; 83 return state;
75} 84}
76 85
86static void LoadInputDevices() {
87 std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
88 Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
89 buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
90 circle_pad = Input::CreateDevice<Input::AnalogDevice>(
91 Settings::values.analogs[Settings::NativeAnalog::CirclePad]);
92}
93
94static void UnloadInputDevices() {
95 for (auto& button : buttons) {
96 button.reset();
97 }
98 circle_pad.reset();
99}
100
77static void UpdatePadCallback(u64 userdata, int cycles_late) { 101static void UpdatePadCallback(u64 userdata, int cycles_late) {
78 SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer()); 102 SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer());
79 103
80 PadState state = VideoCore::g_emu_window->GetPadState(); 104 if (is_device_reload_pending.exchange(false))
105 LoadInputDevices();
106
107 PadState state;
108 using namespace Settings::NativeButton;
109 state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
110 state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
111 state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
112 state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
113 state.right.Assign(buttons[Right - BUTTON_HID_BEGIN]->GetStatus());
114 state.left.Assign(buttons[Left - BUTTON_HID_BEGIN]->GetStatus());
115 state.up.Assign(buttons[Up - BUTTON_HID_BEGIN]->GetStatus());
116 state.down.Assign(buttons[Down - BUTTON_HID_BEGIN]->GetStatus());
117 state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
118 state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
119 state.start.Assign(buttons[Start - BUTTON_HID_BEGIN]->GetStatus());
120 state.select.Assign(buttons[Select - BUTTON_HID_BEGIN]->GetStatus());
81 121
82 // Get current circle pad position and update circle pad direction 122 // Get current circle pad position and update circle pad direction
83 s16 circle_pad_x, circle_pad_y; 123 float circle_pad_x_f, circle_pad_y_f;
84 std::tie(circle_pad_x, circle_pad_y) = VideoCore::g_emu_window->GetCirclePadState(); 124 std::tie(circle_pad_x_f, circle_pad_y_f) = circle_pad->GetStatus();
125 constexpr int MAX_CIRCLEPAD_POS = 0x9C; // Max value for a circle pad position
126 s16 circle_pad_x = static_cast<s16>(circle_pad_x_f * MAX_CIRCLEPAD_POS);
127 s16 circle_pad_y = static_cast<s16>(circle_pad_y_f * MAX_CIRCLEPAD_POS);
85 state.hex |= GetCirclePadDirectionState(circle_pad_x, circle_pad_y).hex; 128 state.hex |= GetCirclePadDirectionState(circle_pad_x, circle_pad_y).hex;
86 129
87 mem->pad.current_state.hex = state.hex; 130 mem->pad.current_state.hex = state.hex;
@@ -313,6 +356,8 @@ void Init() {
313 AddService(new HID_U_Interface); 356 AddService(new HID_U_Interface);
314 AddService(new HID_SPVR_Interface); 357 AddService(new HID_SPVR_Interface);
315 358
359 is_device_reload_pending.store(true);
360
316 using Kernel::MemoryPermission; 361 using Kernel::MemoryPermission;
317 shared_mem = 362 shared_mem =
318 SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, MemoryPermission::Read, 363 SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, MemoryPermission::Read,
@@ -350,6 +395,11 @@ void Shutdown() {
350 event_accelerometer = nullptr; 395 event_accelerometer = nullptr;
351 event_gyroscope = nullptr; 396 event_gyroscope = nullptr;
352 event_debug_pad = nullptr; 397 event_debug_pad = nullptr;
398 UnloadInputDevices();
399}
400
401void ReloadInputDevices() {
402 is_device_reload_pending.store(true);
353} 403}
354 404
355} // namespace HID 405} // namespace HID
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index c7f4ee138..b505cdcd5 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -39,13 +39,6 @@ struct PadState {
39 BitField<10, 1, u32> x; 39 BitField<10, 1, u32> x;
40 BitField<11, 1, u32> y; 40 BitField<11, 1, u32> y;
41 41
42 BitField<14, 1, u32> zl;
43 BitField<15, 1, u32> zr;
44
45 BitField<24, 1, u32> c_right;
46 BitField<25, 1, u32> c_left;
47 BitField<26, 1, u32> c_up;
48 BitField<27, 1, u32> c_down;
49 BitField<28, 1, u32> circle_right; 42 BitField<28, 1, u32> circle_right;
50 BitField<29, 1, u32> circle_left; 43 BitField<29, 1, u32> circle_left;
51 BitField<30, 1, u32> circle_up; 44 BitField<30, 1, u32> circle_up;
@@ -183,33 +176,6 @@ ASSERT_REG_POSITION(touch.index_reset_ticks, 0x2A);
183#undef ASSERT_REG_POSITION 176#undef ASSERT_REG_POSITION
184#endif // !defined(_MSC_VER) 177#endif // !defined(_MSC_VER)
185 178
186// Pre-defined PadStates for single button presses
187const PadState PAD_NONE = {{0}};
188const PadState PAD_A = {{1u << 0}};
189const PadState PAD_B = {{1u << 1}};
190const PadState PAD_SELECT = {{1u << 2}};
191const PadState PAD_START = {{1u << 3}};
192const PadState PAD_RIGHT = {{1u << 4}};
193const PadState PAD_LEFT = {{1u << 5}};
194const PadState PAD_UP = {{1u << 6}};
195const PadState PAD_DOWN = {{1u << 7}};
196const PadState PAD_R = {{1u << 8}};
197const PadState PAD_L = {{1u << 9}};
198const PadState PAD_X = {{1u << 10}};
199const PadState PAD_Y = {{1u << 11}};
200
201const PadState PAD_ZL = {{1u << 14}};
202const PadState PAD_ZR = {{1u << 15}};
203
204const PadState PAD_C_RIGHT = {{1u << 24}};
205const PadState PAD_C_LEFT = {{1u << 25}};
206const PadState PAD_C_UP = {{1u << 26}};
207const PadState PAD_C_DOWN = {{1u << 27}};
208const PadState PAD_CIRCLE_RIGHT = {{1u << 28}};
209const PadState PAD_CIRCLE_LEFT = {{1u << 29}};
210const PadState PAD_CIRCLE_UP = {{1u << 30}};
211const PadState PAD_CIRCLE_DOWN = {{1u << 31}};
212
213/** 179/**
214 * HID::GetIPCHandles service function 180 * HID::GetIPCHandles service function
215 * Inputs: 181 * Inputs:
@@ -297,5 +263,8 @@ void Init();
297 263
298/// Shutdown HID service 264/// Shutdown HID service
299void Shutdown(); 265void Shutdown();
266
267/// Reload input devices. Used when input configuration changed
268void ReloadInputDevices();
300} 269}
301} 270}
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index 3a32b70aa..a598f9f2f 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -4,6 +4,7 @@
4 4
5#include "audio_core/audio_core.h" 5#include "audio_core/audio_core.h"
6#include "core/gdbstub/gdbstub.h" 6#include "core/gdbstub/gdbstub.h"
7#include "core/hle/service/hid/hid.h"
7#include "settings.h" 8#include "settings.h"
8#include "video_core/video_core.h" 9#include "video_core/video_core.h"
9 10
@@ -29,6 +30,8 @@ void Apply() {
29 30
30 AudioCore::SelectSink(values.sink_id); 31 AudioCore::SelectSink(values.sink_id);
31 AudioCore::EnableStretching(values.enable_audio_stretching); 32 AudioCore::EnableStretching(values.enable_audio_stretching);
33
34 Service::HID::ReloadInputDevices();
32} 35}
33 36
34} // namespace 37} // namespace
diff --git a/src/core/settings.h b/src/core/settings.h
index b6c75531f..d1a9f0da8 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -18,64 +18,68 @@ enum class LayoutOption {
18 Custom, 18 Custom,
19}; 19};
20 20
21namespace NativeInput { 21namespace NativeButton {
22
23enum Values { 22enum Values {
24 // directly mapped keys
25 A, 23 A,
26 B, 24 B,
27 X, 25 X,
28 Y, 26 Y,
27 Up,
28 Down,
29 Left,
30 Right,
29 L, 31 L,
30 R, 32 R,
33 Start,
34 Select,
35
31 ZL, 36 ZL,
32 ZR, 37 ZR,
33 START, 38
34 SELECT, 39 Home,
35 HOME, 40
36 DUP, 41 NumButtons,
37 DDOWN,
38 DLEFT,
39 DRIGHT,
40 CUP,
41 CDOWN,
42 CLEFT,
43 CRIGHT,
44
45 // indirectly mapped keys
46 CIRCLE_UP,
47 CIRCLE_DOWN,
48 CIRCLE_LEFT,
49 CIRCLE_RIGHT,
50 CIRCLE_MODIFIER,
51
52 NUM_INPUTS
53}; 42};
54 43
55static const std::array<const char*, NUM_INPUTS> Mapping = {{ 44constexpr int BUTTON_HID_BEGIN = A;
56 // directly mapped keys 45constexpr int BUTTON_IR_BEGIN = ZL;
57 "pad_a", "pad_b", "pad_x", "pad_y", "pad_l", "pad_r", "pad_zl", "pad_zr", "pad_start", 46constexpr int BUTTON_NS_BEGIN = Home;
58 "pad_select", "pad_home", "pad_dup", "pad_ddown", "pad_dleft", "pad_dright", "pad_cup", 47
59 "pad_cdown", "pad_cleft", "pad_cright", 48constexpr int BUTTON_HID_END = BUTTON_IR_BEGIN;
49constexpr int BUTTON_IR_END = BUTTON_NS_BEGIN;
50constexpr int BUTTON_NS_END = NumButtons;
51
52constexpr int NUM_BUTTONS_HID = BUTTON_HID_END - BUTTON_HID_BEGIN;
53constexpr int NUM_BUTTONS_IR = BUTTON_IR_END - BUTTON_IR_BEGIN;
54constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN;
60 55
61 // indirectly mapped keys 56static const std::array<const char*, NumButtons> mapping = {{
62 "pad_circle_up", "pad_circle_down", "pad_circle_left", "pad_circle_right", 57 "button_a", "button_b", "button_x", "button_y", "button_up", "button_down", "button_left",
63 "pad_circle_modifier", 58 "button_right", "button_l", "button_r", "button_start", "button_select", "button_zl",
59 "button_zr", "button_home",
64}}; 60}};
65static const std::array<Values, NUM_INPUTS> All = {{ 61} // namespace NativeButton
66 A, B, X, Y, L, R, ZL, ZR, 62
67 START, SELECT, HOME, DUP, DDOWN, DLEFT, DRIGHT, CUP, 63namespace NativeAnalog {
68 CDOWN, CLEFT, CRIGHT, CIRCLE_UP, CIRCLE_DOWN, CIRCLE_LEFT, CIRCLE_RIGHT, CIRCLE_MODIFIER, 64enum Values {
65 CirclePad,
66 CStick,
67
68 NumAnalogs,
69};
70
71static const std::array<const char*, NumAnalogs> mapping = {{
72 "circle_pad", "c_stick",
69}}; 73}};
70} 74} // namespace NumAnalog
71 75
72struct Values { 76struct Values {
73 // CheckNew3DS 77 // CheckNew3DS
74 bool is_new_3ds; 78 bool is_new_3ds;
75 79
76 // Controls 80 // Controls
77 std::array<int, NativeInput::NUM_INPUTS> input_mappings; 81 std::array<std::string, NativeButton::NumButtons> buttons;
78 float pad_circle_modifier_scale; 82 std::array<std::string, NativeAnalog::NumAnalogs> analogs;
79 83
80 // Core 84 // Core
81 bool use_cpu_jit; 85 bool use_cpu_jit;
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
new file mode 100644
index 000000000..cfe5caaa3
--- /dev/null
+++ b/src/input_common/CMakeLists.txt
@@ -0,0 +1,27 @@
1set(SRCS
2 analog_from_button.cpp
3 keyboard.cpp
4 main.cpp
5 )
6
7set(HEADERS
8 analog_from_button.h
9 keyboard.h
10 main.h
11 )
12
13if(SDL2_FOUND)
14 set(SRCS ${SRCS} sdl/sdl.cpp)
15 set(HEADERS ${HEADERS} sdl/sdl.h)
16 include_directories(${SDL2_INCLUDE_DIR})
17endif()
18
19create_directory_groups(${SRCS} ${HEADERS})
20
21add_library(input_common STATIC ${SRCS} ${HEADERS})
22target_link_libraries(input_common common core)
23
24if(SDL2_FOUND)
25 target_link_libraries(input_common ${SDL2_LIBRARY})
26 set_property(TARGET input_common APPEND PROPERTY COMPILE_DEFINITIONS HAVE_SDL2)
27endif()
diff --git a/src/input_common/analog_from_button.cpp b/src/input_common/analog_from_button.cpp
new file mode 100755
index 000000000..e1a260762
--- /dev/null
+++ b/src/input_common/analog_from_button.cpp
@@ -0,0 +1,58 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "input_common/analog_from_button.h"
6
7namespace InputCommon {
8
9class Analog final : public Input::AnalogDevice {
10public:
11 using Button = std::unique_ptr<Input::ButtonDevice>;
12
13 Analog(Button up_, Button down_, Button left_, Button right_, Button modifier_,
14 float modifier_scale_)
15 : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)),
16 right(std::move(right_)), modifier(std::move(modifier_)),
17 modifier_scale(modifier_scale_) {}
18
19 std::tuple<float, float> GetStatus() const override {
20 constexpr float SQRT_HALF = 0.707106781f;
21 int x = 0, y = 0;
22
23 if (right->GetStatus())
24 ++x;
25 if (left->GetStatus())
26 --x;
27 if (up->GetStatus())
28 ++y;
29 if (down->GetStatus())
30 --y;
31
32 float coef = modifier->GetStatus() ? modifier_scale : 1.0f;
33 return std::make_tuple(x * coef * (y == 0 ? 1.0f : SQRT_HALF),
34 y * coef * (x == 0 ? 1.0f : SQRT_HALF));
35 }
36
37private:
38 Button up;
39 Button down;
40 Button left;
41 Button right;
42 Button modifier;
43 float modifier_scale;
44};
45
46std::unique_ptr<Input::AnalogDevice> AnalogFromButton::Create(const Common::ParamPackage& params) {
47 const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
48 auto up = Input::CreateDevice<Input::ButtonDevice>(params.Get("up", null_engine));
49 auto down = Input::CreateDevice<Input::ButtonDevice>(params.Get("down", null_engine));
50 auto left = Input::CreateDevice<Input::ButtonDevice>(params.Get("left", null_engine));
51 auto right = Input::CreateDevice<Input::ButtonDevice>(params.Get("right", null_engine));
52 auto modifier = Input::CreateDevice<Input::ButtonDevice>(params.Get("modifier", null_engine));
53 auto modifier_scale = params.Get("modifier_scale", 0.5f);
54 return std::make_unique<Analog>(std::move(up), std::move(down), std::move(left),
55 std::move(right), std::move(modifier), modifier_scale);
56}
57
58} // namespace InputCommon
diff --git a/src/input_common/analog_from_button.h b/src/input_common/analog_from_button.h
new file mode 100755
index 000000000..bbd583dd9
--- /dev/null
+++ b/src/input_common/analog_from_button.h
@@ -0,0 +1,31 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "core/frontend/input.h"
9
10namespace InputCommon {
11
12/**
13 * An analog device factory that takes direction button devices and combines them into a analog
14 * device.
15 */
16class AnalogFromButton final : public Input::Factory<Input::AnalogDevice> {
17public:
18 /**
19 * Creates an analog device from direction button devices
20 * @param params contains parameters for creating the device:
21 * - "up": a serialized ParamPackage for creating a button device for up direction
22 * - "down": a serialized ParamPackage for creating a button device for down direction
23 * - "left": a serialized ParamPackage for creating a button device for left direction
24 * - "right": a serialized ParamPackage for creating a button device for right direction
25 * - "modifier": a serialized ParamPackage for creating a button device as the modifier
26 * - "modifier_scale": a float for the multiplier the modifier gives to the position
27 */
28 std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
29};
30
31} // namespace InputCommon
diff --git a/src/input_common/keyboard.cpp b/src/input_common/keyboard.cpp
new file mode 100644
index 000000000..a8fc01f2e
--- /dev/null
+++ b/src/input_common/keyboard.cpp
@@ -0,0 +1,82 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <atomic>
6#include <list>
7#include <mutex>
8#include "input_common/keyboard.h"
9
10namespace InputCommon {
11
12class KeyButton final : public Input::ButtonDevice {
13public:
14 explicit KeyButton(std::shared_ptr<KeyButtonList> key_button_list_)
15 : key_button_list(key_button_list_) {}
16
17 ~KeyButton();
18
19 bool GetStatus() const override {
20 return status.load();
21 }
22
23 friend class KeyButtonList;
24
25private:
26 std::shared_ptr<KeyButtonList> key_button_list;
27 std::atomic<bool> status{false};
28};
29
30struct KeyButtonPair {
31 int key_code;
32 KeyButton* key_button;
33};
34
35class KeyButtonList {
36public:
37 void AddKeyButton(int key_code, KeyButton* key_button) {
38 std::lock_guard<std::mutex> guard(mutex);
39 list.push_back(KeyButtonPair{key_code, key_button});
40 }
41
42 void RemoveKeyButton(const KeyButton* key_button) {
43 std::lock_guard<std::mutex> guard(mutex);
44 list.remove_if(
45 [key_button](const KeyButtonPair& pair) { return pair.key_button == key_button; });
46 }
47
48 void ChangeKeyStatus(int key_code, bool pressed) {
49 std::lock_guard<std::mutex> guard(mutex);
50 for (const KeyButtonPair& pair : list) {
51 if (pair.key_code == key_code)
52 pair.key_button->status.store(pressed);
53 }
54 }
55
56private:
57 std::mutex mutex;
58 std::list<KeyButtonPair> list;
59};
60
61Keyboard::Keyboard() : key_button_list{std::make_shared<KeyButtonList>()} {}
62
63KeyButton::~KeyButton() {
64 key_button_list->RemoveKeyButton(this);
65}
66
67std::unique_ptr<Input::ButtonDevice> Keyboard::Create(const Common::ParamPackage& params) {
68 int key_code = params.Get("code", 0);
69 std::unique_ptr<KeyButton> button = std::make_unique<KeyButton>(key_button_list);
70 key_button_list->AddKeyButton(key_code, button.get());
71 return std::move(button);
72}
73
74void Keyboard::PressKey(int key_code) {
75 key_button_list->ChangeKeyStatus(key_code, true);
76}
77
78void Keyboard::ReleaseKey(int key_code) {
79 key_button_list->ChangeKeyStatus(key_code, false);
80}
81
82} // namespace InputCommon
diff --git a/src/input_common/keyboard.h b/src/input_common/keyboard.h
new file mode 100644
index 000000000..76359aa30
--- /dev/null
+++ b/src/input_common/keyboard.h
@@ -0,0 +1,45 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "core/frontend/input.h"
9
10namespace InputCommon {
11
12class KeyButtonList;
13
14/**
15 * A button device factory representing a keyboard. It receives keyboard events and forward them
16 * to all button devices it created.
17 */
18class Keyboard final : public Input::Factory<Input::ButtonDevice> {
19public:
20 Keyboard();
21
22 /**
23 * Creates a button device from a keyboard key
24 * @param params contains parameters for creating the device:
25 * - "code": the code of the key to bind with the button
26 */
27 std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
28
29 /**
30 * Sets the status of all buttons bound with the key to pressed
31 * @param key_code the code of the key to press
32 */
33 void PressKey(int key_code);
34
35 /**
36 * Sets the status of all buttons bound with the key to released
37 * @param key_code the code of the key to release
38 */
39 void ReleaseKey(int key_code);
40
41private:
42 std::shared_ptr<KeyButtonList> key_button_list;
43};
44
45} // namespace InputCommon
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
new file mode 100644
index 000000000..699f41e6b
--- /dev/null
+++ b/src/input_common/main.cpp
@@ -0,0 +1,63 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <memory>
6#include "common/param_package.h"
7#include "input_common/analog_from_button.h"
8#include "input_common/keyboard.h"
9#include "input_common/main.h"
10#ifdef HAVE_SDL2
11#include "input_common/sdl/sdl.h"
12#endif
13
14namespace InputCommon {
15
16static std::shared_ptr<Keyboard> keyboard;
17
18void Init() {
19 keyboard = std::make_shared<InputCommon::Keyboard>();
20 Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
21 Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
22 std::make_shared<InputCommon::AnalogFromButton>());
23#ifdef HAVE_SDL2
24 SDL::Init();
25#endif
26}
27
28void Shutdown() {
29 Input::UnregisterFactory<Input::ButtonDevice>("keyboard");
30 keyboard.reset();
31 Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
32
33#ifdef HAVE_SDL2
34 SDL::Shutdown();
35#endif
36}
37
38Keyboard* GetKeyboard() {
39 return keyboard.get();
40}
41
42std::string GenerateKeyboardParam(int key_code) {
43 Common::ParamPackage param{
44 {"engine", "keyboard"}, {"code", std::to_string(key_code)},
45 };
46 return param.Serialize();
47}
48
49std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
50 int key_modifier, float modifier_scale) {
51 Common::ParamPackage circle_pad_param{
52 {"engine", "analog_from_button"},
53 {"up", GenerateKeyboardParam(key_up)},
54 {"down", GenerateKeyboardParam(key_down)},
55 {"left", GenerateKeyboardParam(key_left)},
56 {"right", GenerateKeyboardParam(key_right)},
57 {"modifier", GenerateKeyboardParam(key_modifier)},
58 {"modifier_scale", std::to_string(modifier_scale)},
59 };
60 return circle_pad_param.Serialize();
61}
62
63} // namespace InputCommon
diff --git a/src/input_common/main.h b/src/input_common/main.h
new file mode 100644
index 000000000..140bbd014
--- /dev/null
+++ b/src/input_common/main.h
@@ -0,0 +1,29 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <string>
8
9namespace InputCommon {
10
11/// Initializes and registers all built-in input device factories.
12void Init();
13
14/// Unresisters all build-in input device factories and shut them down.
15void Shutdown();
16
17class Keyboard;
18
19/// Gets the keyboard button device factory.
20Keyboard* GetKeyboard();
21
22/// Generates a serialized param package for creating a keyboard button device
23std::string GenerateKeyboardParam(int key_code);
24
25/// Generates a serialized param package for creating an analog device taking input from keyboard
26std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
27 int key_modifier, float modifier_scale);
28
29} // namespace InputCommon
diff --git a/src/input_common/sdl/sdl.cpp b/src/input_common/sdl/sdl.cpp
new file mode 100644
index 000000000..ae0206909
--- /dev/null
+++ b/src/input_common/sdl/sdl.cpp
@@ -0,0 +1,202 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <cmath>
6#include <memory>
7#include <string>
8#include <tuple>
9#include <unordered_map>
10#include <SDL.h>
11#include "common/math_util.h"
12#include "input_common/sdl/sdl.h"
13
14namespace InputCommon {
15
16namespace SDL {
17
18class SDLJoystick;
19class SDLButtonFactory;
20class SDLAnalogFactory;
21static std::unordered_map<int, std::weak_ptr<SDLJoystick>> joystick_list;
22static std::shared_ptr<SDLButtonFactory> button_factory;
23static std::shared_ptr<SDLAnalogFactory> analog_factory;
24
25static bool initialized = false;
26
27class SDLJoystick {
28public:
29 explicit SDLJoystick(int joystick_index)
30 : joystick{SDL_JoystickOpen(joystick_index), SDL_JoystickClose} {
31 if (!joystick) {
32 LOG_ERROR(Input, "failed to open joystick %d", joystick_index);
33 }
34 }
35
36 bool GetButton(int button) const {
37 if (!joystick)
38 return {};
39 SDL_JoystickUpdate();
40 return SDL_JoystickGetButton(joystick.get(), button) == 1;
41 }
42
43 std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const {
44 if (!joystick)
45 return {};
46 SDL_JoystickUpdate();
47 float x = SDL_JoystickGetAxis(joystick.get(), axis_x) / 32767.0f;
48 float y = SDL_JoystickGetAxis(joystick.get(), axis_y) / 32767.0f;
49 y = -y; // 3DS uses an y-axis inverse from SDL
50
51 // Make sure the coordinates are in the unit circle,
52 // otherwise normalize it.
53 float r = x * x + y * y;
54 if (r > 1.0f) {
55 r = std::sqrt(r);
56 x /= r;
57 y /= r;
58 }
59
60 return std::make_tuple(x, y);
61 }
62
63 bool GetHatDirection(int hat, Uint8 direction) const {
64 return (SDL_JoystickGetHat(joystick.get(), hat) & direction) != 0;
65 }
66
67private:
68 std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> joystick;
69};
70
71class SDLButton final : public Input::ButtonDevice {
72public:
73 explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_)
74 : joystick(joystick_), button(button_) {}
75
76 bool GetStatus() const override {
77 return joystick->GetButton(button);
78 }
79
80private:
81 std::shared_ptr<SDLJoystick> joystick;
82 int button;
83};
84
85class SDLDirectionButton final : public Input::ButtonDevice {
86public:
87 explicit SDLDirectionButton(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
88 : joystick(joystick_), hat(hat_), direction(direction_) {}
89
90 bool GetStatus() const override {
91 return joystick->GetHatDirection(hat, direction);
92 }
93
94private:
95 std::shared_ptr<SDLJoystick> joystick;
96 int hat;
97 Uint8 direction;
98};
99
100class SDLAnalog final : public Input::AnalogDevice {
101public:
102 SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_)
103 : joystick(joystick_), axis_x(axis_x_), axis_y(axis_y_) {}
104
105 std::tuple<float, float> GetStatus() const override {
106 return joystick->GetAnalog(axis_x, axis_y);
107 }
108
109private:
110 std::shared_ptr<SDLJoystick> joystick;
111 int axis_x;
112 int axis_y;
113};
114
115static std::shared_ptr<SDLJoystick> GetJoystick(int joystick_index) {
116 std::shared_ptr<SDLJoystick> joystick = joystick_list[joystick_index].lock();
117 if (!joystick) {
118 joystick = std::make_shared<SDLJoystick>(joystick_index);
119 joystick_list[joystick_index] = joystick;
120 }
121 return joystick;
122}
123
124/// A button device factory that creates button devices from SDL joystick
125class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> {
126public:
127 /**
128 * Creates a button device from a joystick button
129 * @param params contains parameters for creating the device:
130 * - "joystick": the index of the joystick to bind
131 * - "button"(optional): the index of the button to bind
132 * - "hat"(optional): the index of the hat to bind as direction buttons
133 * - "direction"(only used for hat): the direction name of the hat to bind. Can be "up",
134 * "down", "left" or "right"
135 */
136 std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override {
137 const int joystick_index = params.Get("joystick", 0);
138
139 if (params.Has("hat")) {
140 const int hat = params.Get("hat", 0);
141 const std::string direction_name = params.Get("direction", "");
142 Uint8 direction;
143 if (direction_name == "up") {
144 direction = SDL_HAT_UP;
145 } else if (direction_name == "down") {
146 direction = SDL_HAT_DOWN;
147 } else if (direction_name == "left") {
148 direction = SDL_HAT_LEFT;
149 } else if (direction_name == "right") {
150 direction = SDL_HAT_RIGHT;
151 } else {
152 direction = 0;
153 }
154 return std::make_unique<SDLDirectionButton>(GetJoystick(joystick_index), hat,
155 direction);
156 }
157
158 const int button = params.Get("button", 0);
159 return std::make_unique<SDLButton>(GetJoystick(joystick_index), button);
160 }
161};
162
163/// An analog device factory that creates analog devices from SDL joystick
164class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
165public:
166 /**
167 * Creates analog device from joystick axes
168 * @param params contains parameters for creating the device:
169 * - "joystick": the index of the joystick to bind
170 * - "axis_x": the index of the axis to be bind as x-axis
171 * - "axis_y": the index of the axis to be bind as y-axis
172 */
173 std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override {
174 const int joystick_index = params.Get("joystick", 0);
175 const int axis_x = params.Get("axis_x", 0);
176 const int axis_y = params.Get("axis_y", 1);
177 return std::make_unique<SDLAnalog>(GetJoystick(joystick_index), axis_x, axis_y);
178 }
179};
180
181void Init() {
182 if (SDL_Init(SDL_INIT_JOYSTICK) < 0) {
183 LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: %s", SDL_GetError());
184 } else {
185 using namespace Input;
186 RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>());
187 RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>());
188 initialized = true;
189 }
190}
191
192void Shutdown() {
193 if (initialized) {
194 using namespace Input;
195 UnregisterFactory<ButtonDevice>("sdl");
196 UnregisterFactory<AnalogDevice>("sdl");
197 SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
198 }
199}
200
201} // namespace SDL
202} // namespace InputCommon
diff --git a/src/input_common/sdl/sdl.h b/src/input_common/sdl/sdl.h
new file mode 100644
index 000000000..3e72debcc
--- /dev/null
+++ b/src/input_common/sdl/sdl.h
@@ -0,0 +1,19 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "core/frontend/input.h"
8
9namespace InputCommon {
10namespace SDL {
11
12/// Initializes and registers SDL device factories
13void Init();
14
15/// Unresisters SDL device factories and shut them down.
16void Shutdown();
17
18} // namespace SDL
19} // namespace InputCommon
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index b47156ca4..d1144ba77 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -1,6 +1,7 @@
1set(SRCS 1set(SRCS
2 glad.cpp 2 glad.cpp
3 tests.cpp 3 tests.cpp
4 common/param_package.cpp
4 core/file_sys/path_parser.cpp 5 core/file_sys/path_parser.cpp
5 ) 6 )
6 7
diff --git a/src/tests/common/param_package.cpp b/src/tests/common/param_package.cpp
new file mode 100644
index 000000000..efec2cc86
--- /dev/null
+++ b/src/tests/common/param_package.cpp
@@ -0,0 +1,25 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <catch.hpp>
6#include <math.h>
7#include "common/param_package.h"
8
9namespace Common {
10
11TEST_CASE("ParamPackage", "[common]") {
12 ParamPackage original{
13 {"abc", "xyz"}, {"def", "42"}, {"jkl", "$$:1:$2$,3"},
14 };
15 original.Set("ghi", 3.14f);
16 ParamPackage copy(original.Serialize());
17 REQUIRE(copy.Get("abc", "") == "xyz");
18 REQUIRE(copy.Get("def", 0) == 42);
19 REQUIRE(std::abs(copy.Get("ghi", 0.0f) - 3.14f) < 0.01f);
20 REQUIRE(copy.Get("jkl", "") == "$$:1:$2$,3");
21 REQUIRE(copy.Get("mno", "uvw") == "uvw");
22 REQUIRE(copy.Get("abc", 42) == 42);
23}
24
25} // namespace Common
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index 0818a87b3..456443e86 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -17,6 +17,7 @@
17#include "common/vector_math.h" 17#include "common/vector_math.h"
18#include "core/frontend/emu_window.h" 18#include "core/frontend/emu_window.h"
19#include "core/memory.h" 19#include "core/memory.h"
20#include "core/settings.h"
20#include "video_core/pica_state.h" 21#include "video_core/pica_state.h"
21#include "video_core/renderer_opengl/gl_rasterizer_cache.h" 22#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
22#include "video_core/renderer_opengl/gl_state.h" 23#include "video_core/renderer_opengl/gl_state.h"