NoGUI: Add SDL platform

This commit is contained in:
Stenzek 2024-03-21 01:53:34 +10:00
parent 604dd5df40
commit bcc7ab71cb
No known key found for this signature in database
11 changed files with 843 additions and 4 deletions

View file

@ -86,3 +86,12 @@ if(ENABLE_WAYLAND)
X11::xkbcommon
)
endif()
if(ENABLE_SDL2)
message(STATUS "Building SDL NoGUI Platform.")
target_sources(duckstation-nogui PRIVATE
sdl_nogui_platform.cpp
sdl_nogui_platform.h
)
target_link_libraries(duckstation-nogui PUBLIC SDL2::SDL2)
endif()

View file

@ -6,6 +6,7 @@
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="sdl_nogui_platform.cpp" />
<ClCompile Include="wayland_nogui_platform.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
@ -19,6 +20,7 @@
<ClInclude Include="nogui_platform.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="sdl_nogui_platform.h" />
<ClInclude Include="wayland_nogui_platform.h">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClInclude>

View file

@ -6,6 +6,7 @@
<ClCompile Include="wayland_nogui_platform.cpp" />
<ClCompile Include="x11_nogui_platform.cpp" />
<ClCompile Include="pch.cpp" />
<ClCompile Include="sdl_nogui_platform.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="resource.h" />
@ -15,6 +16,7 @@
<ClInclude Include="wayland_nogui_platform.h" />
<ClInclude Include="x11_nogui_platform.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="sdl_nogui_platform.h" />
</ItemGroup>
<ItemGroup>
<Manifest Include="duckstation-nogui.manifest" />

View file

@ -870,13 +870,20 @@ std::unique_ptr<NoGUIPlatform> NoGUIHost::CreatePlatform()
{
std::unique_ptr<NoGUIPlatform> ret;
const char* platform = std::getenv("DUCKSTATION_NOGUI_PLATFORM");
#ifdef ENABLE_SDL2
if (platform && StringUtil::Strcasecmp(platform, "sdl") == 0)
ret = NoGUIPlatform::CreateSDLPlatform();
#endif
#if defined(_WIN32)
ret = NoGUIPlatform::CreateWin32Platform();
if (!ret)
ret = NoGUIPlatform::CreateWin32Platform();
#elif defined(__APPLE__)
ret = NoGUIPlatform::CreateCocoaPlatform();
if (!ret)
ret = NoGUIPlatform::CreateCocoaPlatform();
#else
// linux
const char* platform = std::getenv("DUCKSTATION_NOGUI_PLATFORM");
#ifdef NOGUI_PLATFORM_WAYLAND
if (!ret && (!platform || StringUtil::Strcasecmp(platform, "wayland") == 0) && std::getenv("WAYLAND_DISPLAY"))
ret = NoGUIPlatform::CreateWaylandPlatform();

View file

@ -52,6 +52,9 @@ public:
#ifdef __APPLE__
static std::unique_ptr<NoGUIPlatform> CreateCocoaPlatform();
#endif
#ifdef ENABLE_SDL2
static std::unique_ptr<NoGUIPlatform> CreateSDLPlatform();
#endif
#ifdef NOGUI_PLATFORM_WAYLAND
static std::unique_ptr<NoGUIPlatform> CreateWaylandPlatform();
#endif

View file

@ -0,0 +1,274 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "common/types.h"
#include "common/windows_headers.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"}};
static const char* GetKeyName(DWORD key)
{
const auto it = s_sdl_key_names.find(key);
return it == s_sdl_key_names.end() ? nullptr : it->second;
}
static std::optional<DWORD> 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;
}
} // namespace SDLKeyNames

View file

