SDL: Migrate to common host interface

It now supports controllers again. But you have to bind them in Qt.
This commit is contained in:
Connor McLaughlin 2020-04-11 00:00:44 +10:00
parent 6cfad33b8e
commit 156a360d86
7 changed files with 474 additions and 311 deletions

View File

@ -27,7 +27,6 @@ Other features include:
- Direct booting of homebrew executables
- Digital and analog controllers for input (rumble is forwarded to host)
- Qt and SDL frontends for desktop
- Qt frontend has graphical configuration, and controller binding
- Automatic content scanning - game titles/regions are provided by redump.org
## System Requirements
@ -142,26 +141,25 @@ click the button next to button name, and press the key/button you want to use w
**Currently, it is only possible to bind one input to each controller button/axis. Multiple bindings per button are planned for the future.**
## Default keyboard bindings for SDL frontend
Keyboard bindings in the SDL frontend are currently not customizable. For reference:
- **D-Pad:** W/A/S/D or Up/Left/Down/Right
- **Triangle/Square/Circle/Cross:** I/J/L/K or Numpad8/Numpad4/Numpad6/Numpad2
## Bindings for SDL frontend
Keyboard bindings in the SDL frontend are currently not customizable in the frontend itself. You should use the Qt frontend to set up your key/controller bindings first.
## Default bindings
Controller 1:
- **D-Pad:** W/A/S/D
- **Triangle/Square/Circle/Cross:** Numpad8/Numpad4/Numpad6/Numpad2
- **L1/R1:** Q/E
- **L2/L2:** 1/3
- **Start:** Enter
- **Select:** Backspace
Gamepads are automatically detected and supported. Tested with an Xbox One controller.
To access the menus with the controller, press the right stick down and use the D-Pad to navigate, A to select.
## Useful hotkeys for SDL frontend
- **F1-F8:** Quick load/save (hold shift to save)
- **F11:** Toggle fullscreen
Hotkeys:
- **Escape:** Power off console
- **ALT+ENTER:** Toggle fullscreen
- **Tab:** Temporarily disable speed limiter
- **Pause/Break:** Pause/resume emulation
- **Space:** Frame step
- **End:** Toggle software renderer
- **Page Up/Down:** Increase/decrease resolution scale in hardware renderers
- **End:** Toggle software renderer
## Tests
- Passes amidog's CPU and GTE tests in both interpreter and recompiler modes, partial passing of CPX tests

View File

@ -6,6 +6,7 @@ add_executable(duckstation-sdl
opengl_host_display.h
sdl_host_interface.cpp
sdl_host_interface.h
sdl_key_names.h
)
target_include_directories(duckstation-sdl PRIVATE ${SDL2_INCLUDE_DIRS})

View File

@ -63,6 +63,7 @@
<ClInclude Include="imgui_impl_sdl.h" />
<ClInclude Include="opengl_host_display.h" />
<ClInclude Include="sdl_host_interface.h" />
<ClInclude Include="sdl_key_names.h" />
</ItemGroup>
<ItemGroup>
<Manifest Include="duckstation-sdl.manifest" />

View File

@ -12,6 +12,7 @@
<ClInclude Include="sdl_host_interface.h" />
<ClInclude Include="d3d11_host_display.h" />
<ClInclude Include="imgui_impl_sdl.h" />
<ClInclude Include="sdl_key_names.h" />
</ItemGroup>
<ItemGroup>
<Manifest Include="duckstation-sdl.manifest" />

View File

