diff --git a/README.md b/README.md
index d7c19ae67..f4d620d8c 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/src/duckstation-sdl/CMakeLists.txt b/src/duckstation-sdl/CMakeLists.txt
index cc101b8d9..b25b4a4b6 100644
--- a/src/duckstation-sdl/CMakeLists.txt
+++ b/src/duckstation-sdl/CMakeLists.txt
@@ -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})
diff --git a/src/duckstation-sdl/duckstation-sdl.vcxproj b/src/duckstation-sdl/duckstation-sdl.vcxproj
index 02c82b077..300527d76 100644
--- a/src/duckstation-sdl/duckstation-sdl.vcxproj
+++ b/src/duckstation-sdl/duckstation-sdl.vcxproj
@@ -63,6 +63,7 @@
+
@@ -394,4 +395,4 @@
-
\ No newline at end of file
+
diff --git a/src/duckstation-sdl/duckstation-sdl.vcxproj.filters b/src/duckstation-sdl/duckstation-sdl.vcxproj.filters
index 7f3f2db71..8ff6182dd 100644
--- a/src/duckstation-sdl/duckstation-sdl.vcxproj.filters
+++ b/src/duckstation-sdl/duckstation-sdl.vcxproj.filters
@@ -12,6 +12,7 @@
+
diff --git a/src/duckstation-sdl/sdl_host_interface.cpp b/src/duckstation-sdl/sdl_host_interface.cpp
index d028c4579..9c1afdd1c 100644
--- a/src/duckstation-sdl/sdl_host_interface.cpp
+++ b/src/duckstation-sdl/sdl_host_interface.cpp
@@ -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
#include
#include
@@ -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 SDLHostInterface::CreateAudioStream(AudioBackend ba
}
}
+std::unique_ptr SDLHostInterface::CreateControllerInterface()
+{
+ return std::make_unique();
+}
+
+std::optional SDLHostInterface::GetHostKeyCode(const std::string_view key_code) const
+{
+ const std::optional code = SDLKeyNames::ParseKeyString(key_code);
+ if (!code)
+ return std::nullopt;
+
+ return static_cast(*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 callback)
@@ -256,29 +285,35 @@ void SDLHostInterface::RunLater(std::function 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(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::Create()
@@ -288,7 +323,7 @@ std::unique_ptr 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(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(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(static_cast(event->key.keysym.sym) |
+ static_cast(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(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(action)]) \
- { \
- controller->SetButtonState(m_keyboard_button_mapping[static_cast(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(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();
diff --git a/src/duckstation-sdl/sdl_host_interface.h b/src/duckstation-sdl/sdl_host_interface.h
index 5cc635082..59c3634fe 100644
--- a/src/duckstation-sdl/sdl_host_interface.h
+++ b/src/duckstation-sdl/sdl_host_interface.h
@@ -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
#include
#include
@@ -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 CreateAudioStream(AudioBackend backend) override;
+ std::unique_ptr CreateControllerInterface() override;
void OnSystemCreated() override;
void OnSystemPaused(bool paused) override;
void OnSystemDestroyed() override;
- void OnControllerTypeChanged(u32 slot) override;
+ void OnRunningGameChanged() override;
+
+ std::optional 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(KeyboardControllerAction::Count)>;
-
- struct ControllerData
- {
- SDL_GameController* controller;
- SDL_Haptic* haptic;
- u32 controller_index;
- float last_rumble_strength;
- };
-
bool HasSystem() const { return static_cast(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 m_app_icon_texture;
-
- KeyboardControllerActionMap m_keyboard_button_mapping;
-
+ std::unique_ptr 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;
diff --git a/src/duckstation-sdl/sdl_key_names.h b/src/duckstation-sdl/sdl_key_names.h
new file mode 100644
index 000000000..de0e2ffcf
--- /dev/null
+++ b/src/duckstation-sdl/sdl_key_names.h
@@ -0,0 +1,353 @@
+#pragma once
+#include "common/string.h"
+#include "common/types.h"
+#include
+#include
+#include
+#include