@ -0,0 +1,449 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "sdl_nogui_platform.h"
#include "nogui_host.h"
#include "sdl_key_names.h"
#include "core/host.h"
#include "util/imgui_manager.h"
#include "util/sdl_input_source.h"
#include "common/log.h"
#include "common/scoped_guard.h"
#include "common/string_util.h"
#include "common/threading.h"
#include <SDL.h>
#include <SDL_syswm.h>
Log_SetChannel(SDLNoGUIPlatform);
static constexpr float DEFAULT_WINDOW_DPI = 96.0f;
SDLNoGUIPlatform::SDLNoGUIPlatform()
{
m_message_loop_running.store(true, std::memory_order_release);
}
SDLNoGUIPlatform::~SDLNoGUIPlatform()
{
SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
}
bool SDLNoGUIPlatform::Initialize()
{
if (SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0)
{
Log_ErrorFmt("SDL_InitSubSystem() failed: {}", SDL_GetError());
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error",
TinyString::from_format("SDL_InitSubSystem() failed: {}", SDL_GetError()), nullptr);
return false;
}
m_func_event_id = SDL_RegisterEvents(1);
m_wakeup_event_id = SDL_RegisterEvents(1);
if (m_func_event_id == static_cast<u32>(-1) || m_wakeup_event_id == static_cast<u32>(-1))
{
Log_ErrorFmt("SDL_RegisterEvents() failed: {}", SDL_GetError());
return false;
}
// prevent input source polling on main thread...
SDLInputSource::ALLOW_EVENT_POLLING = false;
return true;
}
void SDLNoGUIPlatform::ReportError(const std::string_view& title, const std::string_view& message)
{
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, SmallString(title).c_str(), SmallString(message).c_str(), m_window);
}
bool SDLNoGUIPlatform::ConfirmMessage(const std::string_view& title, const std::string_view& message)
{
const SmallString title_copy(title);
const SmallString message_copy(message);
static constexpr SDL_MessageBoxButtonData bd[2] = {
{SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 1, "Yes"},
{SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 2, "No"},
};
const SDL_MessageBoxData md = {SDL_MESSAGEBOX_INFORMATION,
m_window,
title_copy.c_str(),
message_copy.c_str(),
static_cast<int>(std::size(bd)),
bd,
nullptr};
int buttonid = -1;
SDL_ShowMessageBox(&md, &buttonid);
return (buttonid == 1);
}
void SDLNoGUIPlatform::SetDefaultConfig(SettingsInterface& si)
{
// noop
}
bool SDLNoGUIPlatform::CreatePlatformWindow(std::string title)
{
s32 window_x, window_y, window_width, window_height;
if (!NoGUIHost::GetSavedPlatformWindowGeometry(&window_x, &window_y, &window_width, &window_height))
{
window_x = SDL_WINDOWPOS_UNDEFINED;
window_y = SDL_WINDOWPOS_UNDEFINED;
window_width = DEFAULT_WINDOW_WIDTH;
window_height = DEFAULT_WINDOW_HEIGHT;
}
m_window = SDL_CreateWindow(title.c_str(), window_x, window_y, window_width, window_height,
SDL_WINDOW_RESIZABLE | SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_MOUSE_FOCUS |
SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI);
if (!m_window)
{
Log_ErrorFmt("SDL_CreateWindow() failed: {}", SDL_GetError());
ReportError("Error", TinyString::from_format("SDL_CreateWindow() failed: {}", SDL_GetError()));
return false;
}
if (m_fullscreen.load(std::memory_order_acquire))
SetFullscreen(true);
return true;
}
bool SDLNoGUIPlatform::HasPlatformWindow() const
{
return (m_window != nullptr);
}
void SDLNoGUIPlatform::DestroyPlatformWindow()
{
if (!m_window)
return;
if (!m_fullscreen.load(std::memory_order_acquire))
{
int window_x = SDL_WINDOWPOS_UNDEFINED, window_y = SDL_WINDOWPOS_UNDEFINED;
int window_width = DEFAULT_WINDOW_WIDTH, window_height = DEFAULT_WINDOW_HEIGHT;
SDL_GetWindowPosition(m_window, &window_x, &window_y);
SDL_GetWindowSize(m_window, &window_width, &window_height);
NoGUIHost::SavePlatformWindowGeometry(window_x, window_y, window_width, window_height);
}
SDL_DestroyWindow(m_window);
m_window = nullptr;
}
std::optional<WindowInfo> SDLNoGUIPlatform::GetPlatformWindowInfo()
{
if (!m_window)
return std::nullopt;
SDL_SysWMinfo swi = {};
SDL_VERSION(&swi.version);
if (!SDL_GetWindowWMInfo(m_window, &swi))
{
Log_ErrorFmt("SDL_GetWindowWMInfo() failed: {}", SDL_GetError());
return std::nullopt;
}
int window_width = 1, window_height = 1;
int window_px_width = 1, window_px_height = 1;
SDL_GetWindowSize(m_window, &window_width, &window_height);
SDL_GetWindowSizeInPixels(m_window, &window_px_width, &window_px_height);
m_window_scale = static_cast<float>(std::max(window_px_width, 1)) / static_cast<float>(std::max(window_width, 1));
if (const int display_index = SDL_GetWindowDisplayIndex(m_window); display_index >= 0)
{
float ddpi, hdpi, vdpi;
if (SDL_GetDisplayDPI(display_index, &ddpi, &hdpi, &vdpi) == 0)
m_window_scale = std::max(ddpi / DEFAULT_WINDOW_DPI, 0.5f);
}
WindowInfo wi;
wi.surface_width = static_cast<u32>(window_px_width);
wi.surface_height = static_cast<u32>(window_px_height);
wi.surface_scale = m_window_scale;
switch (swi.subsystem)
{
#ifdef SDL_VIDEO_DRIVER_WINDOWS
case SDL_SYSWM_WINDOWS:
wi.type = WindowInfo::Type::Win32;
wi.window_handle = swi.info.win.window;
break;
#endif
#ifdef SDL_VIDEO_DRIVER_X11
case SDL_SYSWM_X11:
wi.type = WindowInfo::Type::X11;
wi.display_connection = swi.info.x11.display;
wi.window_handle = swi.info.x11.window;
break;
#endif
#ifdef SDL_VIDEO_DRIVER_WAYLAND
case SDL_SYSWM_WAYLAND:
wi.type = WindowInfo::Type::Wayland;
wi.display_connection = swi.info.wl.display;
wi.window_handle = swi.info.wl.surface;
break;
#endif
#ifdef SDL_VIDEO_DRIVER_COCOA
case SDL_SYSWM_COCOA:
wi.type = WindowInfo::Type::MacOS;
wi.window_handle = swi.info.cocoa.window;
break;
#endif
default:
Log_ErrorFmt("Unhandled WM subsystem {}", static_cast<int>(swi.subsystem));
return std::nullopt;
}
return wi;
}
void SDLNoGUIPlatform::SetPlatformWindowTitle(std::string title)
{
if (!m_window)
return;
SDL_SetWindowTitle(m_window, title.c_str());
}
std::optional<u32> SDLNoGUIPlatform::ConvertHostKeyboardStringToCode(const std::string_view& str)
{
std::optional<DWORD> converted(SDLKeyNames::GetKeyCodeForName(str));
return converted.has_value() ? std::optional<u32>(static_cast<u32>(converted.value())) : std::nullopt;
return std::nullopt;
}
std::optional<std::string> SDLNoGUIPlatform::ConvertHostKeyboardCodeToString(u32 code)
{
const char* converted = SDLKeyNames::GetKeyName(code);
return converted ? std::optional<std::string>(converted) : std::nullopt;
}
void SDLNoGUIPlatform::RunMessageLoop()
{
while (m_message_loop_running.load(std::memory_order_acquire))
{
SDL_Event ev;
if (!SDL_WaitEvent(&ev))
continue;
ProcessEvent(&ev);
}
}
void SDLNoGUIPlatform::ExecuteInMessageLoop(std::function<void()> func)
{
std::function<void()>* pfunc = new std::function<void()>(std::move(func));
SDL_Event ev;
ev.user = {};
ev.type = m_func_event_id;
ev.user.data1 = pfunc;
SDL_PushEvent(&ev);
}
void SDLNoGUIPlatform::QuitMessageLoop()
{
m_message_loop_running.store(false, std::memory_order_release);
SDL_Event ev;
ev.user = {};
ev.type = m_wakeup_event_id;
SDL_PushEvent(&ev);
}
void SDLNoGUIPlatform::SetFullscreen(bool enabled)
{
if (!m_window || m_fullscreen.load(std::memory_order_acquire) == enabled)
return;
if (SDL_SetWindowFullscreen(m_window, enabled ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0) != 0)
{
Log_ErrorFmt("SDL_SetWindowFullscreen() failed: {}", SDL_GetError());
return;
}
m_fullscreen.store(enabled, std::memory_order_release);
}
bool SDLNoGUIPlatform::RequestRenderWindowSize(s32 new_window_width, s32 new_window_height)
{
if (!m_window || m_fullscreen.load(std::memory_order_acquire))
return false;
SDL_SetWindowSize(m_window, new_window_width, new_window_height);
return true;
}
bool SDLNoGUIPlatform::OpenURL(const std::string_view& url)
{
if (SDL_OpenURL(SmallString(url).c_str()) != 0)
{
Log_ErrorFmt("SDL_OpenURL() failed: {}", SDL_GetError());
return false;
}
return true;
}
bool SDLNoGUIPlatform::CopyTextToClipboard(const std::string_view& text)
{
if (SDL_SetClipboardText(SmallString(text).c_str()) != 0)
{
Log_ErrorFmt("SDL_SetClipboardText() failed: {}", SDL_GetError());
return false;
}
return true;
}
void SDLNoGUIPlatform::ProcessEvent(const SDL_Event* ev)
{
switch (ev->type)
{
case SDL_WINDOWEVENT:
{
switch (ev->window.event)
{
case SDL_WINDOWEVENT_SIZE_CHANGED:
{
int window_width = ev->window.data1, window_height = ev->window.data2;
SDL_GetWindowSizeInPixels(m_window, &window_width, &window_height);
NoGUIHost::ProcessPlatformWindowResize(window_width, window_height, m_window_scale);
}
break;
case SDL_WINDOWEVENT_DISPLAY_CHANGED:
{
const int new_display = ev->window.data1;
float ddpi, hdpi, vdpi;
if (SDL_GetDisplayDPI(new_display, &ddpi, &hdpi, &vdpi) == 0)
{
if (const float new_scale = std::max(ddpi / DEFAULT_WINDOW_DPI, 0.5f); new_scale != m_window_scale)
{
m_window_scale = new_scale;
int window_width = 1, window_height = 1;
SDL_GetWindowSizeInPixels(m_window, &window_width, &window_height);
NoGUIHost::ProcessPlatformWindowResize(window_width, window_height, m_window_scale);
}
}
}
break;
case SDL_WINDOWEVENT_CLOSE:
{
Host::RunOnCPUThread([]() { Host::RequestExit(false); });
}
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
{
NoGUIHost::PlatformWindowFocusGained();
}
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
{
NoGUIHost::PlatformWindowFocusLost();
}
break;
default:
break;
}
}
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
{
const bool pressed = (ev->type == SDL_KEYDOWN);
NoGUIHost::ProcessPlatformKeyEvent(static_cast<s32>(ev->key.keysym.sym), pressed);
}
break;
case SDL_TEXTINPUT:
{
if (ImGuiManager::WantsTextInput())
NoGUIHost::ProcessPlatformTextEvent(ev->text.text);
}
break;
case SDL_MOUSEMOTION:
{
const float x = static_cast<float>(ev->motion.x);
const float y = static_cast<float>(ev->motion.y);
NoGUIHost::ProcessPlatformMouseMoveEvent(x, y);
}
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
{
const bool pressed = (ev->type == SDL_MOUSEBUTTONDOWN);
if (ev->button.button > 0)
NoGUIHost::ProcessPlatformMouseButtonEvent(ev->button.button - 1, pressed);
}
break;
case SDL_MOUSEWHEEL:
{
NoGUIHost::ProcessPlatformMouseWheelEvent(ev->wheel.preciseX, ev->wheel.preciseY);
}
break;
case SDL_QUIT:
{
Host::RunOnCPUThread([]() { Host::RequestExit(false); });
}
break;
default:
{
if (ev->type == m_func_event_id)
{
std::function<void()>* pfunc = reinterpret_cast<std::function<void()>*>(ev->user.data1);
if (pfunc)
{
(*pfunc)();
delete pfunc;
}
}
else if (ev->type == m_wakeup_event_id)
{
}
else if (SDLInputSource::IsHandledInputEvent(ev) && InputManager::GetInputSourceInterface(InputSourceType::SDL))
{
Host::RunOnCPUThread([event_copy = *ev]() {
SDLInputSource* is =
static_cast<SDLInputSource*>(InputManager::GetInputSourceInterface(InputSourceType::SDL));
if (is) [[likely]]
is->ProcessSDLEvent(&event_copy);
});
}
}
break;
}
}
std::unique_ptr<NoGUIPlatform> NoGUIPlatform::CreateSDLPlatform()
{
std::unique_ptr<SDLNoGUIPlatform> ret(new SDLNoGUIPlatform());
if (!ret->Initialize())
return {};
return ret;
}

