Hotkey config changes (#3391)

* This works, but it's missing some hotkeys and the GUI isn't hooked up to anything now

* More hotkeys

* Remove debug log

* clang

* accidentally used the wrong value here

* gui changes for new backend (#10)

* gui changes for new backend

* fix lmeta

* don't erase non-hotkey lines

* do not erase hotkey configs in kbm or controller guis

* Fix repeated inputs

* Documentation

---------

Co-authored-by: rainmakerv2 <30595646+rainmakerv3@users.noreply.github.com>
This commit is contained in:
kalaposfos13
2025-09-04 19:47:06 +02:00
committed by GitHub
parent e8abbd4305
commit bcbe07e6b1
16 changed files with 1330 additions and 654 deletions

View File

@@ -1077,7 +1077,22 @@ void setDefaultValues() {
m_language = 1;
}
constexpr std::string_view GetDefaultKeyboardConfig() {
constexpr std::string_view GetDefaultGlobalConfig() {
return R"(# Anything put here will be loaded for all games,
# alongside the game's config or default.ini depending on your preference.
hotkey_renderdoc_capture = f12
hotkey_fullscreen = f11
hotkey_show_fps = f10
hotkey_pause = f9
hotkey_reload_inputs = f8
hotkey_toggle_mouse_to_joystick = f7
hotkey_toggle_mouse_to_gyro = f6
hotkey_quit = lctrl, lshift, end
)";
}
constexpr std::string_view GetDefaultInputConfig() {
return R"(#Feeling lost? Check out the Help section!
# Keyboard bindings
@@ -1151,7 +1166,7 @@ analog_deadzone = rightjoystick, 2, 127
override_controller_color = false, 0, 0, 255
)";
}
std::filesystem::path GetFoolproofKbmConfigFile(const std::string& game_id) {
std::filesystem::path GetFoolproofInputConfigFile(const std::string& game_id) {
// Read configuration file of the game, and if it doesn't exist, generate it from default
// If that doesn't exist either, generate that from getDefaultConfig() and try again
// If even the folder is missing, we start with that.
@@ -1168,7 +1183,7 @@ std::filesystem::path GetFoolproofKbmConfigFile(const std::string& game_id) {
// Check if the default config exists
if (!std::filesystem::exists(default_config_file)) {
// If the default config is also missing, create it from getDefaultConfig()
const auto default_config = GetDefaultKeyboardConfig();
const auto default_config = GetDefaultInputConfig();
std::ofstream default_config_stream(default_config_file);
if (default_config_stream) {
default_config_stream << default_config;
@@ -1180,6 +1195,17 @@ std::filesystem::path GetFoolproofKbmConfigFile(const std::string& game_id) {
return default_config_file;
}
// Create global config if it doesn't exist yet
if (game_id == "global" && !std::filesystem::exists(config_file)) {
if (!std::filesystem::exists(config_file)) {
const auto global_config = GetDefaultGlobalConfig();
std::ofstream global_config_stream(config_file);
if (global_config_stream) {
global_config_stream << global_config;
}
}
}
// If game-specific config doesn't exist, create it from the default config
if (!std::filesystem::exists(config_file)) {
std::filesystem::copy(default_config_file, config_file);

View File

@@ -156,7 +156,7 @@ std::filesystem::path getAddonInstallDir();
void setDefaultValues();
// todo: name and function location pending
std::filesystem::path GetFoolproofKbmConfigFile(const std::string& game_id = "");
constexpr std::string_view GetDefaultGlobalConfig();
std::filesystem::path GetFoolproofInputConfigFile(const std::string& game_id = "");
}; // namespace Config

View File

@@ -373,8 +373,6 @@ void L::Draw() {
if (IsKeyPressed(ImGuiKey_F10, false)) {
if (io.KeyCtrl) {
DebugState.IsShowingDebugMenuBar() ^= true;
} else {
show_simple_fps = !show_simple_fps;
}
visibility_toggled = true;
}
@@ -384,22 +382,13 @@ void L::Draw() {
if (!DebugState.ShouldPauseInSubmit()) {
DebugState.RequestFrameDump(dump_frame_count);
}
} else {
if (DebugState.IsGuestThreadsPaused()) {
DebugState.ResumeGuestThreads();
SDL_Log("Game resumed from Keyboard");
} else {
DebugState.PauseGuestThreads();
SDL_Log("Game paused from Keyboard");
}
visibility_toggled = true;
}
}
if (DebugState.IsGuestThreadsPaused()) {
ImVec2 pos = ImVec2(10, 10);
ImU32 color = IM_COL32(255, 255, 255, 255);
ImGui::GetForegroundDrawList()->AddText(pos, color, "Game Paused Press F9 to Resume");
ImGui::GetForegroundDrawList()->AddText(pos, color, "Emulation Paused");
}
if (show_simple_fps) {

View File

@@ -9,6 +9,7 @@
#include "core/memory.h"
#include "imgui_impl_sdl3.h"
#include "input/controller.h"
#include "input/input_handler.h"
#include "sdl_window.h"
// SDL

View File

@@ -60,10 +60,6 @@ std::pair<int, int> leftjoystick_deadzone, rightjoystick_deadzone, lefttrigger_d
std::list<std::pair<InputEvent, bool>> pressed_keys;
std::list<InputID> toggled_keys;
static std::vector<BindingConnection> connections;
static std::vector<std::string> fullscreenHotkeyInputsPad(3, "");
static std::vector<std::string> pauseHotkeyInputsPad(3, "");
static std::vector<std::string> simpleFpsHotkeyInputsPad(3, "");
static std::vector<std::string> quitHotkeyInputsPad(3, "");
auto output_array = std::array{
// Important: these have to be the first, or else they will update in the wrong order
@@ -103,6 +99,15 @@ auto output_array = std::array{
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFT_TRIGGER),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER),
ControllerOutput(HOTKEY_FULLSCREEN),
ControllerOutput(HOTKEY_PAUSE),
ControllerOutput(HOTKEY_SIMPLE_FPS),
ControllerOutput(HOTKEY_QUIT),
ControllerOutput(HOTKEY_RELOAD_INPUTS),
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK),
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_GYRO),
ControllerOutput(HOTKEY_RENDERDOC),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID),
};
@@ -211,8 +216,9 @@ InputBinding GetBindingFromString(std::string& line) {
}
void ParseInputConfig(const std::string game_id = "") {
std::string config_type = Config::GetUseUnifiedInputConfig() ? "default" : game_id;
const auto config_file = Config::GetFoolproofKbmConfigFile(config_type);
std::string game_id_or_default = Config::GetUseUnifiedInputConfig() ? "default" : game_id;
const auto config_file = Config::GetFoolproofInputConfigFile(game_id_or_default);
const auto global_config_file = Config::GetFoolproofInputConfigFile("global");
// we reset these here so in case the user fucks up or doesn't include some of these,
// we can fall back to default
@@ -231,9 +237,10 @@ void ParseInputConfig(const std::string game_id = "") {
int lineCount = 0;
std::ifstream file(config_file);
std::ifstream config_stream(config_file);
std::ifstream global_config_stream(global_config_file);
std::string line = "";
while (std::getline(file, line)) {
auto ProcessLine = [&]() -> void {
lineCount++;
// Strip the ; and whitespace
@@ -241,7 +248,7 @@ void ParseInputConfig(const std::string game_id = "") {
[](unsigned char c) { return std::isspace(c); }),
line.end());
if (line.empty()) {
continue;
return;
}
// Truncate lines starting at #
@@ -250,7 +257,7 @@ void ParseInputConfig(const std::string game_id = "") {
line = line.substr(0, comment_pos);
}
if (line.empty()) {
continue;
return;
}
// Split the line by '='
@@ -258,7 +265,7 @@ void ParseInputConfig(const std::string game_id = "") {
if (equal_pos == std::string::npos) {
LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.",
lineCount, line);
continue;
return;
}
std::string output_string = line.substr(0, equal_pos);
@@ -287,7 +294,7 @@ void ParseInputConfig(const std::string game_id = "") {
LOG_WARNING(Input, "Invalid argument for mouse-to-joystick binding");
SetMouseToJoystick(0);
}
continue;
return;
} else if (output_string == "key_toggle") {
if (comma_pos != std::string::npos) {
// handle key-to-key toggling (separate list?)
@@ -297,18 +304,18 @@ void ParseInputConfig(const std::string game_id = "") {
"Syntax error: Please provide exactly 2 keys: "
"first is the toggler, the second is the key to toggle: {}",
line);
continue;
return;
}
ControllerOutput* toggle_out =
&*std::ranges::find(output_array, ControllerOutput(KEY_TOGGLE));
BindingConnection toggle_connection = BindingConnection(
InputBinding(toggle_keys.keys[0]), toggle_out, 0, toggle_keys.keys[1]);
connections.insert(connections.end(), toggle_connection);
continue;
return;
}
LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.",
lineCount, line);
continue;
return;
} else if (output_string == "mouse_movement_params") {
std::stringstream ss(input_string);
char comma; // To hold the comma separators between the floats
@@ -317,10 +324,10 @@ void ParseInputConfig(const std::string game_id = "") {
// Check for invalid input (in case there's an unexpected format)
if (ss.fail()) {
LOG_WARNING(Input, "Failed to parse mouse movement parameters from line: {}", line);
continue;
return;
}
SetMouseParams(mouse_deadzone_offset, mouse_speed, mouse_speed_offset);
continue;
return;
} else if (output_string == "analog_deadzone") {
std::stringstream ss(input_string);
std::string device, inner_deadzone_str, outer_deadzone_str;
@@ -328,7 +335,7 @@ void ParseInputConfig(const std::string game_id = "") {
if (!std::getline(ss, device, ',') || !std::getline(ss, inner_deadzone_str, ',') ||
!std::getline(ss, outer_deadzone_str)) {
LOG_WARNING(Input, "Malformed deadzone config at line {}: \"{}\"", lineCount, line);
continue;
return;
}
auto inner_deadzone = parseInt(inner_deadzone_str);
@@ -336,7 +343,7 @@ void ParseInputConfig(const std::string game_id = "") {
if (!inner_deadzone || !outer_deadzone) {
LOG_WARNING(Input, "Invalid deadzone values at line {}: \"{}\"", lineCount, line);
continue;
return;
}
std::pair<int, int> deadzone = {*inner_deadzone, *outer_deadzone};
@@ -356,7 +363,7 @@ void ParseInputConfig(const std::string game_id = "") {
LOG_WARNING(Input, "Invalid axis name at line {}: \"{}\", skipping line.",
lineCount, line);
}
continue;
return;
} else if (output_string == "override_controller_color") {
std::stringstream ss(input_string);
std::string enable, r_s, g_s, b_s;
@@ -365,7 +372,7 @@ void ParseInputConfig(const std::string game_id = "") {
!std::getline(ss, g_s, ',') || !std::getline(ss, b_s)) {
LOG_WARNING(Input, "Malformed controller color config at line {}: \"{}\"",
lineCount, line);
continue;
return;
}
r = parseInt(r_s);
g = parseInt(g_s);
@@ -373,13 +380,13 @@ void ParseInputConfig(const std::string game_id = "") {
if (!r || !g || !b) {
LOG_WARNING(Input, "Invalid RGB values at line {}: \"{}\", skipping line.",
lineCount, line);
continue;
return;
}
Config::SetOverrideControllerColor(enable == "true");
Config::SetControllerCustomColor(*r, *g, *b);
LOG_DEBUG(Input, "Parsed color settings: {} {} {} {}",
enable == "true" ? "override" : "no override", *r, *b, *g);
continue;
return;
}
// normal cases
@@ -390,7 +397,7 @@ void ParseInputConfig(const std::string game_id = "") {
if (binding.IsEmpty()) {
LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.",
lineCount, line);
continue;
return;
}
if (button_it != string_to_cbutton_map.end()) {
connection = BindingConnection(
@@ -409,11 +416,17 @@ void ParseInputConfig(const std::string game_id = "") {
} else {
LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.",
lineCount, line);
continue;
return;
}
LOG_DEBUG(Input, "Succesfully parsed line {}", lineCount);
};
while (std::getline(global_config_stream, line)) {
ProcessLine();
}
file.close();
while (std::getline(config_stream, line)) {
ProcessLine();
}
config_stream.close();
std::sort(connections.begin(), connections.end());
for (auto& c : connections) {
LOG_DEBUG(Input, "Binding: {} : {}", c.output->ToString(), c.binding.ToString());
@@ -489,11 +502,16 @@ void ControllerOutput::ResetUpdate() {
*new_param = 0; // bruh
}
void ControllerOutput::AddUpdate(InputEvent event) {
if (button == KEY_TOGGLE) {
switch (button) {
case KEY_TOGGLE:
if (event.active) {
ToggleKeyInList(event.input);
}
} else if (button != SDL_GAMEPAD_BUTTON_INVALID) {
return;
default:
break;
}
if (button != SDL_GAMEPAD_BUTTON_INVALID) {
if (event.input.type == InputType::Axis) {
bool temp = event.axis_value * (positive_axis ? 1 : -1) > 0x40;
new_button_state |= event.active && event.axis_value * (positive_axis ? 1 : -1) > 0x40;
@@ -509,6 +527,14 @@ void ControllerOutput::AddUpdate(InputEvent event) {
}
}
void ControllerOutput::FinalizeUpdate() {
auto PushSDLEvent = [&](u32 event_type) {
if (new_button_state) {
SDL_Event e;
SDL_memset(&e, 0, sizeof(e));
e.type = event_type;
SDL_PushEvent(&e);
}
};
state_changed = old_button_state != new_button_state || old_param != *new_param;
if (!state_changed) {
return;
@@ -535,10 +561,33 @@ void ControllerOutput::FinalizeUpdate() {
case RIGHTJOYSTICK_HALFMODE:
rightjoystick_halfmode = new_button_state;
break;
// KEY_TOGGLE isn't handled here anymore, as this function doesn't have the necessary data
// to do it, and it would be inconvenient to force it here, when AddUpdate does the job just
// fine, and a toggle doesn't have to checked against every input that's bound to it, it's
// enough that one is pressed
case HOTKEY_FULLSCREEN:
PushSDLEvent(SDL_EVENT_TOGGLE_FULLSCREEN);
break;
case HOTKEY_PAUSE:
PushSDLEvent(SDL_EVENT_TOGGLE_PAUSE);
break;
case HOTKEY_SIMPLE_FPS:
PushSDLEvent(SDL_EVENT_TOGGLE_SIMPLE_FPS);
break;
case HOTKEY_RELOAD_INPUTS:
PushSDLEvent(SDL_EVENT_RELOAD_INPUTS);
break;
case HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK:
PushSDLEvent(SDL_EVENT_MOUSE_TO_JOYSTICK);
break;
case HOTKEY_TOGGLE_MOUSE_TO_GYRO:
PushSDLEvent(SDL_EVENT_MOUSE_TO_GYRO);
break;
case HOTKEY_RENDERDOC:
PushSDLEvent(SDL_EVENT_RDOC_CAPTURE);
break;
case HOTKEY_QUIT:
PushSDLEvent(SDL_EVENT_QUIT_DIALOG);
break;
case KEY_TOGGLE:
// noop
break;
case MOUSE_GYRO_ROLL_MODE:
SetMouseGyroRollMode(new_button_state);
break;
@@ -553,8 +602,8 @@ void ControllerOutput::FinalizeUpdate() {
*value = 0;
} else {
*value = (*value >= 0 ? 1 : -1) *
std::clamp((int)((128.0 * (std::abs(*value) - deadzone.first)) /
(float)(deadzone.second - deadzone.first)),
std::clamp(static_cast<s32>((128.0 * (std::abs(*value) - deadzone.first)) /
(float)(deadzone.second - deadzone.first)),
0, 128);
}
};
@@ -735,158 +784,4 @@ void ActivateOutputsFromInputs() {
}
}
std::vector<std::string> GetHotkeyInputs(Input::HotkeyPad hotkey) {
switch (hotkey) {
case Input::HotkeyPad::FullscreenPad:
return fullscreenHotkeyInputsPad;
case Input::HotkeyPad::PausePad:
return pauseHotkeyInputsPad;
case Input::HotkeyPad::SimpleFpsPad:
return simpleFpsHotkeyInputsPad;
case Input::HotkeyPad::QuitPad:
return quitHotkeyInputsPad;
default:
return {};
}
}
void LoadHotkeyInputs() {
const auto hotkey_file = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "hotkeys.ini";
if (!std::filesystem::exists(hotkey_file)) {
createHotkeyFile(hotkey_file);
}
std::string controllerFullscreenString, controllerPauseString, controllerFpsString,
controllerQuitString = "";
std::ifstream file(hotkey_file);
int lineCount = 0;
std::string line = "";
while (std::getline(file, line)) {
lineCount++;
std::size_t equal_pos = line.find('=');
if (equal_pos == std::string::npos)
continue;
if (line.contains("controllerFullscreen")) {
controllerFullscreenString = line.substr(equal_pos + 2);
} else if (line.contains("controllerStop")) {
controllerQuitString = line.substr(equal_pos + 2);
} else if (line.contains("controllerFps")) {
controllerFpsString = line.substr(equal_pos + 2);
} else if (line.contains("controllerPause")) {
controllerPauseString = line.substr(equal_pos + 2);
}
}
file.close();
auto getVectorFromString = [&](std::vector<std::string>& inputVector,
const std::string& inputString) {
std::size_t comma_pos = inputString.find(',');
if (comma_pos == std::string::npos) {
inputVector[0] = inputString;
inputVector[1] = "unused";
inputVector[2] = "unused";
} else {
inputVector[0] = inputString.substr(0, comma_pos);
std::string substring = inputString.substr(comma_pos + 1);
std::size_t comma2_pos = substring.find(',');
if (comma2_pos == std::string::npos) {
inputVector[1] = substring;
inputVector[2] = "unused";
} else {
inputVector[1] = substring.substr(0, comma2_pos);
inputVector[2] = substring.substr(comma2_pos + 1);
}
}
};
getVectorFromString(fullscreenHotkeyInputsPad, controllerFullscreenString);
getVectorFromString(quitHotkeyInputsPad, controllerQuitString);
getVectorFromString(pauseHotkeyInputsPad, controllerPauseString);
getVectorFromString(simpleFpsHotkeyInputsPad, controllerFpsString);
}
bool HotkeyInputsPressed(std::vector<std::string> inputs) {
if (inputs[0] == "unmapped") {
return false;
}
auto controller = Common::Singleton<Input::GameController>::Instance();
auto engine = controller->GetEngine();
SDL_Gamepad* gamepad = engine->m_gamepad;
if (!gamepad) {
return false;
}
std::vector<bool> isPressed(3, false);
for (int i = 0; i < 3; i++) {
if (inputs[i] == "cross") {
isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH);
} else if (inputs[i] == "circle") {
isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_EAST);
} else if (inputs[i] == "square") {
isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_WEST);
} else if (inputs[i] == "triangle") {
isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_NORTH);
} else if (inputs[i] == "pad_up") {
isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP);
} else if (inputs[i] == "pad_down") {
isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
} else if (inputs[i] == "pad_left") {
isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
} else if (inputs[i] == "pad_right") {
isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
} else if (inputs[i] == "l1") {
isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER);
} else if (inputs[i] == "r1") {
isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER);
} else if (inputs[i] == "l3") {
isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_STICK);
} else if (inputs[i] == "r3") {
isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_RIGHT_STICK);
} else if (inputs[i] == "options") {
isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_START);
} else if (inputs[i] == "back") {
isPressed[i] = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_BACK);
} else if (inputs[i] == "l2") {
isPressed[i] = (SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER) > 16000);
} else if (inputs[i] == "r2") {
isPressed[i] = (SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) > 16000);
} else if (inputs[i] == "unused") {
isPressed[i] = true;
} else {
isPressed[i] = false;
}
}
if (isPressed[0] && isPressed[1] && isPressed[2]) {
return true;
}
return false;
}
void createHotkeyFile(std::filesystem::path hotkey_file) {
std::string_view default_hotkeys = R"(controllerStop = unmapped
controllerFps = l2,r2,r3
controllerPause = l2,r2,options
controllerFullscreen = l2,r2,l3
keyboardStop = placeholder
keyboardFps = placeholder
keyboardPause = placeholder
keyboardFullscreen = placeholder
)";
std::ofstream default_hotkeys_stream(hotkey_file);
if (default_hotkeys_stream) {
default_hotkeys_stream << default_hotkeys;
}
}
} // namespace Input

