summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
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)
add_subdirectory(core)
add_subdirectory(video_core)
add_subdirectory(audio_core)
+add_subdirectory(input_common)
add_subdirectory(tests)
if (ENABLE_SDL2)
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})
include_directories(${SDL2_INCLUDE_DIR})
add_executable(citra ${SRCS} ${HEADERS})
-target_link_libraries(citra core video_core audio_core common)
+target_link_libraries(citra core video_core audio_core common input_common)
target_link_libraries(citra ${SDL2_LIBRARY} ${OPENGL_gl_LIBRARY} inih glad)
if (MSVC)
target_link_libraries(citra getopt)
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 @@
#include "citra/default_ini.h"
#include "common/file_util.h"
#include "common/logging/log.h"
+#include "common/param_package.h"
#include "config.h"
#include "core/settings.h"
+#include "input_common/main.h"
Config::Config() {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
@@ -37,25 +39,40 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) {
return true;
}
-static const std::array<int, Settings::NativeInput::NUM_INPUTS> defaults = {
- // directly mapped keys
- SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_Q, SDL_SCANCODE_W,
- SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_M, SDL_SCANCODE_N, SDL_SCANCODE_B, SDL_SCANCODE_T,
- SDL_SCANCODE_G, SDL_SCANCODE_F, SDL_SCANCODE_H, SDL_SCANCODE_I, SDL_SCANCODE_K, SDL_SCANCODE_J,
- SDL_SCANCODE_L,
-
- // indirectly mapped keys
- SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_D,
+static const std::array<int, Settings::NativeButton::NumButtons> default_buttons = {
+ SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_T,
+ SDL_SCANCODE_G, SDL_SCANCODE_F, SDL_SCANCODE_H, SDL_SCANCODE_Q, SDL_SCANCODE_W,
+ SDL_SCANCODE_M, SDL_SCANCODE_N, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_B,
};
+static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs{{
+ {
+ SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_D,
+ },
+ {
+ SDL_SCANCODE_I, SDL_SCANCODE_K, SDL_SCANCODE_J, SDL_SCANCODE_L, SDL_SCANCODE_D,
+ },
+}};
+
void Config::ReadValues() {
// Controls
- for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
- Settings::values.input_mappings[Settings::NativeInput::All[i]] =
- sdl2_config->GetInteger("Controls", Settings::NativeInput::Mapping[i], defaults[i]);
+ for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+ std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+ Settings::values.buttons[i] =
+ sdl2_config->Get("Controls", Settings::NativeButton::mapping[i], default_param);
+ if (Settings::values.buttons[i].empty())
+ Settings::values.buttons[i] = default_param;
+ }
+
+ for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+ std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+ default_analogs[i][3], default_analogs[i][4], 0.5f);
+ Settings::values.analogs[i] =
+ sdl2_config->Get("Controls", Settings::NativeAnalog::mapping[i], default_param);
+ if (Settings::values.analogs[i].empty())
+ Settings::values.analogs[i] = default_param;
}
- Settings::values.pad_circle_modifier_scale =
- (float)sdl2_config->GetReal("Controls", "pad_circle_modifier_scale", 0.5);
// Core
Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
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 {
const char* sdl2_config_file = R"(
[Controls]
-pad_start =
-pad_select =
-pad_home =
-pad_dup =
-pad_ddown =
-pad_dleft =
-pad_dright =
-pad_a =
-pad_b =
-pad_x =
-pad_y =
-pad_l =
-pad_r =
-pad_zl =
-pad_zr =
-pad_cup =
-pad_cdown =
-pad_cleft =
-pad_cright =
-pad_circle_up =
-pad_circle_down =
-pad_circle_left =
-pad_circle_right =
-pad_circle_modifier =
-
-# The applied modifier scale to circle pad.
-# Must be in range of 0.0-1.0. Defaults to 0.5
-pad_circle_modifier_scale =
+# The input devices and parameters for each 3DS native input
+# It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..."
+# Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values
+
+# for button input, the following devices are avaible:
+# - "keyboard" (default) for keyboard input. Required parameters:
+# - "code": the code of the key to bind
+# - "sdl" for joystick input using SDL. Required parameters:
+# - "joystick": the index of the joystick to bind
+# - "button"(optional): the index of the button to bind
+# - "hat"(optional): the index of the hat to bind as direction buttons
+# - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right"
+button_a=
+button_b=
+button_x=
+button_y=
+button_up=
+button_down=
+button_left=
+button_right=
+button_l=
+button_r=
+button_start=
+button_select=
+button_zl=
+button_zr=
+button_home=
+
+# for analog input, the following devices are avaible:
+# - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters:
+# - "up", "down", "left", "right": sub-devices for each direction.
+# Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00"
+# - "modifier": sub-devices as a modifier.
+# - "modifier_scale": a float number representing the applied modifier scale to the analog input.
+# Must be in range of 0.0-1.0. Defaults to 0.5
+# - "sdl" for joystick input using SDL. Required parameters:
+# - "joystick": the index of the joystick to bind
+# - "axis_x": the index of the axis to bind as x-axis (default to 0)
+# - "axis_y": the index of the axis to bind as y-axis (default to 1)
+circle_pad=
+c_stick=
[Core]
# Whether to use the Just-In-Time (JIT) compiler for CPU emulation
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 @@
#include "common/logging/log.h"
#include "common/scm_rev.h"
#include "common/string_util.h"
-#include "core/frontend/key_map.h"
-#include "core/hle/service/hid/hid.h"
#include "core/settings.h"
+#include "input_common/keyboard.h"
+#include "input_common/main.h"
#include "video_core/video_core.h"
void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
@@ -40,9 +40,9 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) {
if (state == SDL_PRESSED) {
- KeyMap::PressKey(*this, {key, keyboard_id});
+ InputCommon::GetKeyboard()->PressKey(key);
} else if (state == SDL_RELEASED) {
- KeyMap::ReleaseKey(*this, {key, keyboard_id});
+ InputCommon::GetKeyboard()->ReleaseKey(key);
}
}
@@ -57,9 +57,8 @@ void EmuWindow_SDL2::OnResize() {
}
EmuWindow_SDL2::EmuWindow_SDL2() {
- keyboard_id = KeyMap::NewDeviceId();
+ InputCommon::Init();
- ReloadSetKeymaps();
motion_emu = std::make_unique<Motion::MotionEmu>(*this);
SDL_SetMainReady();
@@ -117,6 +116,7 @@ EmuWindow_SDL2::~EmuWindow_SDL2() {
SDL_GL_DeleteContext(gl_context);
SDL_Quit();
motion_emu = nullptr;
+ InputCommon::Shutdown();
}
void EmuWindow_SDL2::SwapBuffers() {
@@ -169,15 +169,6 @@ void EmuWindow_SDL2::DoneCurrent() {
SDL_GL_MakeCurrent(render_window, nullptr);
}
-void EmuWindow_SDL2::ReloadSetKeymaps() {
- KeyMap::ClearKeyMapping(keyboard_id);
- for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
- KeyMap::SetKeyMapping(
- {Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id},
- KeyMap::mapping_targets[i]);
- }
-}
-
void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(
const std::pair<unsigned, unsigned>& minimal_size) {
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:
/// Whether the window is still open, and a close request hasn't yet been sent
bool IsOpen() const;
- /// Load keymap from configuration
- void ReloadSetKeymaps() override;
-
private:
/// Called by PollEvents when a key is pressed or released.
void OnKeyEvent(int key, u8 state);
@@ -61,9 +58,6 @@ private:
/// The OpenGL context associated with the window
SDL_GLContext gl_context;
- /// Device id of keyboard for use with KeyMap
- int keyboard_id;
-
/// Motion sensors emulation
std::unique_ptr<Motion::MotionEmu> motion_emu;
};
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)
else()
add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS})
endif()
-target_link_libraries(citra-qt core video_core audio_core common)
+target_link_libraries(citra-qt core video_core audio_core common input_common)
target_link_libraries(citra-qt ${OPENGL_gl_LIBRARY} ${CITRA_QT_LIBS})
target_link_libraries(citra-qt ${PLATFORM_LIBRARIES} Threads::Threads)
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 @@
#include "common/scm_rev.h"
#include "common/string_util.h"
#include "core/core.h"
-#include "core/frontend/key_map.h"
+#include "core/settings.h"
+#include "input_common/keyboard.h"
+#include "input_common/main.h"
#include "video_core/debug_utils/debug_utils.h"
#include "video_core/video_core.h"
@@ -99,14 +101,17 @@ private:
};
GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
- : QWidget(parent), child(nullptr), keyboard_id(0), emu_thread(emu_thread) {
+ : QWidget(parent), child(nullptr), emu_thread(emu_thread) {
std::string window_title = Common::StringFromFormat("Citra %s| %s-%s", Common::g_build_name,
Common::g_scm_branch, Common::g_scm_desc);
setWindowTitle(QString::fromStdString(window_title));
- keyboard_id = KeyMap::NewDeviceId();
- ReloadSetKeymaps();
+ InputCommon::Init();
+}
+
+GRenderWindow::~GRenderWindow() {
+ InputCommon::Shutdown();
}
void GRenderWindow::moveContext() {
@@ -197,11 +202,11 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
}
void GRenderWindow::keyPressEvent(QKeyEvent* event) {
- KeyMap::PressKey(*this, {event->key(), keyboard_id});
+ InputCommon::GetKeyboard()->PressKey(event->key());
}
void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
- KeyMap::ReleaseKey(*this, {event->key(), keyboard_id});
+ InputCommon::GetKeyboard()->ReleaseKey(event->key());
}
void GRenderWindow::mousePressEvent(QMouseEvent* event) {
@@ -230,14 +235,7 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
motion_emu->EndTilt();
}
-void GRenderWindow::ReloadSetKeymaps() {
- KeyMap::ClearKeyMapping(keyboard_id);
- for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
- KeyMap::SetKeyMapping(
- {Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id},
- KeyMap::mapping_targets[i]);
- }
-}
+void GRenderWindow::ReloadSetKeymaps() {}
void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) {
NotifyClientAreaSizeChanged(std::make_pair(width, height));
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 {
public:
GRenderWindow(QWidget* parent, EmuThread* emu_thread);
+ ~GRenderWindow();
// EmuWindow implementation
void SwapBuffers() override;
@@ -127,7 +128,7 @@ public:
void mouseMoveEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
- void ReloadSetKeymaps() override;
+ void ReloadSetKeymaps();
void OnClientAreaResized(unsigned width, unsigned height);
@@ -152,9 +153,6 @@ private:
QByteArray geometry;
- /// Device id of keyboard for use with KeyMap
- int keyboard_id;
-
EmuThread* emu_thread;
/// Motion sensors emulation
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 @@
#include "citra_qt/config.h"
#include "citra_qt/ui_settings.h"
#include "common/file_util.h"
+#include "input_common/main.h"
Config::Config() {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
@@ -16,25 +17,46 @@ Config::Config() {
Reload();
}
-const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> Config::defaults = {
- // directly mapped keys
- Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_Q, Qt::Key_W, Qt::Key_1, Qt::Key_2,
- Qt::Key_M, Qt::Key_N, Qt::Key_B, Qt::Key_T, Qt::Key_G, Qt::Key_F, Qt::Key_H, Qt::Key_I,
- Qt::Key_K, Qt::Key_J, Qt::Key_L,
-
- // indirectly mapped keys
- Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right, Qt::Key_D,
+const std::array<int, Settings::NativeButton::NumButtons> Config::default_buttons = {
+ Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_T, Qt::Key_G, Qt::Key_F, Qt::Key_H,
+ Qt::Key_Q, Qt::Key_W, Qt::Key_M, Qt::Key_N, Qt::Key_1, Qt::Key_2, Qt::Key_B,
};
+const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{
+ {
+ Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right, Qt::Key_D,
+ },
+ {
+ Qt::Key_I, Qt::Key_K, Qt::Key_J, Qt::Key_L, Qt::Key_D,
+ },
+}};
+
void Config::ReadValues() {
qt_config->beginGroup("Controls");
- for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
- Settings::values.input_mappings[Settings::NativeInput::All[i]] =
- qt_config->value(QString::fromStdString(Settings::NativeInput::Mapping[i]), defaults[i])
- .toInt();
+ for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+ std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+ Settings::values.buttons[i] =
+ qt_config
+ ->value(Settings::NativeButton::mapping[i], QString::fromStdString(default_param))
+ .toString()
+ .toStdString();
+ if (Settings::values.buttons[i].empty())
+ Settings::values.buttons[i] = default_param;
+ }
+
+ for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+ std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+ default_analogs[i][3], default_analogs[i][4], 0.5f);
+ Settings::values.analogs[i] =
+ qt_config
+ ->value(Settings::NativeAnalog::mapping[i], QString::fromStdString(default_param))
+ .toString()
+ .toStdString();
+ if (Settings::values.analogs[i].empty())
+ Settings::values.analogs[i] = default_param;
}
- Settings::values.pad_circle_modifier_scale =
- qt_config->value("pad_circle_modifier_scale", 0.5).toFloat();
+
qt_config->endGroup();
qt_config->beginGroup("Core");
@@ -155,12 +177,14 @@ void Config::ReadValues() {
void Config::SaveValues() {
qt_config->beginGroup("Controls");
- for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
- qt_config->setValue(QString::fromStdString(Settings::NativeInput::Mapping[i]),
- Settings::values.input_mappings[Settings::NativeInput::All[i]]);
+ for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+ qt_config->setValue(QString::fromStdString(Settings::NativeButton::mapping[i]),
+ QString::fromStdString(Settings::values.buttons[i]));
+ }
+ for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+ qt_config->setValue(QString::fromStdString(Settings::NativeAnalog::mapping[i]),
+ QString::fromStdString(Settings::values.analogs[i]));
}
- qt_config->setValue("pad_circle_modifier_scale",
- (double)Settings::values.pad_circle_modifier_scale);
qt_config->endGroup();
qt_config->beginGroup("Core");
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 @@
#pragma once
+#include <array>
#include <string>
#include <QVariant>
#include "core/settings.h"
@@ -23,5 +24,7 @@ public:
void Reload();
void Save();
- static const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> defaults;
+
+ static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
+ static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs;
};
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 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <memory>
#include <utility>
#include <QTimer>
#include "citra_qt/config.h"
#include "citra_qt/configure_input.h"
+#include "common/param_package.h"
+#include "input_common/main.h"
-static QString getKeyName(Qt::Key key_code) {
+const std::array<std::string, ConfigureInput::ANALOG_SUB_BUTTONS_NUM>
+ ConfigureInput::analog_sub_buttons{{
+ "up", "down", "left", "right", "modifier",
+ }};
+
+static QString getKeyName(int key_code) {
switch (key_code) {
case Qt::Key_Shift:
return QObject::tr("Shift");
@@ -23,6 +31,20 @@ static QString getKeyName(Qt::Key key_code) {
}
}
+static void SetButtonKey(int key, Common::ParamPackage& button_param) {
+ button_param = Common::ParamPackage{InputCommon::GenerateKeyboardParam(key)};
+}
+
+static void SetAnalogKey(int key, Common::ParamPackage& analog_param,
+ const std::string& button_name) {
+ if (analog_param.Get("engine", "") != "analog_from_button") {
+ analog_param = {
+ {"engine", "analog_from_button"}, {"modifier_scale", "0.5"},
+ };
+ }
+ analog_param.Set(button_name, InputCommon::GenerateKeyboardParam(key));
+}
+
ConfigureInput::ConfigureInput(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()),
timer(std::make_unique<QTimer>()) {
@@ -31,36 +53,41 @@ ConfigureInput::ConfigureInput(QWidget* parent)
setFocusPolicy(Qt::ClickFocus);
button_map = {
- {Settings::NativeInput::Values::A, ui->buttonA},
- {Settings::NativeInput::Values::B, ui->buttonB},
- {Settings::NativeInput::Values::X, ui->buttonX},
- {Settings::NativeInput::Values::Y, ui->buttonY},
- {Settings::NativeInput::Values::L, ui->buttonL},
- {Settings::NativeInput::Values::R, ui->buttonR},
- {Settings::NativeInput::Values::ZL, ui->buttonZL},
- {Settings::NativeInput::Values::ZR, ui->buttonZR},
- {Settings::NativeInput::Values::START, ui->buttonStart},
- {Settings::NativeInput::Values::SELECT, ui->buttonSelect},
- {Settings::NativeInput::Values::HOME, ui->buttonHome},
- {Settings::NativeInput::Values::DUP, ui->buttonDpadUp},
- {Settings::NativeInput::Values::DDOWN, ui->buttonDpadDown},
- {Settings::NativeInput::Values::DLEFT, ui->buttonDpadLeft},
- {Settings::NativeInput::Values::DRIGHT, ui->buttonDpadRight},
- {Settings::NativeInput::Values::CUP, ui->buttonCStickUp},
- {Settings::NativeInput::Values::CDOWN, ui->buttonCStickDown},
- {Settings::NativeInput::Values::CLEFT, ui->buttonCStickLeft},
- {Settings::NativeInput::Values::CRIGHT, ui->buttonCStickRight},
- {Settings::NativeInput::Values::CIRCLE_UP, ui->buttonCircleUp},
- {Settings::NativeInput::Values::CIRCLE_DOWN, ui->buttonCircleDown},
- {Settings::NativeInput::Values::CIRCLE_LEFT, ui->buttonCircleLeft},
- {Settings::NativeInput::Values::CIRCLE_RIGHT, ui->buttonCircleRight},
- {Settings::NativeInput::Values::CIRCLE_MODIFIER, ui->buttonCircleMod},
+ ui->buttonA, ui->buttonB, ui->buttonX, ui->buttonY, ui->buttonDpadUp,
+ ui->buttonDpadDown, ui->buttonDpadLeft, ui->buttonDpadRight, ui->buttonL, ui->buttonR,
+ ui->buttonStart, ui->buttonSelect, ui->buttonZL, ui->buttonZR, ui->buttonHome,
};
- for (const auto& entry : button_map) {
- const Settings::NativeInput::Values input_id = entry.first;
- connect(entry.second, &QPushButton::released,
- [this, input_id]() { handleClick(input_id); });
+ analog_map = {{
+ {
+ ui->buttonCircleUp, ui->buttonCircleDown, ui->buttonCircleLeft, ui->buttonCircleRight,
+ ui->buttonCircleMod,
+ },
+ {
+ ui->buttonCStickUp, ui->buttonCStickDown, ui->buttonCStickLeft, ui->buttonCStickRight,
+ nullptr,
+ },
+ }};
+
+ for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
+ if (button_map[button_id])
+ connect(button_map[button_id], &QPushButton::released, [=]() {
+ handleClick(button_map[button_id],
+ [=](int key) { SetButtonKey(key, buttons_param[button_id]); });
+ });
+ }
+
+ for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
+ for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
+ if (analog_map[analog_id][sub_button_id] != nullptr) {
+ connect(analog_map[analog_id][sub_button_id], &QPushButton::released, [=]() {
+ handleClick(analog_map[analog_id][sub_button_id], [=](int key) {
+ SetAnalogKey(key, analogs_param[analog_id],
+ analog_sub_buttons[sub_button_id]);
+ });
+ });
+ }
+ }
}
connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); });
@@ -69,50 +96,93 @@ ConfigureInput::ConfigureInput(QWidget* parent)
connect(timer.get(), &QTimer::timeout, [this]() {
releaseKeyboard();
releaseMouse();
- current_input_id = boost::none;
+ key_setter = boost::none;
updateButtonLabels();
});
this->loadConfiguration();
+
+ // TODO(wwylele): enable these when the input emulation for them is implemented
+ ui->buttonZL->setEnabled(false);
+ ui->buttonZR->setEnabled(false);
+ ui->buttonHome->setEnabled(false);
+ ui->buttonCStickUp->setEnabled(false);
+ ui->buttonCStickDown->setEnabled(false);
+ ui->buttonCStickLeft->setEnabled(false);
+ ui->buttonCStickRight->setEnabled(false);
}
void ConfigureInput::applyConfiguration() {
- for (const auto& input_id : Settings::NativeInput::All) {
- const size_t index = static_cast<size_t>(input_id);
- Settings::values.input_mappings[index] = static_cast<int>(key_map[input_id]);
- }
+ std::transform(buttons_param.begin(), buttons_param.end(), Settings::values.buttons.begin(),
+ [](const Common::ParamPackage& param) { return param.Serialize(); });
+ std::transform(analogs_param.begin(), analogs_param.end(), Settings::values.analogs.begin(),
+ [](const Common::ParamPackage& param) { return param.Serialize(); });
+
Settings::Apply();
}
void ConfigureInput::loadConfiguration() {
- for (const auto& input_id : Settings::NativeInput::All) {
- const size_t index = static_cast<size_t>(input_id);
- key_map[input_id] = static_cast<Qt::Key>(Settings::values.input_mappings[index]);
- }
+ std::transform(Settings::values.buttons.begin(), Settings::values.buttons.end(),
+ buttons_param.begin(),
+ [](const std::string& str) { return Common::ParamPackage(str); });
+ std::transform(Settings::values.analogs.begin(), Settings::values.analogs.end(),
+ analogs_param.begin(),
+ [](const std::string& str) { return Common::ParamPackage(str); });
updateButtonLabels();
}
void ConfigureInput::restoreDefaults() {
- for (const auto& input_id : Settings::NativeInput::All) {
- const size_t index = static_cast<size_t>(input_id);
- key_map[input_id] = static_cast<Qt::Key>(Config::defaults[index].toInt());
+ for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
+ SetButtonKey(Config::default_buttons[button_id], buttons_param[button_id]);
+ }
+
+ for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
+ for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
+ SetAnalogKey(Config::default_analogs[analog_id][sub_button_id],
+ analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
+ }
}
updateButtonLabels();
applyConfiguration();
}
void ConfigureInput::updateButtonLabels() {
- for (const auto& input_id : Settings::NativeInput::All) {
- button_map[input_id]->setText(getKeyName(key_map[input_id]));
+ QString non_keyboard(tr("[non-keyboard]"));
+
+ auto KeyToText = [&non_keyboard](const Common::ParamPackage& param) {
+ if (param.Get("engine", "") != "keyboard") {
+ return non_keyboard;
+ } else {
+ return getKeyName(param.Get("code", 0));
+ }
+ };
+
+ for (int button = 0; button < Settings::NativeButton::NumButtons; button++) {
+ button_map[button]->setText(KeyToText(buttons_param[button]));
+ }
+
+ for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
+ if (analogs_param[analog_id].Get("engine", "") != "analog_from_button") {
+ for (QPushButton* button : analog_map[analog_id]) {
+ if (button)
+ button->setText(non_keyboard);
+ }
+ } else {
+ for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
+ Common::ParamPackage param(
+ analogs_param[analog_id].Get(analog_sub_buttons[sub_button_id], ""));
+ if (analog_map[analog_id][sub_button_id])
+ analog_map[analog_id][sub_button_id]->setText(KeyToText(param));
+ }
+ }
}
}
-void ConfigureInput::handleClick(Settings::NativeInput::Values input_id) {
- QPushButton* button = button_map[input_id];
+void ConfigureInput::handleClick(QPushButton* button, std::function<void(int)> new_key_setter) {
button->setText(tr("[press key]"));
button->setFocus();
- current_input_id = input_id;
+ key_setter = new_key_setter;
grabKeyboard();
grabMouse();
@@ -123,23 +193,13 @@ void ConfigureInput::keyPressEvent(QKeyEvent* event) {
releaseKeyboard();
releaseMouse();
- if (!current_input_id || !event)
+ if (!key_setter || !event)
return;
if (event->key() != Qt::Key_Escape)
- setInput(*current_input_id, static_cast<Qt::Key>(event->key()));
+ (*key_setter)(event->key());
updateButtonLabels();
- current_input_id = boost::none;
+ key_setter = boost::none;
timer->stop();
}
-
-void ConfigureInput::setInput(Settings::NativeInput::Values input_id, Qt::Key key_pressed) {
- // Remove duplicates
- for (auto& pair : key_map) {
- if (pair.second == key_pressed)
- pair.second = Qt::Key_unknown;
- }
-
- key_map[input_id] = key_pressed;
-}
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 @@
#pragma once
+#include <array>
+#include <functional>
#include <memory>
+#include <string>
#include <QKeyEvent>
#include <QWidget>
#include <boost/optional.hpp>
+#include "common/param_package.h"
#include "core/settings.h"
#include "ui_configure_input.h"
@@ -31,15 +35,25 @@ public:
private:
std::unique_ptr<Ui::ConfigureInput> ui;
- /// This input is currently awaiting configuration.
- /// (i.e.: its corresponding QPushButton has been pressed.)
- boost::optional<Settings::NativeInput::Values> current_input_id;
std::unique_ptr<QTimer> timer;
- /// Each input is represented by a QPushButton.
- std::map<Settings::NativeInput::Values, QPushButton*> button_map;
- /// Each input is configured to respond to the press of a Qt::Key.
- std::map<Settings::NativeInput::Values, Qt::Key> key_map;
+ /// This will be the the setting function when an input is awaiting configuration.
+ boost::optional<std::function<void(int)>> key_setter;
+
+ std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param;
+ std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param;
+
+ static constexpr int ANALOG_SUB_BUTTONS_NUM = 5;
+
+ /// Each button input is represented by a QPushButton.
+ std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map;
+
+ /// Each analog input is represented by five QPushButtons which represents up, down, left, right
+ /// and modifier
+ std::array<std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM>, Settings::NativeAnalog::NumAnalogs>
+ analog_map;
+
+ static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons;
/// Load configuration settings.
void loadConfiguration();
@@ -48,10 +62,8 @@ private:
/// Update UI to reflect current configuration.
void updateButtonLabels();
- /// Called when the button corresponding to input_id was pressed.
- void handleClick(Settings::NativeInput::Values input_id);
+ /// Called when the button was pressed.
+ void handleClick(QPushButton* button, std::function<void(int)> new_key_setter);
/// Handle key press events.
void keyPressEvent(QKeyEvent* event) override;
- /// Configure input input_id to respond to key key_pressed.
- void setInput(Settings::NativeInput::Values input_id, Qt::Key key_pressed);
};
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
memory_util.cpp
microprofile.cpp
misc.cpp
+ param_package.cpp
scm_rev.cpp
string_util.cpp
symbols.cpp
@@ -66,6 +67,7 @@ set(HEADERS
memory_util.h
microprofile.h
microprofileui.h
+ param_package.h
platform.h
quaternion.h
scm_rev.h
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 {
CLS(Audio) \
SUB(Audio, DSP) \
SUB(Audio, Sink) \
+ CLS(Input) \
CLS(Loader)
// 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 {
Audio_DSP, ///< The HLE implementation of the DSP
Audio_Sink, ///< Emulator audio output backend
Loader, ///< ROM loader
+ Input, ///< Input emulation
Count ///< Total number of logging classes
};
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 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <vector>
+#include "common/logging/log.h"
+#include "common/param_package.h"
+#include "common/string_util.h"
+
+namespace Common {
+
+constexpr char KEY_VALUE_SEPARATOR = ':';
+constexpr char PARAM_SEPARATOR = ',';
+constexpr char ESCAPE_CHARACTER = '$';
+const std::string KEY_VALUE_SEPARATOR_ESCAPE{ESCAPE_CHARACTER, '0'};
+const std::string PARAM_SEPARATOR_ESCAPE{ESCAPE_CHARACTER, '1'};
+const std::string ESCAPE_CHARACTER_ESCAPE{ESCAPE_CHARACTER, '2'};
+
+ParamPackage::ParamPackage(const std::string& serialized) {
+ std::vector<std::string> pairs;
+ Common::SplitString(serialized, PARAM_SEPARATOR, pairs);
+
+ for (const std::string& pair : pairs) {
+ std::vector<std::string> key_value;
+ Common::SplitString(pair, KEY_VALUE_SEPARATOR, key_value);
+ if (key_value.size() != 2) {
+ LOG_ERROR(Common, "invalid key pair %s", pair.c_str());
+ continue;
+ }
+
+ for (std::string& part : key_value) {
+ part = Common::ReplaceAll(part, KEY_VALUE_SEPARATOR_ESCAPE, {KEY_VALUE_SEPARATOR});
+ part = Common::ReplaceAll(part, PARAM_SEPARATOR_ESCAPE, {PARAM_SEPARATOR});
+ part = Common::ReplaceAll(part, ESCAPE_CHARACTER_ESCAPE, {ESCAPE_CHARACTER});
+ }
+
+ Set(key_value[0], key_value[1]);
+ }
+}
+
+ParamPackage::ParamPackage(std::initializer_list<DataType::value_type> list) : data(list) {}
+
+std::string ParamPackage::Serialize() const {
+ if (data.empty())
+ return "";
+
+ std::string result;
+
+ for (const auto& pair : data) {
+ std::array<std::string, 2> key_value{{pair.first, pair.second}};
+ for (std::string& part : key_value) {
+ part = Common::ReplaceAll(part, {ESCAPE_CHARACTER}, ESCAPE_CHARACTER_ESCAPE);
+ part = Common::ReplaceAll(part, {PARAM_SEPARATOR}, PARAM_SEPARATOR_ESCAPE);
+ part = Common::ReplaceAll(part, {KEY_VALUE_SEPARATOR}, KEY_VALUE_SEPARATOR_ESCAPE);
+ }
+ result += key_value[0] + KEY_VALUE_SEPARATOR + key_value[1] + PARAM_SEPARATOR;
+ }
+
+ result.pop_back(); // discard the trailing PARAM_SEPARATOR
+ return result;
+}
+
+std::string ParamPackage::Get(const std::string& key, const std::string& default_value) const {
+ auto pair = data.find(key);
+ if (pair == data.end()) {
+ LOG_DEBUG(Common, "key %s not found", key.c_str());
+ return default_value;
+ }
+
+ return pair->second;
+}
+
+int ParamPackage::Get(const std::string& key, int default_value) const {
+ auto pair = data.find(key);
+ if (pair == data.end()) {
+ LOG_DEBUG(Common, "key %s not found", key.c_str());
+ return default_value;
+ }
+
+ try {
+ return std::stoi(pair->second);
+ } catch (const std::logic_error&) {
+ LOG_ERROR(Common, "failed to convert %s to int", pair->second.c_str());
+ return default_value;
+ }
+}
+
+float ParamPackage::Get(const std::string& key, float default_value) const {
+ auto pair = data.find(key);
+ if (pair == data.end()) {
+ LOG_DEBUG(Common, "key %s not found", key.c_str());
+ return default_value;
+ }
+
+ try {
+ return std::stof(pair->second);
+ } catch (const std::logic_error&) {
+ LOG_ERROR(Common, "failed to convert %s to float", pair->second.c_str());
+ return default_value;
+ }
+}
+
+void ParamPackage::Set(const std::string& key, const std::string& value) {
+ data[key] = value;
+}
+
+void ParamPackage::Set(const std::string& key, int value) {
+ data[key] = std::to_string(value);
+}
+
+void ParamPackage::Set(const std::string& key, float value) {
+ data[key] = std::to_string(value);
+}
+
+bool ParamPackage::Has(const std::string& key) const {
+ return data.find(key) != data.end();
+}
+
+} // namespace Common
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 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <initializer_list>
+#include <string>
+#include <unordered_map>
+
+namespace Common {
+
+/// A string-based key-value container supporting serializing to and deserializing from a string
+class ParamPackage {
+public:
+ using DataType = std::unordered_map<std::string, std::string>;
+
+ ParamPackage() = default;
+ explicit ParamPackage(const std::string& serialized);
+ ParamPackage(std::initializer_list<DataType::value_type> list);
+ ParamPackage(const ParamPackage& other) = default;
+ ParamPackage(ParamPackage&& other) = default;
+
+ ParamPackage& operator=(const ParamPackage& other) = default;
+ ParamPackage& operator=(ParamPackage&& other) = default;
+
+ std::string Serialize() const;
+ std::string Get(const std::string& key, const std::string& default_value) const;
+ int Get(const std::string& key, int default_value) const;
+ float Get(const std::string& key, float default_value) const;
+ void Set(const std::string& key, const std::string& value);
+ void Set(const std::string& key, int value);
+ void Set(const std::string& key, float value);
+ bool Has(const std::string& key) const;
+
+private:
+ DataType data;
+};
+
+} // namespace Common
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
frontend/camera/factory.cpp
frontend/camera/interface.cpp
frontend/emu_window.cpp
- frontend/key_map.cpp
frontend/motion_emu.cpp
gdbstub/gdbstub.cpp
hle/config_mem.cpp
@@ -218,7 +217,7 @@ set(HEADERS
frontend/camera/factory.h
frontend/camera/interface.h
frontend/emu_window.h
- frontend/key_map.h
+ frontend/input.h
frontend/motion_emu.h
gdbstub/gdbstub.h
hle/config_mem.h
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 @@
#include "common/assert.h"
#include "core/core.h"
#include "core/frontend/emu_window.h"
-#include "core/frontend/key_map.h"
+#include "core/settings.h"
#include "video_core/video_core.h"
-void EmuWindow::ButtonPressed(Service::HID::PadState pad) {
- pad_state.hex |= pad.hex;
-}
-
-void EmuWindow::ButtonReleased(Service::HID::PadState pad) {
- pad_state.hex &= ~pad.hex;
-}
-
-void EmuWindow::CirclePadUpdated(float x, float y) {
- constexpr int MAX_CIRCLEPAD_POS = 0x9C; // Max value for a circle pad position
-
- // Make sure the coordinates are in the unit circle,
- // otherwise normalize it.
- float r = x * x + y * y;
- if (r > 1) {
- r = std::sqrt(r);
- x /= r;
- y /= r;
- }
-
- circle_pad_x = static_cast<s16>(x * MAX_CIRCLEPAD_POS);
- circle_pad_y = static_cast<s16>(y * MAX_CIRCLEPAD_POS);
-}
-
/**
* Check if the given x/y coordinates are within the touchpad specified by the framebuffer layout
* @param layout FramebufferLayout object describing the framebuffer size and screen positions
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 @@
#include "common/common_types.h"
#include "common/framebuffer_layout.h"
#include "common/math_util.h"
-#include "core/hle/service/hid/hid.h"
/**
* Abstraction class used to provide an interface between emulation code and the frontend
@@ -52,30 +51,6 @@ public:
/// Releases (dunno if this is the "right" word) the GLFW context from the caller thread
virtual void DoneCurrent() = 0;
- virtual void ReloadSetKeymaps() = 0;
-
- /**
- * Signals a button press action to the HID module.
- * @param pad_state indicates which button to press
- * @note only handles real buttons (A/B/X/Y/...), excluding analog inputs like the circle pad.
- */
- void ButtonPressed(Service::HID::PadState pad_state);
-
- /**
- * Signals a button release action to the HID module.
- * @param pad_state indicates which button to press
- * @note only handles real buttons (A/B/X/Y/...), excluding analog inputs like the circle pad.
- */
- void ButtonReleased(Service::HID::PadState pad_state);
-
- /**
- * Signals a circle pad change action to the HID module.
- * @param x new x-coordinate of the circle pad, in the range [-1.0, 1.0]
- * @param y new y-coordinate of the circle pad, in the range [-1.0, 1.0]
- * @note the coordinates will be normalized if the radius is larger than 1
- */
- void CirclePadUpdated(float x, float y);
-
/**
* Signal that a touch pressed event has occurred (e.g. mouse click pressed)
* @param framebuffer_x Framebuffer x-coordinate that was pressed
@@ -115,27 +90,6 @@ public:
void GyroscopeChanged(float x, float y, float z);
/**
- * Gets the current pad state (which buttons are pressed).
- * @note This should be called by the core emu thread to get a state set by the window thread.
- * @note This doesn't include analog input like circle pad direction
- * @todo Fix this function to be thread-safe.
- * @return PadState object indicating the current pad state
- */
- Service::HID::PadState GetPadState() const {
- return pad_state;
- }
-
- /**
- * Gets the current circle pad state.
- * @note This should be called by the core emu thread to get a state set by the window thread.
- * @todo Fix this function to be thread-safe.
- * @return std::tuple of (x, y), where `x` and `y` are the circle pad coordinates
- */
- std::tuple<s16, s16> GetCirclePadState() const {
- return std::make_tuple(circle_pad_x, circle_pad_y);
- }
-
- /**
* Gets the current touch screen state (touch X/Y coordinates and whether or not it is pressed).
* @note This should be called by the core emu thread to get a state set by the window thread.
* @todo Fix this function to be thread-safe.
@@ -230,11 +184,8 @@ protected:
// TODO: Find a better place to set this.
config.min_client_area_size = std::make_pair(400u, 480u);
active_config = config;
- pad_state.hex = 0;
touch_x = 0;
touch_y = 0;
- circle_pad_x = 0;
- circle_pad_y = 0;
touch_pressed = false;
accel_x = 0;
accel_y = -512;
@@ -304,9 +255,6 @@ private:
u16 touch_x; ///< Touchpad X-position in native 3DS pixel coordinates (0-320)
u16 touch_y; ///< Touchpad Y-position in native 3DS pixel coordinates (0-240)
- s16 circle_pad_x; ///< Circle pad X-position in native 3DS pixel coordinates (-156 - 156)
- s16 circle_pad_y; ///< Circle pad Y-position in native 3DS pixel coordinates (-156 - 156)
-
std::mutex accel_mutex;
s16 accel_x; ///< Accelerometer X-axis value in native 3DS units
s16 accel_y; ///< Accelerometer Y-axis value in native 3DS units
@@ -321,6 +269,4 @@ private:
* Clip the provided coordinates to be inside the touchscreen area.
*/
std::tuple<unsigned, unsigned> ClipToTouchScreen(unsigned new_x, unsigned new_y);
-
- Service::HID::PadState pad_state;
};
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 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <tuple>
+#include <unordered_map>
+#include <utility>
+#include "common/logging/log.h"
+#include "common/param_package.h"
+
+namespace Input {
+
+/// An abstract class template for an input device (a button, an analog input, etc.).
+template <typename StatusType>
+class InputDevice {
+public:
+ virtual ~InputDevice() = default;
+ virtual StatusType GetStatus() const {
+ return {};
+ }
+};
+
+/// An abstract class template for a factory that can create input devices.
+template <typename InputDeviceType>
+class Factory {
+public:
+ virtual ~Factory() = default;
+ virtual std::unique_ptr<InputDeviceType> Create(const Common::ParamPackage&) = 0;
+};
+
+namespace Impl {
+
+template <typename InputDeviceType>
+using FactoryListType = std::unordered_map<std::string, std::shared_ptr<Factory<InputDeviceType>>>;
+
+template <typename InputDeviceType>
+struct FactoryList {
+ static FactoryListType<InputDeviceType> list;
+};
+
+template <typename InputDeviceType>
+FactoryListType<InputDeviceType> FactoryList<InputDeviceType>::list;
+
+} // namespace Impl
+
+/**
+ * Registers an input device factory.
+ * @tparam InputDeviceType the type of input devices the factory can create
+ * @param name the name of the factory. Will be used to match the "engine" parameter when creating
+ * a device
+ * @param factory the factory object to register
+ */
+template <typename InputDeviceType>
+void RegisterFactory(const std::string& name, std::shared_ptr<Factory<InputDeviceType>> factory) {
+ auto pair = std::make_pair(name, std::move(factory));
+ if (!Impl::FactoryList<InputDeviceType>::list.insert(std::move(pair)).second) {
+ LOG_ERROR(Input, "Factory %s already registered", name.c_str());
+ }
+}
+
+/**
+ * Unregisters an input device factory.
+ * @tparam InputDeviceType the type of input devices the factory can create
+ * @param name the name of the factory to unregister
+ */
+template <typename InputDeviceType>
+void UnregisterFactory(const std::string& name) {
+ if (Impl::FactoryList<InputDeviceType>::list.erase(name) == 0) {
+ LOG_ERROR(Input, "Factory %s not registered", name.c_str());
+ }
+}
+
+/**
+ * Create an input device from given paramters.
+ * @tparam InputDeviceType the type of input devices to create
+ * @param params a serialized ParamPackage string contains all parameters for creating the device
+ */
+template <typename InputDeviceType>
+std::unique_ptr<InputDeviceType> CreateDevice(const std::string& params) {
+ const Common::ParamPackage package(params);
+ const std::string engine = package.Get("engine", "null");
+ const auto& factory_list = Impl::FactoryList<InputDeviceType>::list;
+ const auto pair = factory_list.find(engine);
+ if (pair == factory_list.end()) {
+ if (engine != "null") {
+ LOG_ERROR(Input, "Unknown engine name: %s", engine.c_str());
+ }
+ return std::make_unique<InputDeviceType>();
+ }
+ return pair->second->Create(package);
+}
+
+/**
+ * A button device is an input device that returns bool as status.
+ * true for pressed; false for released.
+ */
+using ButtonDevice = InputDevice<bool>;
+
+/**
+ * An analog device is an input device that returns a tuple of x and y coordinates as status. The
+ * coordinates are within the unit circle. x+ is defined as right direction, and y+ is defined as up
+ * direction
+ */
+using AnalogDevice = InputDevice<std::tuple<float, float>>;
+
+} // namespace Input
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 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <map>
-#include "core/frontend/emu_window.h"
-#include "core/frontend/key_map.h"
-
-namespace KeyMap {
-
-// TODO (wwylele): currently we treat c-stick as four direction buttons
-// and map it directly to EmuWindow::ButtonPressed.
-// It should go the analog input way like circle pad does.
-const std::array<KeyTarget, Settings::NativeInput::NUM_INPUTS> mapping_targets = {{
- Service::HID::PAD_A,
- Service::HID::PAD_B,
- Service::HID::PAD_X,
- Service::HID::PAD_Y,
- Service::HID::PAD_L,
- Service::HID::PAD_R,
- Service::HID::PAD_ZL,
- Service::HID::PAD_ZR,
- Service::HID::PAD_START,
- Service::HID::PAD_SELECT,
- Service::HID::PAD_NONE,
- Service::HID::PAD_UP,
- Service::HID::PAD_DOWN,
- Service::HID::PAD_LEFT,
- Service::HID::PAD_RIGHT,
- Service::HID::PAD_C_UP,
- Service::HID::PAD_C_DOWN,
- Service::HID::PAD_C_LEFT,
- Service::HID::PAD_C_RIGHT,
-
- IndirectTarget::CirclePadUp,
- IndirectTarget::CirclePadDown,
- IndirectTarget::CirclePadLeft,
- IndirectTarget::CirclePadRight,
- IndirectTarget::CirclePadModifier,
-}};
-
-static std::map<HostDeviceKey, KeyTarget> key_map;
-static int next_device_id = 0;
-
-static bool circle_pad_up = false;
-static bool circle_pad_down = false;
-static bool circle_pad_left = false;
-static bool circle_pad_right = false;
-static bool circle_pad_modifier = false;
-
-static void UpdateCirclePad(EmuWindow& emu_window) {
- constexpr float SQRT_HALF = 0.707106781f;
- int x = 0, y = 0;
-
- if (circle_pad_right)
- ++x;
- if (circle_pad_left)
- --x;
- if (circle_pad_up)
- ++y;
- if (circle_pad_down)
- --y;
-
- float modifier = circle_pad_modifier ? Settings::values.pad_circle_modifier_scale : 1.0f;
- emu_window.CirclePadUpdated(x * modifier * (y == 0 ? 1.0f : SQRT_HALF),
- y * modifier * (x == 0 ? 1.0f : SQRT_HALF));
-}
-
-int NewDeviceId() {
- return next_device_id++;
-}
-
-void SetKeyMapping(HostDeviceKey key, KeyTarget target) {
- key_map[key] = target;
-}
-
-void ClearKeyMapping(int device_id) {
- auto iter = key_map.begin();
- while (iter != key_map.end()) {
- if (iter->first.device_id == device_id)
- key_map.erase(iter++);
- else
- ++iter;
- }
-}
-
-void PressKey(EmuWindow& emu_window, HostDeviceKey key) {
- auto target = key_map.find(key);
- if (target == key_map.end())
- return;
-
- if (target->second.direct) {
- emu_window.ButtonPressed({{target->second.target.direct_target_hex}});
- } else {
- switch (target->second.target.indirect_target) {
- case IndirectTarget::CirclePadUp:
- circle_pad_up = true;
- UpdateCirclePad(emu_window);
- break;
- case IndirectTarget::CirclePadDown:
- circle_pad_down = true;
- UpdateCirclePad(emu_window);
- break;
- case IndirectTarget::CirclePadLeft:
- circle_pad_left = true;
- UpdateCirclePad(emu_window);
- break;
- case IndirectTarget::CirclePadRight:
- circle_pad_right = true;
- UpdateCirclePad(emu_window);
- break;
- case IndirectTarget::CirclePadModifier:
- circle_pad_modifier = true;
- UpdateCirclePad(emu_window);
- break;
- }
- }
-}
-
-void ReleaseKey(EmuWindow& emu_window, HostDeviceKey key) {
- auto target = key_map.find(key);
- if (target == key_map.end())
- return;
-
- if (target->second.direct) {
- emu_window.ButtonReleased({{target->second.target.direct_target_hex}});
- } else {
- switch (target->second.target.indirect_target) {
- case IndirectTarget::CirclePadUp:
- circle_pad_up = false;
- UpdateCirclePad(emu_window);
- break;
- case IndirectTarget::CirclePadDown:
- circle_pad_down = false;
- UpdateCirclePad(emu_window);
- break;
- case IndirectTarget::CirclePadLeft:
- circle_pad_left = false;
- UpdateCirclePad(emu_window);
- break;
- case IndirectTarget::CirclePadRight:
- circle_pad_right = false;
- UpdateCirclePad(emu_window);
- break;
- case IndirectTarget::CirclePadModifier:
- circle_pad_modifier = false;
- UpdateCirclePad(emu_window);
- break;
- }
- }
-}
-}
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 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include <tuple>
-#include "core/hle/service/hid/hid.h"
-
-class EmuWindow;
-
-namespace KeyMap {
-
-/**
- * Represents key mapping targets that are not real 3DS buttons.
- * They will be handled by KeyMap and translated to 3DS input.
- */
-enum class IndirectTarget {
- CirclePadUp,
- CirclePadDown,
- CirclePadLeft,
- CirclePadRight,
- CirclePadModifier,
-};
-
-/**
- * Represents a key mapping target. It can be a PadState that represents real 3DS buttons,
- * or an IndirectTarget.
- */
-struct KeyTarget {
- bool direct;
- union {
- u32 direct_target_hex;
- IndirectTarget indirect_target;
- } target;
-
- KeyTarget() : direct(true) {
- target.direct_target_hex = 0;
- }
-
- KeyTarget(Service::HID::PadState pad) : direct(true) {
- target.direct_target_hex = pad.hex;
- }
-
- KeyTarget(IndirectTarget i) : direct(false) {
- target.indirect_target = i;
- }
-};
-
-/**
- * Represents a key for a specific host device.
- */
-struct HostDeviceKey {
- int key_code;
- int device_id; ///< Uniquely identifies a host device
-
- bool operator<(const HostDeviceKey& other) const {
- return std::tie(key_code, device_id) < std::tie(other.key_code, other.device_id);
- }
-
- bool operator==(const HostDeviceKey& other) const {
- return std::tie(key_code, device_id) == std::tie(other.key_code, other.device_id);
- }
-};
-
-extern const std::array<KeyTarget, Settings::NativeInput::NUM_INPUTS> mapping_targets;
-
-/**
- * Generates a new device id, which uniquely identifies a host device within KeyMap.
- */
-int NewDeviceId();
-
-/**
- * Maps a device-specific key to a target (a PadState or an IndirectTarget).
- */
-void SetKeyMapping(HostDeviceKey key, KeyTarget target);
-
-/**
- * Clears all key mappings belonging to one device.
- */
-void ClearKeyMapping(int device_id);
-
-/**
- * Maps a key press action and call the corresponding function in EmuWindow
- */
-void PressKey(EmuWindow& emu_window, HostDeviceKey key);
-
-/**
- * Maps a key release action and call the corresponding function in EmuWindow
- */
-void ReleaseKey(EmuWindow& emu_window, HostDeviceKey key);
-}
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 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
+#include <atomic>
#include <cmath>
+#include <memory>
#include "common/logging/log.h"
#include "core/core_timing.h"
#include "core/frontend/emu_window.h"
+#include "core/frontend/input.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/service/hid/hid.h"
@@ -44,6 +48,11 @@ constexpr u64 pad_update_ticks = BASE_CLOCK_RATE_ARM11 / 234;
constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE_ARM11 / 104;
constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101;
+static std::atomic<bool> is_device_reload_pending;
+static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
+ buttons;
+static std::unique_ptr<Input::AnalogDevice> circle_pad;
+
static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
// 30 degree and 60 degree are angular thresholds for directions
constexpr float TAN30 = 0.577350269f;
@@ -74,14 +83,48 @@ static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
return state;
}
+static void LoadInputDevices() {
+ std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
+ Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
+ buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
+ circle_pad = Input::CreateDevice<Input::AnalogDevice>(
+ Settings::values.analogs[Settings::NativeAnalog::CirclePad]);
+}
+
+static void UnloadInputDevices() {
+ for (auto& button : buttons) {
+ button.reset();
+ }
+ circle_pad.reset();
+}
+
static void UpdatePadCallback(u64 userdata, int cycles_late) {
SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer());
- PadState state = VideoCore::g_emu_window->GetPadState();
+ if (is_device_reload_pending.exchange(false))
+ LoadInputDevices();
+
+ PadState state;
+ using namespace Settings::NativeButton;
+ state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
+ state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
+ state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
+ state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
+ state.right.Assign(buttons[Right - BUTTON_HID_BEGIN]->GetStatus());
+ state.left.Assign(buttons[Left - BUTTON_HID_BEGIN]->GetStatus());
+ state.up.Assign(buttons[Up - BUTTON_HID_BEGIN]->GetStatus());
+ state.down.Assign(buttons[Down - BUTTON_HID_BEGIN]->GetStatus());
+ state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
+ state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
+ state.start.Assign(buttons[Start - BUTTON_HID_BEGIN]->GetStatus());
+ state.select.Assign(buttons[Select - BUTTON_HID_BEGIN]->GetStatus());
// Get current circle pad position and update circle pad direction
- s16 circle_pad_x, circle_pad_y;
- std::tie(circle_pad_x, circle_pad_y) = VideoCore::g_emu_window->GetCirclePadState();
+ float circle_pad_x_f, circle_pad_y_f;
+ std::tie(circle_pad_x_f, circle_pad_y_f) = circle_pad->GetStatus();
+ constexpr int MAX_CIRCLEPAD_POS = 0x9C; // Max value for a circle pad position
+ s16 circle_pad_x = static_cast<s16>(circle_pad_x_f * MAX_CIRCLEPAD_POS);
+ s16 circle_pad_y = static_cast<s16>(circle_pad_y_f * MAX_CIRCLEPAD_POS);
state.hex |= GetCirclePadDirectionState(circle_pad_x, circle_pad_y).hex;
mem->pad.current_state.hex = state.hex;
@@ -313,6 +356,8 @@ void Init() {
AddService(new HID_U_Interface);
AddService(new HID_SPVR_Interface);
+ is_device_reload_pending.store(true);
+
using Kernel::MemoryPermission;
shared_mem =
SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, MemoryPermission::Read,
@@ -350,6 +395,11 @@ void Shutdown() {
event_accelerometer = nullptr;
event_gyroscope = nullptr;
event_debug_pad = nullptr;
+ UnloadInputDevices();
+}
+
+void ReloadInputDevices() {
+ is_device_reload_pending.store(true);
}
} // namespace HID
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 {
BitField<10, 1, u32> x;
BitField<11, 1, u32> y;
- BitField<14, 1, u32> zl;
- BitField<15, 1, u32> zr;
-
- BitField<24, 1, u32> c_right;
- BitField<25, 1, u32> c_left;
- BitField<26, 1, u32> c_up;
- BitField<27, 1, u32> c_down;
BitField<28, 1, u32> circle_right;
BitField<29, 1, u32> circle_left;
BitField<30, 1, u32> circle_up;
@@ -183,33 +176,6 @@ ASSERT_REG_POSITION(touch.index_reset_ticks, 0x2A);
#undef ASSERT_REG_POSITION
#endif // !defined(_MSC_VER)
-// Pre-defined PadStates for single button presses
-const PadState PAD_NONE = {{0}};
-const PadState PAD_A = {{1u << 0}};
-const PadState PAD_B = {{1u << 1}};
-const PadState PAD_SELECT = {{1u << 2}};
-const PadState PAD_START = {{1u << 3}};
-const PadState PAD_RIGHT = {{1u << 4}};
-const PadState PAD_LEFT = {{1u << 5}};
-const PadState PAD_UP = {{1u << 6}};
-const PadState PAD_DOWN = {{1u << 7}};
-const PadState PAD_R = {{1u << 8}};
-const PadState PAD_L = {{1u << 9}};
-const PadState PAD_X = {{1u << 10}};
-const PadState PAD_Y = {{1u << 11}};
-
-const PadState PAD_ZL = {{1u << 14}};
-const PadState PAD_ZR = {{1u << 15}};
-
-const PadState PAD_C_RIGHT = {{1u << 24}};
-const PadState PAD_C_LEFT = {{1u << 25}};
-const PadState PAD_C_UP = {{1u << 26}};
-const PadState PAD_C_DOWN = {{1u << 27}};
-const PadState PAD_CIRCLE_RIGHT = {{1u << 28}};
-const PadState PAD_CIRCLE_LEFT = {{1u << 29}};
-const PadState PAD_CIRCLE_UP = {{1u << 30}};
-const PadState PAD_CIRCLE_DOWN = {{1u << 31}};
-
/**
* HID::GetIPCHandles service function
* Inputs:
@@ -297,5 +263,8 @@ void Init();
/// Shutdown HID service
void Shutdown();
+
+/// Reload input devices. Used when input configuration changed
+void ReloadInputDevices();
}
}
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 @@
#include "audio_core/audio_core.h"
#include "core/gdbstub/gdbstub.h"
+#include "core/hle/service/hid/hid.h"
#include "settings.h"
#include "video_core/video_core.h"
@@ -29,6 +30,8 @@ void Apply() {
AudioCore::SelectSink(values.sink_id);
AudioCore::EnableStretching(values.enable_audio_stretching);
+
+ Service::HID::ReloadInputDevices();
}
} // namespace
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 {
Custom,
};
-namespace NativeInput {
-
+namespace NativeButton {
enum Values {
- // directly mapped keys
A,
B,
X,
Y,
+ Up,
+ Down,
+ Left,
+ Right,
L,
R,
+ Start,
+ Select,
+
ZL,
ZR,
- START,
- SELECT,
- HOME,
- DUP,
- DDOWN,
- DLEFT,
- DRIGHT,
- CUP,
- CDOWN,
- CLEFT,
- CRIGHT,
-
- // indirectly mapped keys
- CIRCLE_UP,
- CIRCLE_DOWN,
- CIRCLE_LEFT,
- CIRCLE_RIGHT,
- CIRCLE_MODIFIER,
-
- NUM_INPUTS
+
+ Home,
+
+ NumButtons,
};
-static const std::array<const char*, NUM_INPUTS> Mapping = {{
- // directly mapped keys
- "pad_a", "pad_b", "pad_x", "pad_y", "pad_l", "pad_r", "pad_zl", "pad_zr", "pad_start",
- "pad_select", "pad_home", "pad_dup", "pad_ddown", "pad_dleft", "pad_dright", "pad_cup",
- "pad_cdown", "pad_cleft", "pad_cright",
+constexpr int BUTTON_HID_BEGIN = A;
+constexpr int BUTTON_IR_BEGIN = ZL;
+constexpr int BUTTON_NS_BEGIN = Home;
+
+constexpr int BUTTON_HID_END = BUTTON_IR_BEGIN;
+constexpr int BUTTON_IR_END = BUTTON_NS_BEGIN;
+constexpr int BUTTON_NS_END = NumButtons;
+
+constexpr int NUM_BUTTONS_HID = BUTTON_HID_END - BUTTON_HID_BEGIN;
+constexpr int NUM_BUTTONS_IR = BUTTON_IR_END - BUTTON_IR_BEGIN;
+constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN;
- // indirectly mapped keys
- "pad_circle_up", "pad_circle_down", "pad_circle_left", "pad_circle_right",
- "pad_circle_modifier",
+static const std::array<const char*, NumButtons> mapping = {{
+ "button_a", "button_b", "button_x", "button_y", "button_up", "button_down", "button_left",
+ "button_right", "button_l", "button_r", "button_start", "button_select", "button_zl",
+ "button_zr", "button_home",
}};
-static const std::array<Values, NUM_INPUTS> All = {{
- A, B, X, Y, L, R, ZL, ZR,
- START, SELECT, HOME, DUP, DDOWN, DLEFT, DRIGHT, CUP,
- CDOWN, CLEFT, CRIGHT, CIRCLE_UP, CIRCLE_DOWN, CIRCLE_LEFT, CIRCLE_RIGHT, CIRCLE_MODIFIER,
+} // namespace NativeButton
+
+namespace NativeAnalog {
+enum Values {
+ CirclePad,
+ CStick,
+
+ NumAnalogs,
+};
+
+static const std::array<const char*, NumAnalogs> mapping = {{
+ "circle_pad", "c_stick",
}};
-}
+} // namespace NumAnalog
struct Values {
// CheckNew3DS
bool is_new_3ds;
// Controls
- std::array<int, NativeInput::NUM_INPUTS> input_mappings;
- float pad_circle_modifier_scale;
+ std::array<std::string, NativeButton::NumButtons> buttons;
+ std::array<std::string, NativeAnalog::NumAnalogs> analogs;
// Core
bool use_cpu_jit;
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 @@
+set(SRCS
+ analog_from_button.cpp
+ keyboard.cpp
+ main.cpp
+ )
+
+set(HEADERS
+ analog_from_button.h
+ keyboard.h
+ main.h
+ )
+
+if(SDL2_FOUND)
+ set(SRCS ${SRCS} sdl/sdl.cpp)
+ set(HEADERS ${HEADERS} sdl/sdl.h)
+ include_directories(${SDL2_INCLUDE_DIR})
+endif()
+
+create_directory_groups(${SRCS} ${HEADERS})
+
+add_library(input_common STATIC ${SRCS} ${HEADERS})
+target_link_libraries(input_common common core)
+
+if(SDL2_FOUND)
+ target_link_libraries(input_common ${SDL2_LIBRARY})
+ set_property(TARGET input_common APPEND PROPERTY COMPILE_DEFINITIONS HAVE_SDL2)
+endif()
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 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "input_common/analog_from_button.h"
+
+namespace InputCommon {
+
+class Analog final : public Input::AnalogDevice {
+public:
+ using Button = std::unique_ptr<Input::ButtonDevice>;
+
+ Analog(Button up_, Button down_, Button left_, Button right_, Button modifier_,
+ float modifier_scale_)
+ : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)),
+ right(std::move(right_)), modifier(std::move(modifier_)),
+ modifier_scale(modifier_scale_) {}
+
+ std::tuple<float, float> GetStatus() const override {
+ constexpr float SQRT_HALF = 0.707106781f;
+ int x = 0, y = 0;
+
+ if (right->GetStatus())
+ ++x;
+ if (left->GetStatus())
+ --x;
+ if (up->GetStatus())
+ ++y;
+ if (down->GetStatus())
+ --y;
+
+ float coef = modifier->GetStatus() ? modifier_scale : 1.0f;
+ return std::make_tuple(x * coef * (y == 0 ? 1.0f : SQRT_HALF),
+ y * coef * (x == 0 ? 1.0f : SQRT_HALF));
+ }
+
+private:
+ Button up;
+ Button down;
+ Button left;
+ Button right;
+ Button modifier;
+ float modifier_scale;
+};
+
+std::unique_ptr<Input::AnalogDevice> AnalogFromButton::Create(const Common::ParamPackage& params) {
+ const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
+ auto up = Input::CreateDevice<Input::ButtonDevice>(params.Get("up", null_engine));
+ auto down = Input::CreateDevice<Input::ButtonDevice>(params.Get("down", null_engine));
+ auto left = Input::CreateDevice<Input::ButtonDevice>(params.Get("left", null_engine));
+ auto right = Input::CreateDevice<Input::ButtonDevice>(params.Get("right", null_engine));
+ auto modifier = Input::CreateDevice<Input::ButtonDevice>(params.Get("modifier", null_engine));
+ auto modifier_scale = params.Get("modifier_scale", 0.5f);
+ return std::make_unique<Analog>(std::move(up), std::move(down), std::move(left),
+ std::move(right), std::move(modifier), modifier_scale);
+}
+
+} // namespace InputCommon
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 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include "core/frontend/input.h"
+
+namespace InputCommon {
+
+/**
+ * An analog device factory that takes direction button devices and combines them into a analog
+ * device.
+ */
+class AnalogFromButton final : public Input::Factory<Input::AnalogDevice> {
+public:
+ /**
+ * Creates an analog device from direction button devices
+ * @param params contains parameters for creating the device:
+ * - "up": a serialized ParamPackage for creating a button device for up direction
+ * - "down": a serialized ParamPackage for creating a button device for down direction
+ * - "left": a serialized ParamPackage for creating a button device for left direction
+ * - "right": a serialized ParamPackage for creating a button device for right direction
+ * - "modifier": a serialized ParamPackage for creating a button device as the modifier
+ * - "modifier_scale": a float for the multiplier the modifier gives to the position
+ */
+ std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
+};
+
+} // namespace InputCommon
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 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <atomic>
+#include <list>
+#include <mutex>
+#include "input_common/keyboard.h"
+
+namespace InputCommon {
+
+class KeyButton final : public Input::ButtonDevice {
+public:
+ explicit KeyButton(std::shared_ptr<KeyButtonList> key_button_list_)
+ : key_button_list(key_button_list_) {}
+
+ ~KeyButton();
+
+ bool GetStatus() const override {
+ return status.load();
+ }
+
+ friend class KeyButtonList;
+
+private:
+ std::shared_ptr<KeyButtonList> key_button_list;
+ std::atomic<bool> status{false};
+};
+
+struct KeyButtonPair {
+ int key_code;
+ KeyButton* key_button;
+};
+
+class KeyButtonList {
+public:
+ void AddKeyButton(int key_code, KeyButton* key_button) {
+ std::lock_guard<std::mutex> guard(mutex);
+ list.push_back(KeyButtonPair{key_code, key_button});
+ }
+
+ void RemoveKeyButton(const KeyButton* key_button) {
+ std::lock_guard<std::mutex> guard(mutex);
+ list.remove_if(
+ [key_button](const KeyButtonPair& pair) { return pair.key_button == key_button; });
+ }
+
+ void ChangeKeyStatus(int key_code, bool pressed) {
+ std::lock_guard<std::mutex> guard(mutex);
+ for (const KeyButtonPair& pair : list) {
+ if (pair.key_code == key_code)
+ pair.key_button->status.store(pressed);
+ }
+ }
+
+private:
+ std::mutex mutex;
+ std::list<KeyButtonPair> list;
+};
+
+Keyboard::Keyboard() : key_button_list{std::make_shared<KeyButtonList>()} {}
+
+KeyButton::~KeyButton() {
+ key_button_list->RemoveKeyButton(this);
+}
+
+std::unique_ptr<Input::ButtonDevice> Keyboard::Create(const Common::ParamPackage& params) {
+ int key_code = params.Get("code", 0);
+ std::unique_ptr<KeyButton> button = std::make_unique<KeyButton>(key_button_list);
+ key_button_list->AddKeyButton(key_code, button.get());
+ return std::move(button);
+}
+
+void Keyboard::PressKey(int key_code) {
+ key_button_list->ChangeKeyStatus(key_code, true);
+}
+
+void Keyboard::ReleaseKey(int key_code) {
+ key_button_list->ChangeKeyStatus(key_code, false);
+}
+
+} // namespace InputCommon
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 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include "core/frontend/input.h"
+
+namespace InputCommon {
+
+class KeyButtonList;
+
+/**
+ * A button device factory representing a keyboard. It receives keyboard events and forward them
+ * to all button devices it created.
+ */
+class Keyboard final : public Input::Factory<Input::ButtonDevice> {
+public:
+ Keyboard();
+
+ /**
+ * Creates a button device from a keyboard key
+ * @param params contains parameters for creating the device:
+ * - "code": the code of the key to bind with the button
+ */
+ std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
+
+ /**
+ * Sets the status of all buttons bound with the key to pressed
+ * @param key_code the code of the key to press
+ */
+ void PressKey(int key_code);
+
+ /**
+ * Sets the status of all buttons bound with the key to released
+ * @param key_code the code of the key to release
+ */
+ void ReleaseKey(int key_code);
+
+private:
+ std::shared_ptr<KeyButtonList> key_button_list;
+};
+
+} // namespace InputCommon
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 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+#include "common/param_package.h"
+#include "input_common/analog_from_button.h"
+#include "input_common/keyboard.h"
+#include "input_common/main.h"
+#ifdef HAVE_SDL2
+#include "input_common/sdl/sdl.h"
+#endif
+
+namespace InputCommon {
+
+static std::shared_ptr<Keyboard> keyboard;
+
+void Init() {
+ keyboard = std::make_shared<InputCommon::Keyboard>();
+ Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
+ Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
+ std::make_shared<InputCommon::AnalogFromButton>());
+#ifdef HAVE_SDL2
+ SDL::Init();
+#endif
+}
+
+void Shutdown() {
+ Input::UnregisterFactory<Input::ButtonDevice>("keyboard");
+ keyboard.reset();
+ Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
+
+#ifdef HAVE_SDL2
+ SDL::Shutdown();
+#endif
+}
+
+Keyboard* GetKeyboard() {
+ return keyboard.get();
+}
+
+std::string GenerateKeyboardParam(int key_code) {
+ Common::ParamPackage param{
+ {"engine", "keyboard"}, {"code", std::to_string(key_code)},
+ };
+ return param.Serialize();
+}
+
+std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
+ int key_modifier, float modifier_scale) {
+ Common::ParamPackage circle_pad_param{
+ {"engine", "analog_from_button"},
+ {"up", GenerateKeyboardParam(key_up)},
+ {"down", GenerateKeyboardParam(key_down)},
+ {"left", GenerateKeyboardParam(key_left)},
+ {"right", GenerateKeyboardParam(key_right)},
+ {"modifier", GenerateKeyboardParam(key_modifier)},
+ {"modifier_scale", std::to_string(modifier_scale)},
+ };
+ return circle_pad_param.Serialize();
+}
+
+} // namespace InputCommon
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 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+
+namespace InputCommon {
+
+/// Initializes and registers all built-in input device factories.
+void Init();
+
+/// Unresisters all build-in input device factories and shut them down.
+void Shutdown();
+
+class Keyboard;
+
+/// Gets the keyboard button device factory.
+Keyboard* GetKeyboard();
+
+/// Generates a serialized param package for creating a keyboard button device
+std::string GenerateKeyboardParam(int key_code);
+
+/// Generates a serialized param package for creating an analog device taking input from keyboard
+std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
+ int key_modifier, float modifier_scale);
+
+} // namespace InputCommon
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 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cmath>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <unordered_map>
+#include <SDL.h>
+#include "common/math_util.h"
+#include "input_common/sdl/sdl.h"
+
+namespace InputCommon {
+
+namespace SDL {
+
+class SDLJoystick;
+class SDLButtonFactory;
+class SDLAnalogFactory;
+static std::unordered_map<int, std::weak_ptr<SDLJoystick>> joystick_list;
+static std::shared_ptr<SDLButtonFactory> button_factory;
+static std::shared_ptr<SDLAnalogFactory> analog_factory;
+
+static bool initialized = false;
+
+class SDLJoystick {
+public:
+ explicit SDLJoystick(int joystick_index)
+ : joystick{SDL_JoystickOpen(joystick_index), SDL_JoystickClose} {
+ if (!joystick) {
+ LOG_ERROR(Input, "failed to open joystick %d", joystick_index);
+ }
+ }
+
+ bool GetButton(int button) const {
+ if (!joystick)
+ return {};
+ SDL_JoystickUpdate();
+ return SDL_JoystickGetButton(joystick.get(), button) == 1;
+ }
+
+ std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const {
+ if (!joystick)
+ return {};
+ SDL_JoystickUpdate();
+ float x = SDL_JoystickGetAxis(joystick.get(), axis_x) / 32767.0f;
+ float y = SDL_JoystickGetAxis(joystick.get(), axis_y) / 32767.0f;
+ y = -y; // 3DS uses an y-axis inverse from SDL
+
+ // Make sure the coordinates are in the unit circle,
+ // otherwise normalize it.
+ float r = x * x + y * y;
+ if (r > 1.0f) {
+ r = std::sqrt(r);
+ x /= r;
+ y /= r;
+ }
+
+ return std::make_tuple(x, y);
+ }
+
+ bool GetHatDirection(int hat, Uint8 direction) const {
+ return (SDL_JoystickGetHat(joystick.get(), hat) & direction) != 0;
+ }
+
+private:
+ std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> joystick;
+};
+
+class SDLButton final : public Input::ButtonDevice {
+public:
+ explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_)
+ : joystick(joystick_), button(button_) {}
+
+ bool GetStatus() const override {
+ return joystick->GetButton(button);
+ }
+
+private:
+ std::shared_ptr<SDLJoystick> joystick;
+ int button;
+};
+
+class SDLDirectionButton final : public Input::ButtonDevice {
+public:
+ explicit SDLDirectionButton(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
+ : joystick(joystick_), hat(hat_), direction(direction_) {}
+
+ bool GetStatus() const override {
+ return joystick->GetHatDirection(hat, direction);
+ }
+
+private:
+ std::shared_ptr<SDLJoystick> joystick;
+ int hat;
+ Uint8 direction;
+};
+
+class SDLAnalog final : public Input::AnalogDevice {
+public:
+ SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_)
+ : joystick(joystick_), axis_x(axis_x_), axis_y(axis_y_) {}
+
+ std::tuple<float, float> GetStatus() const override {
+ return joystick->GetAnalog(axis_x, axis_y);
+ }
+
+private:
+ std::shared_ptr<SDLJoystick> joystick;
+ int axis_x;
+ int axis_y;
+};
+
+static std::shared_ptr<SDLJoystick> GetJoystick(int joystick_index) {
+ std::shared_ptr<SDLJoystick> joystick = joystick_list[joystick_index].lock();
+ if (!joystick) {
+ joystick = std::make_shared<SDLJoystick>(joystick_index);
+ joystick_list[joystick_index] = joystick;
+ }
+ return joystick;
+}
+
+/// A button device factory that creates button devices from SDL joystick
+class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> {
+public:
+ /**
+ * Creates a button device from a joystick button
+ * @param params contains parameters for creating the device:
+ * - "joystick": the index of the joystick to bind
+ * - "button"(optional): the index of the button to bind
+ * - "hat"(optional): the index of the hat to bind as direction buttons
+ * - "direction"(only used for hat): the direction name of the hat to bind. Can be "up",
+ * "down", "left" or "right"
+ */
+ std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override {
+ const int joystick_index = params.Get("joystick", 0);
+
+ if (params.Has("hat")) {
+ const int hat = params.Get("hat", 0);
+ const std::string direction_name = params.Get("direction", "");
+ Uint8 direction;
+ if (direction_name == "up") {
+ direction = SDL_HAT_UP;
+ } else if (direction_name == "down") {
+ direction = SDL_HAT_DOWN;
+ } else if (direction_name == "left") {
+ direction = SDL_HAT_LEFT;
+ } else if (direction_name == "right") {
+ direction = SDL_HAT_RIGHT;
+ } else {
+ direction = 0;
+ }
+ return std::make_unique<SDLDirectionButton>(GetJoystick(joystick_index), hat,
+ direction);
+ }
+
+ const int button = params.Get("button", 0);
+ return std::make_unique<SDLButton>(GetJoystick(joystick_index), button);
+ }
+};
+
+/// An analog device factory that creates analog devices from SDL joystick
+class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
+public:
+ /**
+ * Creates analog device from joystick axes
+ * @param params contains parameters for creating the device:
+ * - "joystick": the index of the joystick to bind
+ * - "axis_x": the index of the axis to be bind as x-axis
+ * - "axis_y": the index of the axis to be bind as y-axis
+ */
+ std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override {
+ const int joystick_index = params.Get("joystick", 0);
+ const int axis_x = params.Get("axis_x", 0);
+ const int axis_y = params.Get("axis_y", 1);
+ return std::make_unique<SDLAnalog>(GetJoystick(joystick_index), axis_x, axis_y);
+ }
+};
+
+void Init() {
+ if (SDL_Init(SDL_INIT_JOYSTICK) < 0) {
+ LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: %s", SDL_GetError());
+ } else {
+ using namespace Input;
+ RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>());
+ RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>());
+ initialized = true;
+ }
+}
+
+void Shutdown() {
+ if (initialized) {
+ using namespace Input;
+ UnregisterFactory<ButtonDevice>("sdl");
+ UnregisterFactory<AnalogDevice>("sdl");
+ SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
+ }
+}
+
+} // namespace SDL
+} // namespace InputCommon
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 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/frontend/input.h"
+
+namespace InputCommon {
+namespace SDL {
+
+/// Initializes and registers SDL device factories
+void Init();
+
+/// Unresisters SDL device factories and shut them down.
+void Shutdown();
+
+} // namespace SDL
+} // namespace InputCommon
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 @@
set(SRCS
glad.cpp
tests.cpp
+ common/param_package.cpp
core/file_sys/path_parser.cpp
)
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 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <catch.hpp>
+#include <math.h>
+#include "common/param_package.h"
+
+namespace Common {
+
+TEST_CASE("ParamPackage", "[common]") {
+ ParamPackage original{
+ {"abc", "xyz"}, {"def", "42"}, {"jkl", "$$:1:$2$,3"},
+ };
+ original.Set("ghi", 3.14f);
+ ParamPackage copy(original.Serialize());
+ REQUIRE(copy.Get("abc", "") == "xyz");
+ REQUIRE(copy.Get("def", 0) == 42);
+ REQUIRE(std::abs(copy.Get("ghi", 0.0f) - 3.14f) < 0.01f);
+ REQUIRE(copy.Get("jkl", "") == "$$:1:$2$,3");
+ REQUIRE(copy.Get("mno", "uvw") == "uvw");
+ REQUIRE(copy.Get("abc", 42) == 42);
+}
+
+} // namespace Common
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 @@
#include "common/vector_math.h"
#include "core/frontend/emu_window.h"
#include "core/memory.h"
+#include "core/settings.h"
#include "video_core/pica_state.h"
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
#include "video_core/renderer_opengl/gl_state.h"