View file

@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include <atomic>
#include "common/windows_headers.h"
#include "nogui_platform.h"
#include <SDL.h>
class SDLNoGUIPlatform : public NoGUIPlatform
{
public:
SDLNoGUIPlatform();
~SDLNoGUIPlatform();
bool Initialize();
void ReportError(const std::string_view& title, const std::string_view& message) override;
bool ConfirmMessage(const std::string_view& title, const std::string_view& message) override;
void SetDefaultConfig(SettingsInterface& si) override;
bool CreatePlatformWindow(std::string title) override;
bool HasPlatformWindow() const override;
void DestroyPlatformWindow() override;
std::optional<WindowInfo> GetPlatformWindowInfo() override;
void SetPlatformWindowTitle(std::string title) override;
std::optional<u32> ConvertHostKeyboardStringToCode(const std::string_view& str) override;
std::optional<std::string> ConvertHostKeyboardCodeToString(u32 code) override;
void RunMessageLoop() override;
void ExecuteInMessageLoop(std::function<void()> func) override;
void QuitMessageLoop() override;
void SetFullscreen(bool enabled) override;
bool RequestRenderWindowSize(s32 new_window_width, s32 new_window_height) override;
bool OpenURL(const std::string_view& url) override;
bool CopyTextToClipboard(const std::string_view& text) override;
private:
void ProcessEvent(const SDL_Event* ev);
SDL_Window* m_window = nullptr;
float m_window_scale = 1.0f;
u32 m_func_event_id = 0;
u32 m_wakeup_event_id = 0;
std::atomic_bool m_message_loop_running{false};
std::atomic_bool m_fullscreen{false};
};