@ -15,6 +15,7 @@
#include "frontend-common/sdl_controller_interface.h"
#include "imgui_impl_sdl.h"
#include "opengl_host_display.h"
#include "sdl_key_names.h"
#include <cinttypes>
#include <cmath>
#include <imgui.h>
@ -190,11 +191,18 @@ bool SDLHostInterface::AcquireHostDisplay()
}
#endif
// Switch to fullscreen if requested.
if (m_settings.start_fullscreen)
SetFullscreen(true);
return true;
}
void SDLHostInterface::ReleaseHostDisplay()
{
if (m_fullscreen)
SetFullscreen(false);
// restore vsync, since we don't want to burn cycles at the menu
m_display->SetVSync(true);
}
@ -217,17 +225,35 @@ std::unique_ptr<AudioStream> SDLHostInterface::CreateAudioStream(AudioBackend ba
}
}
std::unique_ptr<ControllerInterface> SDLHostInterface::CreateControllerInterface()
{
return std::make_unique<SDLControllerInterface>();
}
std::optional<CommonHostInterface::HostKeyCode> SDLHostInterface::GetHostKeyCode(const std::string_view key_code) const
{
const std::optional<u32> code = SDLKeyNames::ParseKeyString(key_code);
if (!code)
return std::nullopt;
return static_cast<HostKeyCode>(*code);
}
void SDLHostInterface::UpdateInputMap()
{
CommonHostInterface::UpdateInputMap(*m_settings_interface.get());
}
void SDLHostInterface::OnSystemCreated()
{
HostInterface::OnSystemCreated();
CommonHostInterface::OnSystemCreated();
UpdateKeyboardControllerMapping();
ClearImGuiFocus();
}
void SDLHostInterface::OnSystemPaused(bool paused)
{
HostInterface::OnSystemPaused(paused);
CommonHostInterface::OnSystemPaused(paused);
if (!paused)
ClearImGuiFocus();
@ -235,14 +261,17 @@ void SDLHostInterface::OnSystemPaused(bool paused)
void SDLHostInterface::OnSystemDestroyed()
{
HostInterface::OnSystemDestroyed();
CommonHostInterface::OnSystemDestroyed();
}
void SDLHostInterface::OnControllerTypeChanged(u32 slot)
void SDLHostInterface::OnRunningGameChanged()
{
HostInterface::OnControllerTypeChanged(slot);
CommonHostInterface::OnRunningGameChanged();
UpdateKeyboardControllerMapping();
if (m_system && !m_system->GetRunningTitle().empty())
SDL_SetWindowTitle(m_window, m_system->GetRunningTitle().c_str());
else
SDL_SetWindowTitle(m_window, "DuckStation");
}
void SDLHostInterface::RunLater(std::function<void()> callback)
@ -256,29 +285,35 @@ void SDLHostInterface::RunLater(std::function<void()> callback)
void SDLHostInterface::SaveSettings()
{
INISettingsInterface si(GetSettingsFileName());
m_settings_copy.Save(si);
m_settings_copy.Save(*m_settings_interface.get());
m_settings_interface->Save();
}
void SDLHostInterface::UpdateSettings()
{
HostInterface::UpdateSettings([this]() { m_settings = m_settings_copy; });
CommonHostInterface::UpdateSettings([this]() { m_settings = m_settings_copy; });
}
void SDLHostInterface::SetFullscreen(bool enabled)
bool SDLHostInterface::IsFullscreen() const
{
return m_fullscreen;
}
bool SDLHostInterface::SetFullscreen(bool enabled)
{
if (m_fullscreen == enabled)
return;
return true;
SDL_SetWindowFullscreen(m_window, enabled ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
// We set the margin only in windowed mode, the menu bar is drawn on top in fullscreen.
// We set the margin only in windowed mode, the menu bar is not drawn fullscreen.
m_display->SetDisplayTopMargin(enabled ? 0 : static_cast<int>(20.0f * ImGui::GetIO().DisplayFramebufferScale.x));
int window_width, window_height;
SDL_GetWindowSize(m_window, &window_width, &window_height);
m_display->WindowResized(window_width, window_height);
m_fullscreen = enabled;
return true;
}
std::unique_ptr<SDLHostInterface> SDLHostInterface::Create()
@ -288,7 +323,7 @@ std::unique_ptr<SDLHostInterface> SDLHostInterface::Create()
bool SDLHostInterface::Initialize()
{
if (!HostInterface::Initialize())
if (!CommonHostInterface::Initialize())
return false;
// Change to the user directory so that all default/relative paths in the config are after this.
@ -309,6 +344,10 @@ bool SDLHostInterface::Initialize()
}
ImGui::NewFrame();
// process events to pick up controllers before updating input map
ProcessEvents();
UpdateInputMap();
return true;
}
@ -325,21 +364,27 @@ void SDLHostInterface::Shutdown()
if (m_window)
DestroySDLWindow();
HostInterface::Shutdown();
CommonHostInterface::Shutdown();
}
void SDLHostInterface::LoadSettings()
{
// Settings need to be loaded prior to creating the window for OpenGL bits.
INISettingsInterface si(GetSettingsFileName());
m_settings_copy.Load(si);
m_settings_interface = std::make_unique<INISettingsInterface>(GetSettingsFileName());
m_settings_copy.Load(*m_settings_interface.get());
m_settings = m_settings_copy;
m_fullscreen = m_settings_copy.start_fullscreen;
}
void SDLHostInterface::ReportError(const char* message)
{
const bool was_fullscreen = IsFullscreen();
if (was_fullscreen)
SetFullscreen(false);
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "DuckStation", message, m_window);
if (was_fullscreen)
SetFullscreen(true);
}
void SDLHostInterface::ReportMessage(const char* message)
@ -349,6 +394,10 @@ void SDLHostInterface::ReportMessage(const char* message)
bool SDLHostInterface::ConfirmMessage(const char* message)
{
const bool was_fullscreen = IsFullscreen();
if (was_fullscreen)
SetFullscreen(false);
SDL_MessageBoxData mbd = {};
mbd.flags = SDL_MESSAGEBOX_INFORMATION;
mbd.window = m_window;
@ -369,13 +418,23 @@ bool SDLHostInterface::ConfirmMessage(const char* message)
int button_id = 0;
SDL_ShowMessageBox(&mbd, &button_id);
return (button_id == 0);
const bool result = (button_id == 0);
if (was_fullscreen)
SetFullscreen(true);
return result;
}
void SDLHostInterface::HandleSDLEvent(const SDL_Event* event)
{
ImGui_ImplSDL2_ProcessEvent(event);
// g_sdl_controller_interface.ProcessSDLEvent(event);
if (m_controller_interface &&
static_cast<SDLControllerInterface*>(m_controller_interface.get())->ProcessSDLEvent(event))
{
return;
}
switch (event->type)
{
@ -400,23 +459,12 @@ void SDLHostInterface::HandleSDLEvent(const SDL_Event* event)
case SDL_KEYDOWN:
case SDL_KEYUP:
{
if (!ImGui::GetIO().WantCaptureKeyboard)
HandleSDLKeyEvent(event);
}
break;
case SDL_CONTROLLERDEVICEADDED:
case SDL_CONTROLLERDEVICEREMOVED:
// g_sdl_controller_interface.SetDefaultBindings();
break;
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
{
if (event->type == SDL_CONTROLLERBUTTONDOWN && event->cbutton.button == SDL_CONTROLLER_BUTTON_RIGHTSTICK)
if (!ImGui::GetIO().WantCaptureKeyboard && event->key.repeat == 0)
{
// focus the menu bar
m_focus_main_menu_bar = true;
const HostKeyCode code = static_cast<HostKeyCode>(static_cast<u32>(event->key.keysym.sym) |
static_cast<u32>(event->key.keysym.mod) << 16);
const bool pressed = (event->type == SDL_KEYDOWN);
HandleHostKeyEvent(code, pressed);
}
}
break;
@ -435,208 +483,24 @@ void SDLHostInterface::HandleSDLEvent(const SDL_Event* event)
}
}
void SDLHostInterface::HandleSDLKeyEvent(const SDL_Event* event)
void SDLHostInterface::ProcessEvents()
{
const bool repeat = event->key.repeat != 0;
if (!repeat && HandleSDLKeyEventForController(event))
return;
const bool pressed = (event->type == SDL_KEYDOWN);
switch (event->key.keysym.scancode)
for (;;)
{
case SDL_SCANCODE_F1:
case SDL_SCANCODE_F2:
case SDL_SCANCODE_F3:
case SDL_SCANCODE_F4:
case SDL_SCANCODE_F5:
case SDL_SCANCODE_F6:
case SDL_SCANCODE_F7:
case SDL_SCANCODE_F8:
{
if (!pressed)
{
const u32 index = event->key.keysym.scancode - SDL_SCANCODE_F1 + 1;
if (event->key.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT))
SaveState(true, index);
else
LoadState(true, index);
}
}
break;
case SDL_SCANCODE_RETURN:
case SDL_SCANCODE_KP_ENTER:
{
if ((event->key.keysym.mod & (KMOD_LALT | KMOD_RALT)) && !pressed)
SetFullscreen(!m_fullscreen);
}
break;
case SDL_SCANCODE_TAB:
{
if (!repeat)
{
m_speed_limiter_temp_disabled = pressed;
UpdateSpeedLimiterState();
}
}
break;
case SDL_SCANCODE_PAUSE:
{
if (pressed)
PauseSystem(!m_paused);
}
break;
case SDL_SCANCODE_SPACE:
{
if (pressed)
DoFrameStep();
}
break;
case SDL_SCANCODE_HOME:
{
if (pressed && !repeat && m_system)
{
m_settings.speed_limiter_enabled = !m_settings.speed_limiter_enabled;
m_settings_copy.speed_limiter_enabled = m_settings.speed_limiter_enabled;
UpdateSpeedLimiterState();
AddOSDMessage(m_settings.speed_limiter_enabled ? "Speed limiter enabled." : "Speed limiter disabled.");
}
}
break;
case SDL_SCANCODE_END:
{
if (pressed)
ToggleSoftwareRendering();
}
break;
case SDL_SCANCODE_PAGEUP:
case SDL_SCANCODE_PAGEDOWN:
{
if (pressed)
ModifyResolutionScale(event->key.keysym.scancode == SDL_SCANCODE_PAGEUP ? 1 : -1);
}
break;
}
}
void SDLHostInterface::UpdateKeyboardControllerMapping()
{
m_keyboard_button_mapping.fill(-1);
const Controller* controller = m_system ? m_system->GetController(0) : nullptr;
if (controller)
{
#define SET_BUTTON_MAP(action, name) \
m_keyboard_button_mapping[static_cast<int>(action)] = controller->GetButtonCodeByName(name).value_or(-1)
SET_BUTTON_MAP(KeyboardControllerAction::Up, "Up");
SET_BUTTON_MAP(KeyboardControllerAction::Down, "Down");
SET_BUTTON_MAP(KeyboardControllerAction::Left, "Left");
SET_BUTTON_MAP(KeyboardControllerAction::Right, "Right");
SET_BUTTON_MAP(KeyboardControllerAction::Triangle, "Triangle");
SET_BUTTON_MAP(KeyboardControllerAction::Cross, "Cross");
SET_BUTTON_MAP(KeyboardControllerAction::Square, "Square");
SET_BUTTON_MAP(KeyboardControllerAction::Circle, "Circle");
SET_BUTTON_MAP(KeyboardControllerAction::L1, "L1");
SET_BUTTON_MAP(KeyboardControllerAction::R1, "R1");
SET_BUTTON_MAP(KeyboardControllerAction::L2, "L2");
SET_BUTTON_MAP(KeyboardControllerAction::R2, "R2");
SET_BUTTON_MAP(KeyboardControllerAction::Start, "Start");
SET_BUTTON_MAP(KeyboardControllerAction::Select, "Select");
#undef SET_BUTTON_MAP
}
}
bool SDLHostInterface::HandleSDLKeyEventForController(const SDL_Event* event)
{
const bool pressed = (event->type == SDL_KEYDOWN);
Controller* controller;
#define DO_ACTION(action) \
if ((controller = m_system ? m_system->GetController(0) : nullptr) != nullptr && \
m_keyboard_button_mapping[static_cast<int>(action)]) \
{ \
controller->SetButtonState(m_keyboard_button_mapping[static_cast<int>(action)], pressed); \
}
switch (event->key.keysym.scancode)
{
case SDL_SCANCODE_KP_8:
case SDL_SCANCODE_I:
DO_ACTION(KeyboardControllerAction::Triangle);
return true;
case SDL_SCANCODE_KP_2:
case SDL_SCANCODE_K:
DO_ACTION(KeyboardControllerAction::Cross);
return true;
case SDL_SCANCODE_KP_4:
case SDL_SCANCODE_J:
DO_ACTION(KeyboardControllerAction::Square);
return true;
case SDL_SCANCODE_KP_6:
case SDL_SCANCODE_L:
DO_ACTION(KeyboardControllerAction::Circle);
return true;
case SDL_SCANCODE_W:
case SDL_SCANCODE_UP:
DO_ACTION(KeyboardControllerAction::Up);
return true;
case SDL_SCANCODE_S:
case SDL_SCANCODE_DOWN:
DO_ACTION(KeyboardControllerAction::Down);
return true;
case SDL_SCANCODE_A:
case SDL_SCANCODE_LEFT:
DO_ACTION(KeyboardControllerAction::Left);
return true;
case SDL_SCANCODE_D:
case SDL_SCANCODE_RIGHT:
DO_ACTION(KeyboardControllerAction::Right);
return true;
case SDL_SCANCODE_Q:
DO_ACTION(KeyboardControllerAction::L1);
return true;
case SDL_SCANCODE_E:
DO_ACTION(KeyboardControllerAction::R1);
return true;
case SDL_SCANCODE_1:
DO_ACTION(KeyboardControllerAction::L2);
return true;
case SDL_SCANCODE_3:
DO_ACTION(KeyboardControllerAction::R2);
return true;
case SDL_SCANCODE_RETURN:
DO_ACTION(KeyboardControllerAction::Start);
return true;
case SDL_SCANCODE_BACKSPACE:
DO_ACTION(KeyboardControllerAction::Select);
return true;
default:
SDL_Event ev;
if (SDL_PollEvent(&ev))
HandleSDLEvent(&ev);
else
break;
}
#undef DO_ACTION
return false;
}
void SDLHostInterface::DrawImGuiWindows()
{
DrawMainMenuBar();
if (!m_fullscreen)
DrawMainMenuBar();
HostInterface::DrawImGuiWindows();
CommonHostInterface::DrawImGuiWindows();
if (!m_system)
DrawPoweredOffWindow();
@ -652,26 +516,11 @@ void SDLHostInterface::DrawImGuiWindows()
void SDLHostInterface::DrawMainMenuBar()
{
// We skip drawing the menu bar if we're in fullscreen and the mouse pointer isn't in range.
const float SHOW_THRESHOLD = 20.0f;
if (m_fullscreen && !m_system &&
ImGui::GetIO().MousePos.y >= (SHOW_THRESHOLD * ImGui::GetIO().DisplayFramebufferScale.x) &&
!ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow))
{
return;
}
if (!ImGui::BeginMainMenuBar())
return;
const bool system_enabled = static_cast<bool>(m_system);
if (m_focus_main_menu_bar)
{
ImGui::OpenPopup("System");
m_focus_main_menu_bar = false;
}
if (ImGui::BeginMenu("System"))
{
if (ImGui::MenuItem("Start Disc", nullptr, false, !system_enabled))
@ -1441,14 +1290,7 @@ void SDLHostInterface::Run()
{
while (!m_quit_request)
{
for (;;)
{
SDL_Event ev;
if (SDL_PollEvent(&ev))
HandleSDLEvent(&ev);
else
break;
}
ProcessEvents();
if (m_system && !m_paused)
{
@ -1460,8 +1302,6 @@ void SDLHostInterface::Run()
}
}
// g_sdl_controller_interface.UpdateControllerRumble();
// rendering
{
DrawImGuiWindows();

View File

@ -3,7 +3,7 @@
#include "common/gl/texture.h"
#include "core/host_display.h"
#include "core/host_interface.h"
#include "frontend-common/sdl_controller_interface.h"
#include "frontend-common/common_host_interface.h"
#include <SDL.h>
#include <array>
#include <deque>
@ -15,9 +15,9 @@
class System;
class AudioStream;
class Controller;
class INISettingsInterface;
class SDLHostInterface final : public HostInterface
class SDLHostInterface final : public CommonHostInterface
{
public:
SDLHostInterface();
@ -40,42 +40,17 @@ protected:
bool AcquireHostDisplay() override;
void ReleaseHostDisplay() override;
std::unique_ptr<AudioStream> CreateAudioStream(AudioBackend backend) override;
std::unique_ptr<ControllerInterface> CreateControllerInterface() override;
void OnSystemCreated() override;
void OnSystemPaused(bool paused) override;
void OnSystemDestroyed() override;
void OnControllerTypeChanged(u32 slot) override;
void OnRunningGameChanged() override;
std::optional<HostKeyCode> GetHostKeyCode(const std::string_view key_code) const override;
void UpdateInputMap() override;
private:
enum class KeyboardControllerAction
{
Up,
Down,
Left,
Right,
Triangle,
Cross,
Square,
Circle,
L1,
R1,
L2,
R2,
Start,
Select,
Count
};
using KeyboardControllerActionMap = std::array<s32, static_cast<int>(KeyboardControllerAction::Count)>;
struct ControllerData
{
SDL_GameController* controller;
SDL_Haptic* haptic;
u32 controller_index;
float last_rumble_strength;
};
bool HasSystem() const { return static_cast<bool>(m_system); }
#ifdef WIN32
@ -99,8 +74,8 @@ private:
void SaveSettings();
void UpdateSettings();
bool IsFullscreen() const { return m_fullscreen; }
void SetFullscreen(bool enabled);
bool IsFullscreen() const override;
bool SetFullscreen(bool enabled) override;
// We only pass mouse input through if it's grabbed
void DrawImGuiWindows() override;
@ -109,10 +84,7 @@ private:
void DoFrameStep();
void HandleSDLEvent(const SDL_Event* event);
void HandleSDLKeyEvent(const SDL_Event* event);
void UpdateKeyboardControllerMapping();
bool HandleSDLKeyEventForController(const SDL_Event* event);
void ProcessEvents();
void DrawMainMenuBar();
void DrawQuickSettingsMenu();
@ -125,15 +97,12 @@ private:
SDL_Window* m_window = nullptr;
std::unique_ptr<HostDisplayTexture> m_app_icon_texture;
KeyboardControllerActionMap m_keyboard_button_mapping;
std::unique_ptr<INISettingsInterface> m_settings_interface;
u32 m_run_later_event_id = 0;
bool m_fullscreen = false;
bool m_quit_request = false;
bool m_frame_step_request = false;
bool m_focus_main_menu_bar = false;
bool m_settings_window_open = false;
bool m_about_window_open = false;

View File

@ -0,0 +1,353 @@
#pragma once
#include "common/string.h"
#include "common/types.h"
#include <SDL.h>
#include <array>
#include <cstring>
#include <map>
#include <optional>
#include <string_view>
namespace SDLKeyNames {
static const std::map<int, const char*> s_sdl_key_names = {{SDLK_RETURN, "Return"},
{SDLK_ESCAPE, "Escape"},
{SDLK_BACKSPACE, "Backspace"},
{SDLK_TAB, "Tab"},
{SDLK_SPACE, "Space"},
{SDLK_EXCLAIM, "Exclam"},
{SDLK_QUOTEDBL, "QuoteDbl"},
{SDLK_HASH, "Hash"},
{SDLK_PERCENT, "Percent"},
{SDLK_DOLLAR, "Dollar"},
{SDLK_AMPERSAND, "Ampersand"},
{SDLK_QUOTE, "Apostrophe"},
{SDLK_LEFTPAREN, "ParenLeft"},
{SDLK_RIGHTPAREN, "ParenRight"},
{SDLK_ASTERISK, "Asterisk"},
{SDLK_PLUS, "PLus"},
{SDLK_COMMA, "Comma"},
{SDLK_MINUS, "Minus"},
{SDLK_PERIOD, "Period"},
{SDLK_SLASH, "Slash"},
{SDLK_0, "0"},
{SDLK_1, "1"},
{SDLK_2, "2"},
{SDLK_3, "3"},
{SDLK_4, "4"},
{SDLK_5, "5"},
{SDLK_6, "6"},
{SDLK_7, "7"},
{SDLK_8, "8"},
{SDLK_9, "9"},
{SDLK_COLON, "Colon"},
{SDLK_SEMICOLON, "Semcolon"},
{SDLK_LESS, "Less"},
{SDLK_EQUALS, "Equal"},
{SDLK_GREATER, "Greater"},
{SDLK_QUESTION, "Question"},
{SDLK_AT, "AT"},
{SDLK_LEFTBRACKET, "BracketLeft"},
{SDLK_BACKSLASH, "Backslash"},
{SDLK_RIGHTBRACKET, "BracketRight"},
{SDLK_CARET, "Caret"},
{SDLK_UNDERSCORE, "Underscore"},
{SDLK_BACKQUOTE, "Backquote"},
{SDLK_a, "A"},
{SDLK_b, "B"},
{SDLK_c, "C"},
{SDLK_d, "D"},
{SDLK_e, "E"},
{SDLK_f, "F"},
{SDLK_g, "G"},
{SDLK_h, "H"},
{SDLK_i, "I"},
{SDLK_j, "J"},
{SDLK_k, "K"},
{SDLK_l, "L"},
{SDLK_m, "M"},
{SDLK_n, "N"},
{SDLK_o, "O"},
{SDLK_p, "P"},
{SDLK_q, "Q"},
{SDLK_r, "R"},
{SDLK_s, "S"},
{SDLK_t, "T"},
{SDLK_u, "U"},
{SDLK_v, "V"},
{SDLK_w, "W"},
{SDLK_x, "X"},
{SDLK_y, "Y"},
{SDLK_z, "Z"},
{SDLK_CAPSLOCK, "CapsLock"},
{SDLK_F1, "F1"},
{SDLK_F2, "F2"},
{SDLK_F3, "F3"},
{SDLK_F4, "F4"},
{SDLK_F5, "F5"},
{SDLK_F6, "F6"},
{SDLK_F7, "F7"},
{SDLK_F8, "F8"},
{SDLK_F9, "F9"},
{SDLK_F10, "F10"},
{SDLK_F11, "F11"},
{SDLK_F12, "F12"},
{SDLK_PRINTSCREEN, "Print"},
{SDLK_SCROLLLOCK, "ScrollLock"},
{SDLK_PAUSE, "Pause"},
{SDLK_INSERT, "Insert"},
{SDLK_HOME, "Home"},
{SDLK_PAGEUP, "PageUp"},
{SDLK_DELETE, "Delete"},
{SDLK_END, "End"},
{SDLK_PAGEDOWN, "PageDown"},
{SDLK_RIGHT, "Right"},
{SDLK_LEFT, "Left"},
{SDLK_DOWN, "Down"},
{SDLK_UP, "Up"},
{SDLK_NUMLOCKCLEAR, "NumLock"},
{SDLK_KP_DIVIDE, "Keypad+Divide"},
{SDLK_KP_MULTIPLY, "Keypad+Multiply"},
{SDLK_KP_MINUS, "Keypad+Minus"},
{SDLK_KP_PLUS, "Keypad+Plus"},
{SDLK_KP_ENTER, "Keypad+Return"},
{SDLK_KP_1, "Keypad+1"},
{SDLK_KP_2, "Keypad+2"},
{SDLK_KP_3, "Keypad+3"},
{SDLK_KP_4, "Keypad+4"},
{SDLK_KP_5, "Keypad+5"},
{SDLK_KP_6, "Keypad+6"},
{SDLK_KP_7, "Keypad+7"},
{SDLK_KP_8, "Keypad+8"},
{SDLK_KP_9, "Keypad+9"},
{SDLK_KP_0, "Keypad+0"},
{SDLK_KP_PERIOD, "Keypad+Period"},
{SDLK_APPLICATION, "Application"},
{SDLK_POWER, "Power"},
{SDLK_KP_EQUALS, "Keypad+Equal"},
{SDLK_F13, "F13"},
{SDLK_F14, "F14"},
{SDLK_F15, "F15"},
{SDLK_F16, "F16"},
{SDLK_F17, "F17"},
{SDLK_F18, "F18"},
{SDLK_F19, "F19"},
{SDLK_F20, "F20"},
{SDLK_F21, "F21"},
{SDLK_F22, "F22"},
{SDLK_F23, "F23"},
{SDLK_F24, "F24"},
{SDLK_EXECUTE, "Execute"},
{SDLK_HELP, "Help"},
{SDLK_MENU, "Menu"},
{SDLK_SELECT, "Select"},
{SDLK_STOP, "Stop"},
{SDLK_AGAIN, "Again"},
{SDLK_UNDO, "Undo"},
{SDLK_CUT, "Cut"},
{SDLK_COPY, "Copy"},
{SDLK_PASTE, "Paste"},
{SDLK_FIND, "Find"},
{SDLK_MUTE, "Mute"},
{SDLK_VOLUMEUP, "VolumeUp"},
{SDLK_VOLUMEDOWN, "VolumeDown"},
{SDLK_KP_COMMA, "Keypad+Comma"},
{SDLK_KP_EQUALSAS400, "Keypad+EqualAS400"},
{SDLK_ALTERASE, "AltErase"},
{SDLK_SYSREQ, "SysReq"},
{SDLK_CANCEL, "Cancel"},
{SDLK_CLEAR, "Clear"},
{SDLK_PRIOR, "Prior"},
{SDLK_RETURN2, "Return2"},
{SDLK_SEPARATOR, "Separator"},
{SDLK_OUT, "Out"},
{SDLK_OPER, "Oper"},
{SDLK_CLEARAGAIN, "ClearAgain"},
{SDLK_CRSEL, "CrSel"},
{SDLK_EXSEL, "ExSel"},
{SDLK_KP_00, "Keypad+00"},
{SDLK_KP_000, "Keypad+000"},
{SDLK_THOUSANDSSEPARATOR, "ThousandsSeparator"},
{SDLK_DECIMALSEPARATOR, "DecimalSeparator"},
{SDLK_CURRENCYUNIT, "CurrencyUnit"},
{SDLK_CURRENCYSUBUNIT, "CurrencySubunit"},
{SDLK_KP_LEFTPAREN, "Keypad+ParenLeft"},
{SDLK_KP_RIGHTPAREN, "Keypad+ParenRight"},
{SDLK_KP_LEFTBRACE, "Keypad+LeftBrace"},
{SDLK_KP_RIGHTBRACE, "Keypad+RightBrace"},
{SDLK_KP_TAB, "Keypad+Tab"},
{SDLK_KP_BACKSPACE, "Keypad+Backspace"},
{SDLK_KP_A, "Keypad+A"},
{SDLK_KP_B, "Keypad+B"},
{SDLK_KP_C, "Keypad+C"},
{SDLK_KP_D, "Keypad+D"},
{SDLK_KP_E, "Keypad+E"},
{SDLK_KP_F, "Keypad+F"},
{SDLK_KP_XOR, "Keypad+XOR"},
{SDLK_KP_POWER, "Keypad+Power"},
{SDLK_KP_PERCENT, "Keypad+Percent"},
{SDLK_KP_LESS, "Keypad+Less"},
{SDLK_KP_GREATER, "Keypad+Greater"},
{SDLK_KP_AMPERSAND, "Keypad+Ampersand"},
{SDLK_KP_DBLAMPERSAND, "Keypad+AmpersandDbl"},
{SDLK_KP_VERTICALBAR, "Keypad+Bar"},
{SDLK_KP_DBLVERTICALBAR, "Keypad+BarDbl"},
{SDLK_KP_COLON, "Keypad+Colon"},
{SDLK_KP_HASH, "Keypad+Hash"},
{SDLK_KP_SPACE, "Keypad+Space"},
{SDLK_KP_AT, "Keypad+At"},
{SDLK_KP_EXCLAM, "Keypad+Exclam"},
{SDLK_KP_MEMSTORE, "Keypad+MemStore"},
{SDLK_KP_MEMRECALL, "Keypad+MemRecall"},
{SDLK_KP_MEMCLEAR, "Keypad+MemClear"},
{SDLK_KP_MEMADD, "Keypad+MemAdd"},
{SDLK_KP_MEMSUBTRACT, "Keypad+MemSubtract"},
{SDLK_KP_MEMMULTIPLY, "Keypad+MemMultiply"},
{SDLK_KP_MEMDIVIDE, "Keypad+MemDivide"},
{SDLK_KP_PLUSMINUS, "Keypad+PlusMinus"},
{SDLK_KP_CLEAR, "Keypad+Clear"},
{SDLK_KP_CLEARENTRY, "Keypad+ClearEntry"},
{SDLK_KP_BINARY, "Keypad+Binary"},
{SDLK_KP_OCTAL, "Keypad+Octal"},
{SDLK_KP_DECIMAL, "Keypad+Decimal"},
{SDLK_KP_HEXADECIMAL, "Keypad+Hexadecimal"},
{SDLK_LCTRL, "LeftControl"},
{SDLK_LSHIFT, "LeftShift"},
{SDLK_LALT, "LeftAlt"},
{SDLK_LGUI, "Super_L"},
{SDLK_RCTRL, "RightCtrl"},
{SDLK_RSHIFT, "RightShift"},
{SDLK_RALT, "RightAlt"},
{SDLK_RGUI, "RightSuper"},
{SDLK_MODE, "Mode"},
{SDLK_AUDIONEXT, "MediaNext"},
{SDLK_AUDIOPREV, "MediaPrevious"},
{SDLK_AUDIOSTOP, "MediaStop"},
{SDLK_AUDIOPLAY, "MediaPlay"},
{SDLK_AUDIOMUTE, "VolumeMute"},
{SDLK_MEDIASELECT, "MediaSelect"},
{SDLK_WWW, "WWW"},
{SDLK_MAIL, "Mail"},
{SDLK_CALCULATOR, "Calculator"},
{SDLK_COMPUTER, "Computer"},
{SDLK_AC_SEARCH, "Search"},
{SDLK_AC_HOME, "Home"},
{SDLK_AC_BACK, "Back"},
{SDLK_AC_FORWARD, "Forward"},
{SDLK_AC_STOP, "Stop"},
{SDLK_AC_REFRESH, "Refresh"},
{SDLK_AC_BOOKMARKS, "Bookmarks"},
{SDLK_BRIGHTNESSDOWN, "BrightnessDown"},
{SDLK_BRIGHTNESSUP, "BrightnessUp"},
{SDLK_DISPLAYSWITCH, "DisplaySwitch"},
{SDLK_KBDILLUMTOGGLE, "IllumToggle"},
{SDLK_KBDILLUMDOWN, "IllumDown"},
{SDLK_KBDILLUMUP, "IllumUp"},
{SDLK_EJECT, "Eject"},
{SDLK_SLEEP, "Sleep"},
{SDLK_APP1, "App1"},
{SDLK_APP2, "App2"},
{SDLK_AUDIOREWIND, "MediaRewind"},
{SDLK_AUDIOFASTFORWARD, "MediaFastForward"}};
struct SDLKeyModifierEntry
{
SDL_Keymod mod;
SDL_Keymod mod_mask;
SDL_Keycode key_left;
SDL_Keycode key_right;
const char* name;
};
static const std::array<SDLKeyModifierEntry, 5> s_sdl_key_modifiers = {
{{KMOD_LSHIFT, static_cast<SDL_Keymod>(KMOD_LSHIFT | KMOD_RSHIFT), SDLK_LSHIFT, SDLK_RSHIFT, "Shift"},
{KMOD_LCTRL, static_cast<SDL_Keymod>(KMOD_LCTRL | KMOD_LCTRL), SDLK_LCTRL, SDLK_RCTRL, "Control"},
{KMOD_LALT, static_cast<SDL_Keymod>(KMOD_LALT | KMOD_RALT), SDLK_LALT, SDLK_RALT, "Alt"},
{KMOD_LGUI, static_cast<SDL_Keymod>(KMOD_LGUI | KMOD_RGUI), SDLK_LGUI, SDLK_RGUI, "Meta"}}};
const char* GetKeyName(SDL_Keycode key)
{
const auto it = s_sdl_key_names.find(key);
return it == s_sdl_key_names.end() ? nullptr : it->second;
}
std::optional<SDL_Keycode> GetKeyCodeForName(const std::string_view key_name)
{
for (const auto& it : s_sdl_key_names)
{
if (key_name == it.second)
return it.first;
}
return std::nullopt;
}
u32 KeyEventToInt(const SDL_Event* event)
{
u32 code = static_cast<u32>(event->key.keysym.sym);
const SDL_Keymod mods = static_cast<SDL_Keymod>(event->key.keysym.mod);
if (mods & (KMOD_LSHIFT | KMOD_RSHIFT))
code |= static_cast<u32>(KMOD_LSHIFT) << 16;
if (mods & (KMOD_LCTRL | KMOD_RCTRL))
code |= static_cast<u32>(KMOD_LCTRL) << 16;
if (mods & (KMOD_LALT | KMOD_RALT))
code |= static_cast<u32>(KMOD_LALT) << 16;
if (mods & (KMOD_LGUI | KMOD_RGUI))
code |= static_cast<u32>(KMOD_LGUI) << 16;
return code;
}
bool KeyEventToString(const SDL_Event* event, String& out_string)
{
const SDL_Keycode key = event->key.keysym.sym;
const SDL_Keymod mods = static_cast<SDL_Keymod>(event->key.keysym.mod);
const char* key_name = GetKeyName(event->key.keysym.sym);
if (!key_name)
return false;
out_string.Clear();
for (const SDLKeyModifierEntry& mod : s_sdl_key_modifiers)
{
if (mods & mod.mod_mask && key != mod.key_left && key != mod.key_right)
{
out_string.AppendString(mod.name);
out_string.AppendCharacter('+');
}
}
out_string.AppendString(key_name);
return true;
}
std::optional<u32> ParseKeyString(const std::string_view key_str)
{
u32 modifiers = 0;
std::string_view::size_type pos = 0;
for (;;)
{
std::string_view::size_type plus_pos = key_str.find('+', pos);
if (plus_pos == std::string_view::npos)
break;
const std::string_view mod_part = key_str.substr(pos, plus_pos - pos);
for (const SDLKeyModifierEntry& mod : s_sdl_key_modifiers)
{
if (mod_part == mod.name)
{
modifiers |= static_cast<int>(mod.mod);
break;
}
}
pos = plus_pos + 1;
}
std::optional<SDL_Keycode> key_code = GetKeyCodeForName(key_str.substr(pos));
if (!key_code)
return std::nullopt;
return static_cast<u32>(key_code.value()) | (modifiers << 16);
}
} // namespace SDLKeyNames