View File

@@ -4,11 +4,9 @@
#pragma once
#include <array>
#include <filesystem>
#include <map>
#include <string>
#include <unordered_set>
#include <vector>
#include "SDL3/SDL_events.h"
#include "SDL3/SDL_timer.h"
@@ -29,7 +27,15 @@
#define SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER SDL_GAMEPAD_BUTTON_COUNT + 2
#define SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT SDL_GAMEPAD_BUTTON_COUNT + 3
// idk who already used what where so I just chose a big number
#define SDL_EVENT_TOGGLE_FULLSCREEN SDL_EVENT_USER + 1
#define SDL_EVENT_TOGGLE_PAUSE SDL_EVENT_USER + 2
#define SDL_EVENT_CHANGE_CONTROLLER SDL_EVENT_USER + 3
#define SDL_EVENT_TOGGLE_SIMPLE_FPS SDL_EVENT_USER + 4
#define SDL_EVENT_RELOAD_INPUTS SDL_EVENT_USER + 5
#define SDL_EVENT_MOUSE_TO_JOYSTICK SDL_EVENT_USER + 6
#define SDL_EVENT_MOUSE_TO_GYRO SDL_EVENT_USER + 7
#define SDL_EVENT_RDOC_CAPTURE SDL_EVENT_USER + 8
#define SDL_EVENT_QUIT_DIALOG SDL_EVENT_USER + 9
#define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 10
#define LEFTJOYSTICK_HALFMODE 0x00010000
@@ -39,6 +45,15 @@
#define KEY_TOGGLE 0x00200000
#define MOUSE_GYRO_ROLL_MODE 0x00400000
#define HOTKEY_FULLSCREEN 0xf0000001
#define HOTKEY_PAUSE 0xf0000002
#define HOTKEY_SIMPLE_FPS 0xf0000003
#define HOTKEY_QUIT 0xf0000004
#define HOTKEY_RELOAD_INPUTS 0xf0000005
#define HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK 0xf0000006
#define HOTKEY_TOGGLE_MOUSE_TO_GYRO 0xf0000007
#define HOTKEY_RENDERDOC 0xf0000008
#define SDL_UNMAPPED UINT32_MAX - 1
namespace Input {
@@ -113,11 +128,20 @@ const std::map<std::string, u32> string_to_cbutton_map = {
// this is only for input
{"back", SDL_GAMEPAD_BUTTON_BACK},
{"share", SDL_GAMEPAD_BUTTON_BACK},
{"lpaddle_high", SDL_GAMEPAD_BUTTON_LEFT_PADDLE1},
{"lpaddle_low", SDL_GAMEPAD_BUTTON_LEFT_PADDLE2},
{"rpaddle_high", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1},
{"rpaddle_low", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2},
{"mouse_gyro_roll_mode", MOUSE_GYRO_ROLL_MODE},
{"hotkey_pause", HOTKEY_PAUSE},
{"hotkey_fullscreen", HOTKEY_FULLSCREEN},
{"hotkey_show_fps", HOTKEY_SIMPLE_FPS},
{"hotkey_quit", HOTKEY_QUIT},
{"hotkey_reload_inputs", HOTKEY_RELOAD_INPUTS},
{"hotkey_toggle_mouse_to_joystick", HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK},
{"hotkey_toggle_mouse_to_gyro", HOTKEY_TOGGLE_MOUSE_TO_GYRO},
{"hotkey_renderdoc_capture", HOTKEY_RENDERDOC},
};
const std::map<std::string, AxisMapping> string_to_axis_map = {
@@ -178,6 +202,20 @@ const std::map<std::string, u32> string_to_keyboard_key_map = {
{"8", SDLK_8},
{"9", SDLK_9},
// F keys
{"f1", SDLK_F1},
{"f2", SDLK_F2},
{"f3", SDLK_F3},
{"f4", SDLK_F4},
{"f5", SDLK_F5},
{"f6", SDLK_F6},
{"f7", SDLK_F7},
{"f8", SDLK_F8},
{"f9", SDLK_F9},
{"f10", SDLK_F10},
{"f11", SDLK_F11},
{"f12", SDLK_F12},
// symbols
{"grave", SDLK_GRAVE},
{"tilde", SDLK_TILDE},
@@ -450,16 +488,10 @@ public:
InputEvent ProcessBinding();
};
enum HotkeyPad { FullscreenPad, PausePad, SimpleFpsPad, QuitPad };
// Updates the list of pressed keys with the given input.
// Returns whether the list was updated or not.
bool UpdatePressedKeys(InputEvent event);
void ActivateOutputsFromInputs();
void LoadHotkeyInputs();
bool HotkeyInputsPressed(std::vector<std::string> inputs);
std::vector<std::string> GetHotkeyInputs(Input::HotkeyPad hotkey);
void createHotkeyFile(std::filesystem::path hotkey_file);
} // namespace Input

View File

@@ -9,7 +9,6 @@
#include "common/path_util.h"
#include "control_settings.h"
#include "input/input_handler.h"
#include "sdl_window.h"
#include "ui_control_settings.h"
ControlSettings::ControlSettings(std::shared_ptr<GameInfoClass> game_info_get, bool isGameRunning,
@@ -181,7 +180,7 @@ void ControlSettings::SaveControllerConfig(bool CloseOnSave) {
config_id = (ui->ProfileComboBox->currentText() == tr("Common Config"))
? "default"
: ui->ProfileComboBox->currentText().toStdString();
const auto config_file = Config::GetFoolproofKbmConfigFile(config_id);
const auto config_file = Config::GetFoolproofInputConfigFile(config_id);
int lineCount = 0;
std::string line;
@@ -208,6 +207,11 @@ void ControlSettings::SaveControllerConfig(bool CloseOnSave) {
output_string = line.substr(0, equal_pos - 1);
input_string = line.substr(equal_pos + 2);
if (output_string.contains("hotkey")) {
lines.push_back(line);
continue;
}
bool controllerInputdetected = false;
for (std::string input : ControllerInputs) {
// Needed to avoid detecting backspace while detecting back
@@ -411,7 +415,7 @@ void ControlSettings::SetUIValuestoMappings() {
? "default"
: ui->ProfileComboBox->currentText().toStdString();
const auto config_file = Config::GetFoolproofKbmConfigFile(config_id);
const auto config_file = Config::GetFoolproofInputConfigFile(config_id);
std::ifstream file(config_file);
bool CrossExists = false, CircleExists = false, SquareExists = false, TriangleExists = false,

View File

@@ -3,21 +3,23 @@
#include <fstream>
#include <QKeyEvent>
#include <QtConcurrent>
#include <QMessageBox>
#include <QtConcurrent/qtconcurrentrun.h>
#include <SDL3/SDL.h>
#include "common/config.h"
#include "common/logging/log.h"
#include "common/path_util.h"
#include "hotkeys.h"
#include "input/controller.h"
#include "input/input_handler.h"
#include "sdl_event_wrapper.h"
#include "ui_hotkeys.h"
hotkeys::hotkeys(bool isGameRunning, QWidget* parent)
: QDialog(parent), GameRunning(isGameRunning), ui(new Ui::hotkeys) {
Hotkeys::Hotkeys(bool isGameRunning, QWidget* parent)
: QDialog(parent), GameRunning(isGameRunning), ui(new Ui::Hotkeys) {
ui->setupUi(this);
installEventFilter(this);
if (!GameRunning) {
SDL_InitSubSystem(SDL_INIT_GAMEPAD);
@@ -28,46 +30,59 @@ hotkeys::hotkeys(bool isGameRunning, QWidget* parent)
LoadHotkeys();
CheckGamePad();
installEventFilter(this);
ButtonsList = {
ui->fpsButtonPad,
ui->quitButtonPad,
ui->fullscreenButtonPad,
ui->pauseButtonPad,
};
PadButtonsList = {ui->fpsButtonPad, ui->quitButtonPad, ui->fullscreenButtonPad,
ui->pauseButtonPad, ui->reloadButtonPad};
ui->buttonBox->button(QDialogButtonBox::Save)->setText(tr("Save"));
ui->buttonBox->button(QDialogButtonBox::Apply)->setText(tr("Apply"));
ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel"));
KBButtonsList = {ui->fpsButtonKB, ui->quitButtonKB, ui->fullscreenButtonKB,
ui->pauseButtonKB, ui->reloadButtonKB, ui->renderdocButton,
ui->mouseJoystickButton, ui->mouseGyroButton};
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, [this](QAbstractButton* button) {
if (button == ui->buttonBox->button(QDialogButtonBox::Save)) {
SaveHotkeys(true);
} else if (button == ui->buttonBox->button(QDialogButtonBox::Apply)) {
SaveHotkeys(false);
} else if (button == ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)) {
SetDefault();
} else if (button == ui->buttonBox->button(QDialogButtonBox::Cancel)) {
QWidget::close();
}
});
for (auto& button : ButtonsList) {
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close);
ui->buttonBox->button(QDialogButtonBox::Save)->setText(tr("Save"));
ui->buttonBox->button(QDialogButtonBox::Apply)->setText(tr("Apply"));
ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel"));
ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setText(tr("Restore Defaults"));
for (auto& button : PadButtonsList) {
connect(button, &QPushButton::clicked, this,
[this, &button]() { StartTimer(button, true); });
}
connect(this, &hotkeys::PushGamepadEvent, this, [this]() { CheckMapping(MappingButton); });
for (auto& button : KBButtonsList) {
connect(button, &QPushButton::clicked, this,
[this, &button]() { StartTimer(button, false); });
}
SdlEventWrapper::Wrapper::wrapperActive = true;
QObject::connect(SdlEventWrapper::Wrapper::GetInstance(), &SdlEventWrapper::Wrapper::SDLEvent,
this, &hotkeys::processSDLEvents);
this, &Hotkeys::processSDLEvents);
if (!GameRunning) {
Polling = QtConcurrent::run(&hotkeys::pollSDLEvents, this);
Polling = QtConcurrent::run(&Hotkeys::pollSDLEvents, this);
}
}
void hotkeys::DisableMappingButtons() {
for (const auto& i : ButtonsList) {
void Hotkeys::DisableMappingButtons() {
for (auto& i : PadButtonsList) {
i->setEnabled(false);
}
for (auto& i : KBButtonsList) {
i->setEnabled(false);
}
@@ -75,8 +90,12 @@ void hotkeys::DisableMappingButtons() {
ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
}
void hotkeys::EnableMappingButtons() {
for (const auto& i : ButtonsList) {
void Hotkeys::EnableMappingButtons() {
for (auto& i : PadButtonsList) {
i->setEnabled(true);
}
for (auto& i : KBButtonsList) {
i->setEnabled(true);
}
@@ -84,63 +103,154 @@ void hotkeys::EnableMappingButtons() {
ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
}
void hotkeys::SaveHotkeys(bool CloseOnSave) {
const auto hotkey_file = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "hotkeys.ini";
if (!std::filesystem::exists(hotkey_file)) {
Input::createHotkeyFile(hotkey_file);
}
void Hotkeys::SetDefault() {
QString controllerFullscreenString, controllerPauseString, controllerFpsString,
controllerQuitString = "";
std::ifstream file(hotkey_file);
PadButtonsList = {ui->fpsButtonPad, ui->quitButtonPad, ui->fullscreenButtonPad,
ui->pauseButtonPad, ui->reloadButtonPad};
KBButtonsList = {ui->fpsButtonKB, ui->quitButtonKB, ui->fullscreenButtonKB,
ui->pauseButtonKB, ui->reloadButtonKB, ui->renderdocButton,
ui->mouseJoystickButton, ui->mouseGyroButton};
ui->fpsButtonPad->setText("unmapped");
ui->quitButtonPad->setText("unmapped");
ui->fullscreenButtonPad->setText("unmapped");
ui->pauseButtonPad->setText("unmapped");
ui->reloadButtonPad->setText("unmapped");
ui->fpsButtonKB->setText("f10");
ui->quitButtonKB->setText("lctrl, lshift, end");
ui->fullscreenButtonKB->setText("f11");
ui->pauseButtonKB->setText("f9");
ui->reloadButtonKB->setText("f8");
ui->renderdocButton->setText("f12");
ui->mouseJoystickButton->setText("f7");
ui->mouseGyroButton->setText("f6");
}
void Hotkeys::SaveHotkeys(bool CloseOnSave) {
std::vector<std::string> lines, inputs;
auto add_mapping = [&](const QString& buttonText, const std::string& output_name) {
if (buttonText.toStdString() != "unmapped") {
lines.push_back(output_name + " = " + buttonText.toStdString());
inputs.push_back(buttonText.toStdString());
}
};
lines.push_back("# Anything put here will be loaded for all games,");
lines.push_back("# alongside the game's config or default.ini depending on your preference.");
lines.push_back("");
add_mapping(ui->fullscreenButtonPad->text(), "hotkey_fullscreen");
add_mapping(ui->fullscreenButtonKB->text(), "hotkey_fullscreen");
lines.push_back("");
add_mapping(ui->pauseButtonPad->text(), "hotkey_pause");
add_mapping(ui->pauseButtonKB->text(), "hotkey_pause");
lines.push_back("");
add_mapping(ui->fpsButtonPad->text(), "hotkey_show_fps");
add_mapping(ui->fpsButtonKB->text(), "hotkey_show_fps");
lines.push_back("");
add_mapping(ui->quitButtonPad->text(), "hotkey_quit");
add_mapping(ui->quitButtonKB->text(), "hotkey_quit");
lines.push_back("");
add_mapping(ui->reloadButtonPad->text(), "hotkey_reload_inputs");
add_mapping(ui->reloadButtonKB->text(), "hotkey_reload_inputs");
lines.push_back("");
add_mapping(ui->renderdocButton->text(), "hotkey_renderdoc_capture");
add_mapping(ui->mouseJoystickButton->text(), "hotkey_toggle_mouse_to_joystick");
add_mapping(ui->mouseGyroButton->text(), "hotkey_toggle_mouse_to_gyro");
auto hotkey_file = Config::GetFoolproofInputConfigFile("global");
std::fstream file(hotkey_file);
int lineCount = 0;
std::string line = "";
std::vector<std::string> lines;
std::string line;
while (std::getline(file, line)) {
lineCount++;
std::size_t comment_pos = line.find('#');
if (comment_pos != std::string::npos) {
if (!line.contains("Anything put here will be loaded for all games") &&
!line.contains("alongside the game's config or default.ini depending on your"))
lines.push_back(line);
continue;
}
std::size_t equal_pos = line.find('=');
if (equal_pos == std::string::npos) {
lines.push_back(line);
continue;
}
if (line.contains("controllerFullscreen")) {
line = "controllerFullscreen = " + ui->fullscreenButtonPad->text().toStdString();
} else if (line.contains("controllerStop")) {
line = "controllerStop = " + ui->quitButtonPad->text().toStdString();
} else if (line.contains("controllerFps")) {
line = "controllerFps = " + ui->fpsButtonPad->text().toStdString();
} else if (line.contains("controllerPause")) {
line = "controllerPause = " + ui->pauseButtonPad->text().toStdString();
if (!line.contains("hotkey")) {
lines.push_back(line);
}
lines.push_back(line);
}
file.close();
std::ofstream output_file(hotkey_file);
// Prevent duplicate inputs that break the input engine
bool duplicateFound = false;
QSet<QString> duplicateMappings;
for (auto it = inputs.begin(); it != inputs.end(); ++it) {
if (std::find(it + 1, inputs.end(), *it) != inputs.end()) {
duplicateFound = true;
duplicateMappings.insert(QString::fromStdString(*it));
}
}
if (duplicateFound) {
QStringList duplicatesList;
for (const QString& mapping : duplicateMappings) {
for (auto& button : PadButtonsList) {
if (button->text() == mapping)
duplicatesList.append(button->objectName() + " - " + mapping);
}
for (auto& button : KBButtonsList) {
if (button->text() == mapping)
duplicatesList.append(button->objectName() + " - " + mapping);
}
}
QMessageBox::information(
this, tr("Unable to Save"),
// clang-format off
QString(tr("Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:\n\n%1").arg(duplicatesList.join("\n"))));
// clang-format on
return;
}
std::vector<std::string> save;
bool CurrentLineEmpty = false, LastLineEmpty = false;
for (auto const& line : lines) {
LastLineEmpty = CurrentLineEmpty ? true : false;
CurrentLineEmpty = line.empty() ? true : false;
if (!CurrentLineEmpty || !LastLineEmpty)
save.push_back(line);
}
std::ofstream output_file(hotkey_file);
for (auto const& line : save) {
output_file << line << '\n';
}
output_file.close();
Input::LoadHotkeyInputs();
// this also parses global hotkeys
if (GameRunning)
Input::ParseInputConfig("default");
if (CloseOnSave)
QWidget::close();
}
void hotkeys::LoadHotkeys() {
const auto hotkey_file = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "hotkeys.ini";
if (!std::filesystem::exists(hotkey_file)) {
Input::createHotkeyFile(hotkey_file);
}
QString controllerFullscreenString, controllerPauseString, controllerFpsString,
controllerQuitString = "";
void Hotkeys::LoadHotkeys() {
auto hotkey_file = Config::GetFoolproofInputConfigFile("global");
std::ifstream file(hotkey_file);
int lineCount = 0;
std::string line = "";
@@ -152,26 +262,51 @@ void hotkeys::LoadHotkeys() {
if (equal_pos == std::string::npos)
continue;
if (line.contains("controllerFullscreen")) {
controllerFullscreenString = QString::fromStdString(line.substr(equal_pos + 2));
} else if (line.contains("controllerStop")) {
controllerQuitString = QString::fromStdString(line.substr(equal_pos + 2));
} else if (line.contains("controllerFps")) {
controllerFpsString = QString::fromStdString(line.substr(equal_pos + 2));
} else if (line.contains("controllerPause")) {
controllerPauseString = QString::fromStdString(line.substr(equal_pos + 2));
std::string output_string = line.substr(0, equal_pos);
std::string input_string = line.substr(equal_pos + 2);
bool controllerInputDetected = false;
for (const std::string& input : ControllerInputs) {
// Needed to avoid detecting backspace while detecting back
if (input_string.contains(input) && !input_string.contains("backspace")) {
controllerInputDetected = true;
break;
}
}
if (output_string.contains("hotkey_fullscreen")) {
controllerInputDetected
? ui->fullscreenButtonPad->setText(QString::fromStdString(input_string))
: ui->fullscreenButtonKB->setText(QString::fromStdString(input_string));
} else if (output_string.contains("hotkey_show_fps")) {
controllerInputDetected
? ui->fpsButtonPad->setText(QString::fromStdString(input_string))
: ui->fpsButtonKB->setText(QString::fromStdString(input_string));
} else if (output_string.contains("hotkey_pause")) {
controllerInputDetected
? ui->pauseButtonPad->setText(QString::fromStdString(input_string))
: ui->pauseButtonKB->setText(QString::fromStdString(input_string));
} else if (output_string.contains("hotkey_quit")) {
controllerInputDetected
? ui->quitButtonPad->setText(QString::fromStdString(input_string))
: ui->quitButtonKB->setText(QString::fromStdString(input_string));
} else if (output_string.contains("hotkey_reload_inputs")) {
controllerInputDetected
? ui->reloadButtonPad->setText(QString::fromStdString(input_string))
: ui->reloadButtonKB->setText(QString::fromStdString(input_string));
} else if (output_string.contains("hotkey_renderdoc_capture")) {
ui->renderdocButton->setText(QString::fromStdString(input_string));
} else if (output_string.contains("hotkey_toggle_mouse_to_joystick")) {
ui->mouseJoystickButton->setText(QString::fromStdString(input_string));
} else if (output_string.contains("hotkey_toggle_mouse_to_gyro")) {
ui->mouseGyroButton->setText(QString::fromStdString(input_string));
}
}
file.close();
ui->fpsButtonPad->setText(controllerFpsString);
ui->quitButtonPad->setText(controllerQuitString);
ui->fullscreenButtonPad->setText(controllerFullscreenString);
ui->pauseButtonPad->setText(controllerPauseString);
}
void hotkeys::CheckGamePad() {
void Hotkeys::CheckGamePad() {
if (h_gamepad) {
SDL_CloseGamepad(h_gamepad);
h_gamepad = nullptr;
@@ -181,6 +316,7 @@ void hotkeys::CheckGamePad() {
if (!h_gamepads) {
LOG_ERROR(Input, "Cannot get gamepad list: {}", SDL_GetError());
return;
}
int defaultIndex = GamepadSelect::GetIndexfromGUID(h_gamepads, gamepad_count,
@@ -204,16 +340,14 @@ void hotkeys::CheckGamePad() {
}
}
void hotkeys::StartTimer(QPushButton*& button, bool isButton) {
void Hotkeys::StartTimer(QPushButton*& button, bool isButton) {
MappingTimer = 3;
EnableButtonMapping = true;
MappingCompleted = false;
L2Pressed = false;
R2Pressed = false;
mapping = button->text();
DisableMappingButtons();
button->setText(tr("Press a button") + " [" + QString::number(MappingTimer) + "]");
isButton ? EnablePadMapping = true : EnableKBMapping = true;
button->setText(tr("Waiting for inputs") + " [" + QString::number(MappingTimer) + "]");
timer = new QTimer(this);
MappingButton = button;
@@ -221,18 +355,18 @@ void hotkeys::StartTimer(QPushButton*& button, bool isButton) {
connect(timer, &QTimer::timeout, this, [this]() { CheckMapping(MappingButton); });
}
void hotkeys::CheckMapping(QPushButton*& button) {
void Hotkeys::CheckMapping(QPushButton*& button) {
MappingTimer -= 1;
button->setText(tr("Press a button") + " [" + QString::number(MappingTimer) + "]");
button->setText(tr("Waiting for inputs") + " [" + QString::number(MappingTimer) + "]");
if (pressedButtons.size() > 0) {
QStringList keyStrings;
for (const QString& buttonAction : pressedButtons) {
for (QString& buttonAction : pressedButtons) {
keyStrings << buttonAction;
}
QString combo = keyStrings.join(",");
QString combo = keyStrings.join(", ");
SetMapping(combo);
MappingButton->setText(combo);
pressedButtons.clear();
@@ -240,32 +374,427 @@ void hotkeys::CheckMapping(QPushButton*& button) {
if (MappingCompleted || MappingTimer <= 0) {
button->setText(mapping);
EnableButtonMapping = false;
EnablePadMapping = false;
EnableKBMapping = false;
L2Pressed = false;
R2Pressed = false;
EnableMappingButtons();
timer->stop();
}
}
void hotkeys::SetMapping(QString input) {
void Hotkeys::SetMapping(QString input) {
mapping = input;
MappingCompleted = true;
}
// use QT events instead of SDL to override default event closing the window with escape
bool hotkeys::eventFilter(QObject* obj, QEvent* event) {
if (event->type() == QEvent::KeyPress && EnableButtonMapping) {
// use QT events instead of SDL for KB since SDL doesn't allow background KB events
bool Hotkeys::eventFilter(QObject* obj, QEvent* event) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Escape) {
SetMapping("unmapped");
PushGamepadEvent();
if (EnableKBMapping || EnablePadMapping) {
SetMapping("unmapped");
CheckMapping(MappingButton);
}
return true;
} else {
if (EnableKBMapping) {
if (pressedButtons.size() >= 3) {
return true;
}
switch (keyEvent->key()) {
// modifiers
case Qt::Key_Shift:
if (keyEvent->nativeScanCode() == LSHIFT_KEY) {
pressedButtons.insert(1, "lshift");
} else {
pressedButtons.insert(2, "rshift");
}
break;
case Qt::Key_Alt:
if (keyEvent->nativeScanCode() == LALT_KEY) {
pressedButtons.insert(3, "lalt");
} else {
pressedButtons.insert(4, "ralt");
}
break;
case Qt::Key_Control:
if (keyEvent->nativeScanCode() == LCTRL_KEY) {
pressedButtons.insert(5, "lctrl");
} else {
pressedButtons.insert(6, "rctrl");
}
break;
case Qt::Key_Meta:
#ifdef _WIN32
pressedButtons.insert(7, "lwin");
#else
pressedButtons.insert(7, "lmeta");
#endif
break;
// f keys
case Qt::Key_F1:
pressedButtons.insert(8, "f1");
break;
case Qt::Key_F2:
pressedButtons.insert(9, "f2");
break;
case Qt::Key_F3:
pressedButtons.insert(10, "f3");
break;
case Qt::Key_F4:
pressedButtons.insert(11, "f4");
break;
case Qt::Key_F5:
pressedButtons.insert(12, "f5");
break;
case Qt::Key_F6:
pressedButtons.insert(13, "f6");
break;
case Qt::Key_F7:
pressedButtons.insert(14, "f7");
break;
case Qt::Key_F8:
pressedButtons.insert(15, "f8");
break;
case Qt::Key_F9:
pressedButtons.insert(16, "f9");
break;
case Qt::Key_F10:
pressedButtons.insert(17, "f10");
break;
case Qt::Key_F11:
pressedButtons.insert(18, "f11");
break;
case Qt::Key_F12:
pressedButtons.insert(19, "f12");
break;
// alphanumeric
case Qt::Key_A:
pressedButtons.insert(20, "a");
break;
case Qt::Key_B:
pressedButtons.insert(21, "b");
break;
case Qt::Key_C:
pressedButtons.insert(22, "c");
break;
case Qt::Key_D:
pressedButtons.insert(23, "d");
break;
case Qt::Key_E:
pressedButtons.insert(24, "e");
break;
case Qt::Key_F:
pressedButtons.insert(25, "f");
break;
case Qt::Key_G:
pressedButtons.insert(26, "g");
break;
case Qt::Key_H:
pressedButtons.insert(27, "h");
break;
case Qt::Key_I:
pressedButtons.insert(28, "i");
break;
case Qt::Key_J:
pressedButtons.insert(29, "j");
break;
case Qt::Key_K:
pressedButtons.insert(30, "k");
break;
case Qt::Key_L:
pressedButtons.insert(31, "l");
break;
case Qt::Key_M:
pressedButtons.insert(32, "m");
break;
case Qt::Key_N:
pressedButtons.insert(33, "n");
break;
case Qt::Key_O:
pressedButtons.insert(34, "o");
break;
case Qt::Key_P:
pressedButtons.insert(35, "p");
break;
case Qt::Key_Q:
pressedButtons.insert(36, "q");
break;
case Qt::Key_R:
pressedButtons.insert(37, "r");
break;
case Qt::Key_S:
pressedButtons.insert(38, "s");
break;
case Qt::Key_T:
pressedButtons.insert(39, "t");
break;
case Qt::Key_U:
pressedButtons.insert(40, "u");
break;
case Qt::Key_V:
pressedButtons.insert(41, "v");
break;
case Qt::Key_W:
pressedButtons.insert(42, "w");
break;
case Qt::Key_X:
pressedButtons.insert(43, "x");
break;
case Qt::Key_Y:
pressedButtons.insert(44, "y");
break;
case Qt::Key_Z:
pressedButtons.insert(45, "z");
break;
case Qt::Key_0:
QApplication::keyboardModifiers() & Qt::KeypadModifier
? pressedButtons.insert(46, "kp0")
: pressedButtons.insert(56, "0");
break;
case Qt::Key_1:
QApplication::keyboardModifiers() & Qt::KeypadModifier
? pressedButtons.insert(47, "kp1")
: pressedButtons.insert(57, "1");
break;
case Qt::Key_2:
QApplication::keyboardModifiers() & Qt::KeypadModifier
? pressedButtons.insert(48, "kp2")
: pressedButtons.insert(58, "2");
break;
case Qt::Key_3:
QApplication::keyboardModifiers() & Qt::KeypadModifier
? pressedButtons.insert(49, "kp3")
: pressedButtons.insert(59, "3");
break;
case Qt::Key_4:
QApplication::keyboardModifiers() & Qt::KeypadModifier
? pressedButtons.insert(50, "kp4")
: pressedButtons.insert(60, "4");
break;
case Qt::Key_5:
QApplication::keyboardModifiers() & Qt::KeypadModifier
? pressedButtons.insert(51, "kp5")
: pressedButtons.insert(61, "5");
break;
case Qt::Key_6:
QApplication::keyboardModifiers() & Qt::KeypadModifier
? pressedButtons.insert(52, "kp6")
: pressedButtons.insert(62, "6");
break;
case Qt::Key_7:
QApplication::keyboardModifiers() & Qt::KeypadModifier
? pressedButtons.insert(53, "kp7")
: pressedButtons.insert(63, "7");
break;
case Qt::Key_8:
QApplication::keyboardModifiers() & Qt::KeypadModifier
? pressedButtons.insert(54, "kp8")
: pressedButtons.insert(64, "8");
break;
case Qt::Key_9:
QApplication::keyboardModifiers() & Qt::KeypadModifier
? pressedButtons.insert(55, "kp9")
: pressedButtons.insert(65, "9");
break;
// symbols
case Qt::Key_Asterisk:
QApplication::keyboardModifiers() & Qt::KeypadModifier
? pressedButtons.insert(66, "kpasterisk")
: pressedButtons.insert(74, "asterisk");
break;
case Qt::Key_Minus:
QApplication::keyboardModifiers() & Qt::KeypadModifier
? pressedButtons.insert(67, "kpminus")
: pressedButtons.insert(75, "minus");
break;
case Qt::Key_Equal:
QApplication::keyboardModifiers() & Qt::KeypadModifier
? pressedButtons.insert(68, "kpequals")
: pressedButtons.insert(76, "equals");
break;
case Qt::Key_Plus:
QApplication::keyboardModifiers() & Qt::KeypadModifier
? pressedButtons.insert(69, "kpplus")
: pressedButtons.insert(77, "plus");
break;
case Qt::Key_Slash:
QApplication::keyboardModifiers() & Qt::KeypadModifier
? pressedButtons.insert(70, "kpslash")
: pressedButtons.insert(78, "slash");
break;
case Qt::Key_Comma:
QApplication::keyboardModifiers() & Qt::KeypadModifier
? pressedButtons.insert(71, "kpcomma")
: pressedButtons.insert(79, "comma");
break;
case Qt::Key_Period:
QApplication::keyboardModifiers() & Qt::KeypadModifier
? pressedButtons.insert(72, "kpperiod")
: pressedButtons.insert(80, "period");
break;
case Qt::Key_Enter:
QApplication::keyboardModifiers() & Qt::KeypadModifier
? pressedButtons.insert(73, "kpenter")
: pressedButtons.insert(81, "enter");
break;
case Qt::Key_QuoteLeft:
pressedButtons.insert(82, "grave");
break;
case Qt::Key_AsciiTilde:
pressedButtons.insert(83, "tilde");
break;
case Qt::Key_Exclam:
pressedButtons.insert(84, "exclamation");
break;
case Qt::Key_At:
pressedButtons.insert(85, "at");
break;
case Qt::Key_NumberSign:
pressedButtons.insert(86, "hash");
break;
case Qt::Key_Dollar:
pressedButtons.insert(87, "dollar");
break;
case Qt::Key_Percent:
pressedButtons.insert(88, "percent");
break;
case Qt::Key_AsciiCircum:
pressedButtons.insert(89, "caret");
break;
case Qt::Key_Ampersand:
pressedButtons.insert(90, "ampersand");
break;
case Qt::Key_ParenLeft:
pressedButtons.insert(91, "lparen");
break;
case Qt::Key_ParenRight:
pressedButtons.insert(92, "rparen");
break;
case Qt::Key_BracketLeft:
pressedButtons.insert(93, "lbracket");
break;
case Qt::Key_BracketRight:
pressedButtons.insert(94, "rbracket");
break;
case Qt::Key_BraceLeft:
pressedButtons.insert(95, "lbrace");
break;
case Qt::Key_BraceRight:
pressedButtons.insert(96, "rbrace");
break;
case Qt::Key_Underscore:
pressedButtons.insert(97, "underscore");
break;
case Qt::Key_Backslash:
pressedButtons.insert(98, "backslash");
break;
case Qt::Key_Bar:
pressedButtons.insert(99, "pipe");
break;
case Qt::Key_Semicolon:
pressedButtons.insert(100, "semicolon");
break;
case Qt::Key_Colon:
pressedButtons.insert(101, "colon");
break;
case Qt::Key_Apostrophe:
pressedButtons.insert(102, "apostrophe");
break;
case Qt::Key_QuoteDbl:
pressedButtons.insert(103, "quote");
break;
case Qt::Key_Less:
pressedButtons.insert(104, "less");
break;
case Qt::Key_Greater:
pressedButtons.insert(105, "greater");
break;
case Qt::Key_Question:
pressedButtons.insert(106, "question");
break;
// special keys
case Qt::Key_Print:
pressedButtons.insert(107, "printscreen");
break;
case Qt::Key_ScrollLock:
pressedButtons.insert(108, "scrolllock");
break;
case Qt::Key_Pause:
pressedButtons.insert(109, "pausebreak");
break;
case Qt::Key_Backspace:
pressedButtons.insert(110, "backspace");
break;
case Qt::Key_Insert:
pressedButtons.insert(111, "insert");
break;
case Qt::Key_Delete:
pressedButtons.insert(112, "delete");
break;
case Qt::Key_Home:
pressedButtons.insert(113, "home");
break;
case Qt::Key_End:
pressedButtons.insert(114, "end");
break;
case Qt::Key_PageUp:
pressedButtons.insert(115, "pgup");
break;
case Qt::Key_PageDown:
pressedButtons.insert(116, "pgdown");
break;
case Qt::Key_Tab:
pressedButtons.insert(117, "tab");
break;
case Qt::Key_CapsLock:
pressedButtons.insert(118, "capslock");
break;
case Qt::Key_Return:
pressedButtons.insert(119, "enter");
break;
case Qt::Key_Space:
pressedButtons.insert(120, "space");
break;
case Qt::Key_Up:
pressedButtons.insert(121, "up");
break;
case Qt::Key_Down:
pressedButtons.insert(122, "down");
break;
case Qt::Key_Left:
pressedButtons.insert(123, "left");
break;
case Qt::Key_Right:
pressedButtons.insert(124, "right");
break;
default:
break;
}
return true;
}
}
}
if (event->type() == QEvent::KeyPress && EnableKBMapping) {
CheckMapping(MappingButton);
return true;
}
return QDialog::eventFilter(obj, event);
}
void hotkeys::processSDLEvents(int Type, int Input, int Value) {
if (EnableButtonMapping) {
void Hotkeys::processSDLEvents(int Type, int Input, int Value) {
if (EnablePadMapping) {
if (pressedButtons.size() >= 3) {
return;
@@ -330,7 +859,7 @@ void hotkeys::processSDLEvents(int Type, int Input, int Value) {
L2Pressed = true;
} else if (Value < 5000) {
if (L2Pressed && !R2Pressed)
emit PushGamepadEvent();
CheckMapping(MappingButton);
}
break;
case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER:
@@ -339,7 +868,7 @@ void hotkeys::processSDLEvents(int Type, int Input, int Value) {
R2Pressed = true;
} else if (Value < 5000) {
if (R2Pressed && !L2Pressed)
emit PushGamepadEvent();
CheckMapping(MappingButton);
}
break;
default:
@@ -348,7 +877,7 @@ void hotkeys::processSDLEvents(int Type, int Input, int Value) {
}
if (Type == SDL_EVENT_GAMEPAD_BUTTON_UP)
emit PushGamepadEvent();
CheckMapping(MappingButton);
}
if (Type == SDL_EVENT_GAMEPAD_ADDED || SDL_EVENT_GAMEPAD_REMOVED) {
@@ -356,7 +885,7 @@ void hotkeys::processSDLEvents(int Type, int Input, int Value) {
}
}
void hotkeys::pollSDLEvents() {
void Hotkeys::pollSDLEvents() {
SDL_Event event;
while (SdlEventWrapper::Wrapper::wrapperActive) {
@@ -372,14 +901,15 @@ void hotkeys::pollSDLEvents() {
}
}
void hotkeys::Cleanup() {
void Hotkeys::Cleanup() {
SdlEventWrapper::Wrapper::wrapperActive = false;
if (h_gamepad) {
SDL_CloseGamepad(h_gamepad);
h_gamepad = nullptr;
}
SDL_free(h_gamepads);
if (h_gamepads)
SDL_free(h_gamepads);
if (!GameRunning) {
SDL_Event quitLoop{};
@@ -397,4 +927,4 @@ void hotkeys::Cleanup() {
}
}
hotkeys::~hotkeys() {}
Hotkeys::~Hotkeys() {}

View File

@@ -3,40 +3,50 @@
#include <QDialog>
#include <QFuture>
#include <QTimer>
#include <SDL3/SDL_gamepad.h>
#include "sdl_event_wrapper.h"
#ifdef _WIN32
#define LCTRL_KEY 29
#define LALT_KEY 56
#define LSHIFT_KEY 42
#else
#define LCTRL_KEY 37
#define LALT_KEY 64
#define LSHIFT_KEY 50
#endif
namespace Ui {
class hotkeys;
class Hotkeys;
}
class hotkeys : public QDialog {
class Hotkeys : public QDialog {
Q_OBJECT
public:
explicit hotkeys(bool GameRunning, QWidget* parent = nullptr);
~hotkeys();
explicit Hotkeys(bool GameRunning, QWidget* parent = nullptr);
~Hotkeys();
signals:
void PushGamepadEvent();
private Q_SLOTS:
void processSDLEvents(int Type, int Input, int Value);
void StartTimer(QPushButton*& button, bool isPad);
void SaveHotkeys(bool CloseOnSave);
void SetDefault();
private:
bool eventFilter(QObject* obj, QEvent* event) override;
void CheckMapping(QPushButton*& button);
void StartTimer(QPushButton*& button, bool isButton);
void DisableMappingButtons();
void EnableMappingButtons();
void SaveHotkeys(bool CloseOnSave);
void LoadHotkeys();
void processSDLEvents(int Type, int Input, int Value);
void pollSDLEvents();
void CheckGamePad();
void SetMapping(QString input);
void Cleanup();
bool GameRunning;
bool EnableButtonMapping = false;
bool EnablePadMapping = false;
bool EnableKBMapping = false;
bool MappingCompleted = false;
bool L2Pressed = false;
bool R2Pressed = false;
@@ -50,13 +60,29 @@ private:
// use QMap instead of QSet to maintain order of inserted strings
QMap<int, QString> pressedButtons;
QList<QPushButton*> ButtonsList;
QList<QPushButton*> PadButtonsList;
QList<QPushButton*> KBButtonsList;
QFuture<void> Polling;
Ui::hotkeys* ui;
Ui::Hotkeys* ui;
const std::vector<std::string> ControllerInputs = {
"cross", "circle", "square", "triangle", "l1",
"r1", "l2", "r2", "l3",
"r3", "options", "pad_up",
"pad_down",
"pad_left", "pad_right", "axis_left_x", "axis_left_y", "axis_right_x",
"axis_right_y", "back"};
protected:
void closeEvent(QCloseEvent* event) override {
Cleanup();
}
void accept() override {
// Blank override to prevent quitting when save button pressed
}
};

View File

@@ -2,217 +2,315 @@
<!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later -->
<ui version="4.0">
<class>hotkeys</class>
<widget class="QDialog" name="hotkeys">
<class>Hotkeys</class>
<widget class="QDialog" name="Hotkeys">
<property name="windowModality">
<enum>Qt::WindowModality::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>849</width>
<height>496</height>
<width>987</width>
<height>549</height>
</rect>
</property>
<property name="windowTitle">
<string>Customize Hotkeys</string>
</property>
<widget class="QDialogButtonBox" name="buttonBox">
<widget class="QScrollArea" name="scrollArea">
<property name="geometry">
<rect>
<x>750</x>
<y>200</y>
<width>81</width>
<height>81</height>
<x>3</x>
<y>2</y>
<width>981</width>
<height>541</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
<property name="widgetResizable">
<bool>true</bool>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Apply|QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Save</set>
</property>
</widget>
<widget class="QWidget" name="verticalLayoutWidget">
<property name="geometry">
<rect>
<x>30</x>
<y>10</y>
<width>681</width>
<height>231</height>
</rect>
</property>
<layout class="QVBoxLayout" name="controllerLayout">
<item>
<widget class="QLabel" name="controllerLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>19</pointsize>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Controller Hotkeys</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayoutPad1">
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>979</width>
<height>539</height>
</rect>
</property>
<widget class="QWidget" name="verticalLayoutWidget_3">
<property name="geometry">
<rect>
<x>3</x>
<y>7</y>
<width>971</width>
<height>532</height>
</rect>
</property>
<layout class="QVBoxLayout" name="HotkeysLayout">
<item>
<widget class="QGroupBox" name="fpsGroupbox">
<property name="title">
<string>Show FPS Counter</string>
<widget class="QLabel" name="controllerLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>19</pointsize>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Controller Hotkeys</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPushButton" name="fpsButtonPad">
<property name="focusPolicy">
<enum>Qt::FocusPolicy::NoFocus</enum>
</property>
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="stopGroupbox">
<property name="title">
<string>Stop Emulator</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QPushButton" name="quitButtonPad">
<property name="focusPolicy">
<enum>Qt::FocusPolicy::NoFocus</enum>
</property>
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayoutPad2">
<item>
<widget class="QGroupBox" name="fullscreenGroupbox">
<property name="title">
<string>Toggle Fullscreen</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QPushButton" name="fullscreenButtonPad">
<property name="focusPolicy">
<enum>Qt::FocusPolicy::NoFocus</enum>
</property>
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="pauseGroupbox">
<property name="title">
<string>Toggle Pause</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QPushButton" name="pauseButtonPad">
<property name="focusPolicy">
<enum>Qt::FocusPolicy::NoFocus</enum>
</property>
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="verticalLayoutWidget_2">
<property name="geometry">
<rect>
<x>30</x>
<y>250</y>
<width>681</width>
<height>191</height>
</rect>
</property>
<layout class="QVBoxLayout" name="keyboardLayout">
<item>
<widget class="QLabel" name="keyboardLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>19</pointsize>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Keyboard Hotkeys</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="KBLabelsLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayoutKB1">
<layout class="QHBoxLayout" name="horizontalLayoutPad1">
<item>
<widget class="QLabel" name="fpsLabelKB">
<property name="font">
<font>
<pointsize>11</pointsize>
</font>
<widget class="QGroupBox" name="fpsGroupboxPad">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::Shape::Box</enum>
</property>
<property name="text">
<string>Show Fps Counter: F10</string>
<property name="title">
<string>Show FPS Counter</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPushButton" name="fpsButtonPad">
<property name="focusPolicy">
<enum>Qt::FocusPolicy::NoFocus</enum>
</property>
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="stopLabelKB">
<property name="font">
<font>
<pointsize>11</pointsize>
</font>
<widget class="QGroupBox" name="quitGroupboxKB_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::Shape::Box</enum>
<property name="title">
<string>Quit Emulation</string>
</property>
<property name="text">
<string>Stop Emulator: n/a</string>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QPushButton" name="quitButtonPad">
<property name="focusPolicy">
<enum>Qt::FocusPolicy::NoFocus</enum>
</property>
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="reloadMappingsBoxPad">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Reload Button Mappings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QPushButton" name="reloadButtonPad">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayoutPad2">
<item>
<widget class="QGroupBox" name="fullscreenGroupboxPad">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Toggle Fullscreen</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QPushButton" name="fullscreenButtonPad">
<property name="focusPolicy">
<enum>Qt::FocusPolicy::NoFocus</enum>
</property>
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="pauseGroupboxPad">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Toggle Pause</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QPushButton" name="pauseButtonPad">
<property name="focusPolicy">
<enum>Qt::FocusPolicy::NoFocus</enum>
</property>
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="keyboardLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>19</pointsize>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Keyboard Hotkeys</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayoutKB1">
<item>
<widget class="QGroupBox" name="fpsGroupboxKB">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Show FPS Counter</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QPushButton" name="fpsButtonKB">
<property name="focusPolicy">
<enum>Qt::FocusPolicy::NoFocus</enum>
</property>
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="quitGroupboxKB">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Quit Emulation</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
<widget class="QPushButton" name="quitButtonKB">
<property name="focusPolicy">
<enum>Qt::FocusPolicy::NoFocus</enum>
</property>
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="reloadMappingsBoxKB">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Reload Button Mappings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_11">
<item>
<widget class="QPushButton" name="reloadButtonKB">
<property name="focusPolicy">
<enum>Qt::FocusPolicy::NoFocus</enum>
</property>
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
@@ -220,59 +318,197 @@
<item>
<layout class="QHBoxLayout" name="horizontalLayoutKB2">
<item>
<widget class="QLabel" name="fullscreenLabelKB">
<property name="font">
<font>
<pointsize>11</pointsize>
</font>
<widget class="QGroupBox" name="fullscreenGroupboxKB">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::Shape::Box</enum>
</property>
<property name="text">
<string>Toggle Fullscreen: F11</string>
<property name="title">
<string>Toggle Fullscreen</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QPushButton" name="fullscreenButtonKB">
<property name="focusPolicy">
<enum>Qt::FocusPolicy::NoFocus</enum>
</property>
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="pauseLabelKB">
<property name="font">
<font>
<pointsize>11</pointsize>
</font>
<widget class="QGroupBox" name="pauseGroupboxKB">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::Shape::Box</enum>
<property name="title">
<string>Toggle Pause</string>
</property>
<property name="text">
<string>Toggle Pause: F9</string>
<layout class="QVBoxLayout" name="verticalLayout_10">
<item>
<widget class="QPushButton" name="pauseButtonKB">
<property name="focusPolicy">
<enum>Qt::FocusPolicy::NoFocus</enum>
</property>
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="renderdocGroupbox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Renderdoc Capture (for debugging only)</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_12">
<item>
<widget class="QPushButton" name="renderdocButton">
<property name="focusPolicy">
<enum>Qt::FocusPolicy::NoFocus</enum>
</property>
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QGroupBox" name="mouseJoystickGroupbox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Toggle Mouse to Joystick Emulation</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_13">
<item>
<widget class="QPushButton" name="mouseJoystickButton">
<property name="focusPolicy">
<enum>Qt::FocusPolicy::NoFocus</enum>
</property>
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="mouseGyroGroupbox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Toggle Mouse to Gyro Emulation</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_14">
<item>
<widget class="QPushButton" name="mouseGyroButton">
<property name="focusPolicy">
<enum>Qt::FocusPolicy::NoFocus</enum>
</property>
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="tipLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>12</pointsize>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Tip: Up to three simultaneous inputs can be assigned for each hotkey</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Apply|QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::RestoreDefaults|QDialogButtonBox::StandardButton::Save</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QLabel" name="tipLabel">
<property name="geometry">
<rect>
<x>50</x>
<y>450</y>
<width>631</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>12</pointsize>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Tip: Up to three inputs can be assigned for each function</string>
</property>
</widget>
</widget>
</widget>
</widget>
<resources/>
@@ -280,7 +516,7 @@
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>hotkeys</receiver>
<receiver>Hotkeys</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
@@ -296,7 +532,7 @@
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>hotkeys</receiver>
<receiver>Hotkeys</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">

View File

@@ -85,7 +85,7 @@ EditorDialog::EditorDialog(QWidget* parent) : QDialog(parent) {
void EditorDialog::loadFile(QString game) {
const auto config_file = Config::GetFoolproofKbmConfigFile(game.toStdString());
const auto config_file = Config::GetFoolproofInputConfigFile(game.toStdString());
QFile file(config_file);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
@@ -100,7 +100,7 @@ void EditorDialog::loadFile(QString game) {
void EditorDialog::saveFile(QString game) {
const auto config_file = Config::GetFoolproofKbmConfigFile(game.toStdString());
const auto config_file = Config::GetFoolproofInputConfigFile(game.toStdString());
QFile file(config_file);
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
@@ -195,10 +195,10 @@ void EditorDialog::onResetToDefaultClicked() {
if (reply == QMessageBox::Yes) {
if (default_default) {
const auto default_file = Config::GetFoolproofKbmConfigFile("default");
const auto default_file = Config::GetFoolproofInputConfigFile("default");
std::filesystem::remove(default_file);
}
const auto config_file = Config::GetFoolproofKbmConfigFile("default");
const auto config_file = Config::GetFoolproofInputConfigFile("default");
QFile file(config_file);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {

View File

@@ -254,7 +254,7 @@ void KBMSettings::SaveKBMConfig(bool close_on_save) {
lines.push_back(output_string + " = " + input_string);
lines.push_back("");
const auto config_file = Config::GetFoolproofKbmConfigFile(config_id);
const auto config_file = Config::GetFoolproofInputConfigFile(config_id);
std::fstream file(config_file);
int lineCount = 0;
std::string line;
@@ -293,7 +293,7 @@ void KBMSettings::SaveKBMConfig(bool close_on_save) {
}
if (controllerInputdetected || output_string == "analog_deadzone" ||
output_string == "override_controller_color") {
output_string == "override_controller_color" || output_string.contains("hotkey")) {
lines.push_back(line);
}
}
@@ -396,7 +396,7 @@ void KBMSettings::SetDefault() {
}
void KBMSettings::SetUIValuestoMappings(std::string config_id) {
const auto config_file = Config::GetFoolproofKbmConfigFile(config_id);
const auto config_file = Config::GetFoolproofInputConfigFile(config_id);
std::ifstream file(config_file);
int lineCount = 0;

View File

@@ -114,11 +114,9 @@ HelpDialog::HelpDialog(bool* open_flag, QWidget* parent) : QDialog(parent) {
// Helper functions that store the text contents for each tab inb the HelpDialog menu
QString HelpDialog::quickstart() {
return R"(
The keyboard and controller remapping backend, GUI, and documentation have been written by kalaposfos.
In this section, you will find information about the project and its features, as well as help setting up your ideal setup.
To view the config file's syntax, check out the Syntax tab, for keybind names, visit Normal Keybinds and Special Bindings, and if you are here to view emulator-wide keybinds, you can find it in the FAQ section.
This project began because I disliked the original, unchangeable keybinds. Rather than waiting for someone else to do it, I implemented this myself. From the default keybinds, you can clearly tell this was a project built for Bloodborne, but obviously, you can make adjustments however you like.)";
)";
}
QString HelpDialog::syntax() {
@@ -157,6 +155,8 @@ Keyboard:
'a', 'b', ..., 'z'
Numbers:
'0', '1', ..., '9'
F keys:
'f1', 'f2', ..., 'f12'
Keypad:
'kp0', 'kp1', ..., 'kp9',
'kpperiod', 'kpcomma', 'kpslash', 'kpasterisk', 'kpminus', 'kpplus', 'kpequals', 'kpenter'
@@ -186,7 +186,7 @@ Controller:
'triangle', 'circle', 'cross', 'square', 'l1', 'l3',
'options', touchpad', 'up', 'down', 'left', 'right'
Input-only:
'lpaddle_low', 'lpaddle_high'
'lpaddle_low', 'lpaddle_high', 'back' = 'share' (Xbox and PS4 names)
Output-only:
'touchpad_left', 'touchpad_center', 'touchpad_right'
Axes if you bind them to a button input:
@@ -196,9 +196,6 @@ Controller:
Axes if you bind them to another axis input:
'axis_left_x', 'axis_left_y', 'axis_right_x', 'axis_right_y',
'l2'
Invalid Inputs:
'F1-F12' are reserved for emulator-wide keybinds, and cannot be bound to controller inputs.
Symbols (expanded):
` 'grave'
@@ -235,7 +232,13 @@ Symbols (expanded):
QString HelpDialog::special() {
return R"(
There are some extra bindings you can put in the config file that don't correspond to a controller input but something else.
You can find these here, with detailed comments, examples, and suggestions for most of them.
You can find these here, with comments, examples, and suggestions for most of them.
Emulator hotkeys:
These are regarded as normal bindings, but they are put in a special config named global.ini by default, which is a config that is always loaded alongside the main config (If you want to, you can use this to set some common bindings that'll be in effect for every game, then put only the game specific bindings in their respective files). This doesn't mean you can't add them to the normal configs, you can absolutely make game specific emulator hokeys as well.
'hotkey_pause', 'hotkey_fullscreen', 'hotkey_show_fps',
'hotkey_quit', 'hotkey_reload_inputs', 'hotkey_toggle_mouse_to_joystick',
'hotkey_toggle_mouse_to_gyro', 'hotkey_renderdoc_capture'
'leftjoystick_halfmode' and 'rightjoystick_halfmode' = <key>;
These are a pair of input modifiers that change the way keyboard button-bound axes work. By default, those push the joystick to the max in their respective direction, but if their respective 'joystick_halfmode' modifier value is true, they only push it... halfway. With this, you can change from run to walk in games like Bloodborne.
@@ -272,7 +275,7 @@ You can find these here, with detailed comments, examples, and suggestions for m
QString HelpDialog::faq() {
return R"(
Q: What are the emulator-wide keybinds?
A:
A: By default, they're the following:
-F12: Triggers Renderdoc capture
-F11: Toggles fullscreen
-F10: Toggles FPS counter
@@ -281,6 +284,7 @@ A:
-F8: Reparses the config file while in-game
-F7: Toggles mouse capture and mouse input
-F6: Toggles mouse-to-gyro emulation
More info on these can be found in the Special keybinds tab.
Q: How do I switch between mouse and controller joystick input? Why is it even required?
A: Pressing F7 toggles between mouse and controller joystick input. It is required because the program polls the mouse input, which means it checks mouse movement every frame. If it didn't move, the code would manually set the emulator's virtual controller to 0 (to the center), even if other input devices would update it.

View File

@@ -21,6 +21,7 @@
#include "control_settings.h"
#include "game_install_dialog.h"
#include "hotkeys.h"
#include "input/input_handler.h"
#include "kbm_gui.h"
#include "main_window.h"
#include "settings_dialog.h"
@@ -497,7 +498,7 @@ void MainWindow::CreateConnects() {
});
connect(ui->configureHotkeys, &QAction::triggered, this, [this]() {
auto hotkeyDialog = new hotkeys(isGameRunning, this);
auto hotkeyDialog = new Hotkeys(isGameRunning, this);
hotkeyDialog->exec();
});

View File

@@ -352,7 +352,6 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_
Input::ControllerOutput::SetControllerOutputController(controller);
Input::ControllerOutput::LinkJoystickAxes();
Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial()));
Input::LoadHotkeyInputs();
if (Config::getBackgroundControllerInput()) {
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
@@ -432,6 +431,9 @@ void WindowSDL::WaitEvent() {
case SDL_EVENT_QUIT:
is_open = false;
break;
case SDL_EVENT_QUIT_DIALOG:
Overlay::ToggleQuitWindow();
break;
case SDL_EVENT_TOGGLE_FULLSCREEN: {
if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) {
SDL_SetWindowFullscreen(window, 0);
@@ -441,19 +443,34 @@ void WindowSDL::WaitEvent() {
break;
}
case SDL_EVENT_TOGGLE_PAUSE:
SDL_Log("Received SDL_EVENT_TOGGLE_PAUSE");
if (DebugState.IsGuestThreadsPaused()) {
SDL_Log("Game Resumed");
LOG_INFO(Frontend, "Game Resumed");
DebugState.ResumeGuestThreads();
} else {
SDL_Log("Game Paused");
LOG_INFO(Frontend, "Game Paused");
DebugState.PauseGuestThreads();
}
break;
case SDL_EVENT_CHANGE_CONTROLLER:
controller->GetEngine()->Init();
break;
case SDL_EVENT_TOGGLE_SIMPLE_FPS:
Overlay::ToggleSimpleFps();
break;
case SDL_EVENT_RELOAD_INPUTS:
Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial()));
break;
case SDL_EVENT_MOUSE_TO_JOYSTICK:
SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(),
Input::ToggleMouseModeTo(Input::MouseMode::Joystick));
break;
case SDL_EVENT_MOUSE_TO_GYRO:
SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(),
Input::ToggleMouseModeTo(Input::MouseMode::Gyro));
break;
case SDL_EVENT_RDOC_CAPTURE:
VideoCore::TriggerCapture();
break;
default:
break;
}
@@ -505,40 +522,6 @@ void WindowSDL::OnKeyboardMouseInput(const SDL_Event* event) {
event->type == SDL_EVENT_MOUSE_WHEEL;
Input::InputEvent input_event = Input::InputBinding::GetInputEventFromSDLEvent(*event);
// Handle window controls outside of the input maps
if (event->type == SDL_EVENT_KEY_DOWN) {
u32 input_id = input_event.input.sdl_id;
// Reparse kbm inputs
if (input_id == SDLK_F8) {
Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial()));
return;
}
// Toggle mouse capture and joystick input emulation
else if (input_id == SDLK_F7) {
SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(),
Input::ToggleMouseModeTo(Input::MouseMode::Joystick));
return;
}
// Toggle mouse capture and gyro input emulation
else if (input_id == SDLK_F6) {
SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(),
Input::ToggleMouseModeTo(Input::MouseMode::Gyro));
return;
}
// Toggle fullscreen
else if (input_id == SDLK_F11) {
SDL_WindowFlags flag = SDL_GetWindowFlags(window);
bool is_fullscreen = flag & SDL_WINDOW_FULLSCREEN;
SDL_SetWindowFullscreen(window, !is_fullscreen);
return;
}
// Trigger rdoc capture
else if (input_id == SDLK_F12) {
VideoCore::TriggerCapture();
return;
}
}
// if it's a wheel event, make a timer that turns it off after a set time
if (event->type == SDL_EVENT_MOUSE_WHEEL) {
const SDL_Event* copy = new SDL_Event(*event);
@@ -571,54 +554,9 @@ void WindowSDL::OnGamepadEvent(const SDL_Event* event) {
bool inputs_changed = Input::UpdatePressedKeys(input_event);
if (inputs_changed) {
// process hotkeys
if (event->type == SDL_EVENT_GAMEPAD_BUTTON_UP) {
process_hotkeys = true;
} else if (event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
if (event->gbutton.timestamp)
CheckHotkeys();
} else if (event->type == SDL_EVENT_GAMEPAD_AXIS_MOTION) {
if (event->gaxis.axis == SDL_GAMEPAD_AXIS_LEFT_TRIGGER ||
event->gaxis.axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
if (event->gaxis.value < 5000) {
process_hotkeys = true;
} else if (event->gaxis.value > 16000) {
CheckHotkeys();
}
}
}
// update bindings
Input::ActivateOutputsFromInputs();
}
}
void WindowSDL::CheckHotkeys() {
if (Input::HotkeyInputsPressed(Input::GetHotkeyInputs(Input::HotkeyPad::FullscreenPad))) {
SDL_Event event;
SDL_memset(&event, 0, sizeof(event));
event.type = SDL_EVENT_TOGGLE_FULLSCREEN;
SDL_PushEvent(&event);
process_hotkeys = false;
}
if (Input::HotkeyInputsPressed(Input::GetHotkeyInputs(Input::HotkeyPad::PausePad))) {
SDL_Event event;
SDL_memset(&event, 0, sizeof(event));
event.type = SDL_EVENT_TOGGLE_PAUSE;
SDL_PushEvent(&event);
process_hotkeys = false;
}
if (Input::HotkeyInputsPressed(Input::GetHotkeyInputs(Input::HotkeyPad::SimpleFpsPad))) {
Overlay::ToggleSimpleFps();
process_hotkeys = false;
}
if (Input::HotkeyInputsPressed(Input::GetHotkeyInputs(Input::HotkeyPad::QuitPad))) {
Overlay::ToggleQuitWindow();
process_hotkeys = false;
}
}
} // namespace Frontend

View File

@@ -9,10 +9,6 @@
#include "core/libraries/pad/pad.h"
#include "input/controller.h"
#define SDL_EVENT_TOGGLE_FULLSCREEN (SDL_EVENT_USER + 1)
#define SDL_EVENT_TOGGLE_PAUSE (SDL_EVENT_USER + 2)
#define SDL_EVENT_CHANGE_CONTROLLER (SDL_EVENT_USER + 3)
struct SDL_Window;
struct SDL_Gamepad;
union SDL_Event;
@@ -100,7 +96,6 @@ private:
void OnResize();
void OnKeyboardMouseInput(const SDL_Event* event);
void OnGamepadEvent(const SDL_Event* event);
void CheckHotkeys();
private:
s32 width;
@@ -110,7 +105,6 @@ private:
SDL_Window* window{};
bool is_shown{};
bool is_open{true};
bool process_hotkeys{true};
};
} // namespace Frontend