View file

@ -1,6 +1,11 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "common/types.h"
#include "common/windows_headers.h"
#include <array>
#include <cstring>
#include <map>

View file

@ -151,6 +151,8 @@ static void SDLLogCallback(void* userdata, int category, SDL_LogPriority priorit
Log::Write("SDL", "SDL", priority_map[priority], message);
}
bool SDLInputSource::ALLOW_EVENT_POLLING = true;
SDLInputSource::SDLInputSource() = default;
SDLInputSource::~SDLInputSource()
@ -322,6 +324,9 @@ void SDLInputSource::ShutdownSubsystem()
void SDLInputSource::PollEvents()
{
if (!ALLOW_EVENT_POLLING)
return;
for (;;)
{
SDL_Event ev;
@ -548,6 +553,28 @@ TinyString SDLInputSource::ConvertKeyToIcon(InputBindingKey key)
return ret;
}
bool SDLInputSource::IsHandledInputEvent(const SDL_Event* ev)
{
switch (ev->type)
{
case SDL_CONTROLLERDEVICEADDED:
case SDL_CONTROLLERDEVICEREMOVED:
case SDL_JOYDEVICEADDED:
case SDL_JOYDEVICEREMOVED:
case SDL_CONTROLLERAXISMOTION:
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
case SDL_JOYAXISMOTION:
case SDL_JOYBUTTONDOWN:
case SDL_JOYBUTTONUP:
case SDL_JOYHATMOTION:
return true;
default:
return false;
}
}
bool SDLInputSource::ProcessSDLEvent(const SDL_Event* event)
{
switch (event->type)
@ -859,7 +886,7 @@ bool SDLInputSource::HandleJoystickButtonEvent(const SDL_JoyButtonEvent* ev)
if (it == m_controllers.end())
return false;
if (ev->button < it->joy_button_used_in_gc.size() && it->joy_button_used_in_gc[ev->button])
return false; // Will get handled by GC event
return false; // Will get handled by GC event
const u32 button =
ev->button + static_cast<u32>(std::size(s_sdl_button_names)); // Ensure we don't conflict with GC buttons
const InputBindingKey key(MakeGenericControllerButtonKey(InputSourceType::SDL, it->player_id, button));

View file

@ -46,6 +46,10 @@ public:
static u32 GetRGBForPlayerId(SettingsInterface& si, u32 player_id);
static u32 ParseRGBForPlayerId(const std::string_view& str, u32 player_id);
static bool IsHandledInputEvent(const SDL_Event* ev);
static bool ALLOW_EVENT_POLLING;
private:
struct ControllerData
{