From bcc7ab71cb0e489d93ecd5c7bb113dfa2856d9cb Mon Sep 17 00:00:00 2001 From: Stenzek Date: Thu, 21 Mar 2024 01:53:34 +1000 Subject: [PATCH] NoGUI: Add SDL platform --- src/duckstation-nogui/CMakeLists.txt | 9 + .../duckstation-nogui.vcxproj | 2 + .../duckstation-nogui.vcxproj.filters | 2 + src/duckstation-nogui/nogui_host.cpp | 13 +- src/duckstation-nogui/nogui_platform.h | 3 + src/duckstation-nogui/sdl_key_names.h | 274 +++++++++++ src/duckstation-nogui/sdl_nogui_platform.cpp | 449 ++++++++++++++++++ src/duckstation-nogui/sdl_nogui_platform.h | 57 +++ src/duckstation-nogui/win32_key_names.h | 5 + src/util/sdl_input_source.cpp | 29 +- src/util/sdl_input_source.h | 4 + 11 files changed, 843 insertions(+), 4 deletions(-) create mode 100644 src/duckstation-nogui/sdl_key_names.h create mode 100644 src/duckstation-nogui/sdl_nogui_platform.cpp create mode 100644 src/duckstation-nogui/sdl_nogui_platform.h diff --git a/src/duckstation-nogui/CMakeLists.txt b/src/duckstation-nogui/CMakeLists.txt index 2615eed09..d5feb7387 100644 --- a/src/duckstation-nogui/CMakeLists.txt +++ b/src/duckstation-nogui/CMakeLists.txt @@ -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() diff --git a/src/duckstation-nogui/duckstation-nogui.vcxproj b/src/duckstation-nogui/duckstation-nogui.vcxproj index cc5525a36..fbbb40c64 100644 --- a/src/duckstation-nogui/duckstation-nogui.vcxproj +++ b/src/duckstation-nogui/duckstation-nogui.vcxproj @@ -6,6 +6,7 @@ Create + true @@ -19,6 +20,7 @@ + true diff --git a/src/duckstation-nogui/duckstation-nogui.vcxproj.filters b/src/duckstation-nogui/duckstation-nogui.vcxproj.filters index d08b60d6b..5cdac5f86 100644 --- a/src/duckstation-nogui/duckstation-nogui.vcxproj.filters +++ b/src/duckstation-nogui/duckstation-nogui.vcxproj.filters @@ -6,6 +6,7 @@ + @@ -15,6 +16,7 @@ + diff --git a/src/duckstation-nogui/nogui_host.cpp b/src/duckstation-nogui/nogui_host.cpp index 126ac4de7..7e3c6ab1a 100644 --- a/src/duckstation-nogui/nogui_host.cpp +++ b/src/duckstation-nogui/nogui_host.cpp @@ -870,13 +870,20 @@ std::unique_ptr NoGUIHost::CreatePlatform() { std::unique_ptr 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(); diff --git a/src/duckstation-nogui/nogui_platform.h b/src/duckstation-nogui/nogui_platform.h index 2810d2e68..452e545e5 100644 --- a/src/duckstation-nogui/nogui_platform.h +++ b/src/duckstation-nogui/nogui_platform.h @@ -52,6 +52,9 @@ public: #ifdef __APPLE__ static std::unique_ptr CreateCocoaPlatform(); #endif +#ifdef ENABLE_SDL2 + static std::unique_ptr CreateSDLPlatform(); +#endif #ifdef NOGUI_PLATFORM_WAYLAND static std::unique_ptr CreateWaylandPlatform(); #endif diff --git a/src/duckstation-nogui/sdl_key_names.h b/src/duckstation-nogui/sdl_key_names.h new file mode 100644 index 000000000..efc587ea7 --- /dev/null +++ b/src/duckstation-nogui/sdl_key_names.h @@ -0,0 +1,274 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// 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 +#include +#include +#include +#include + +namespace SDLKeyNames { + +static const std::map 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 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 diff --git a/src/duckstation-nogui/sdl_nogui_platform.cpp b/src/duckstation-nogui/sdl_nogui_platform.cpp new file mode 100644 index 000000000..3afb1e5b2 --- /dev/null +++ b/src/duckstation-nogui/sdl_nogui_platform.cpp @@ -0,0 +1,449 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// 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 +#include + +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(-1) || m_wakeup_event_id == static_cast(-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(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 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(std::max(window_px_width, 1)) / static_cast(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(window_px_width); + wi.surface_height = static_cast(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(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 SDLNoGUIPlatform::ConvertHostKeyboardStringToCode(const std::string_view& str) +{ + std::optional converted(SDLKeyNames::GetKeyCodeForName(str)); + return converted.has_value() ? std::optional(static_cast(converted.value())) : std::nullopt; + return std::nullopt; +} + +std::optional SDLNoGUIPlatform::ConvertHostKeyboardCodeToString(u32 code) +{ + const char* converted = SDLKeyNames::GetKeyName(code); + return converted ? std::optional(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 func) +{ + std::function* pfunc = new std::function(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(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(ev->motion.x); + const float y = static_cast(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* pfunc = reinterpret_cast*>(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(InputManager::GetInputSourceInterface(InputSourceType::SDL)); + if (is) [[likely]] + is->ProcessSDLEvent(&event_copy); + }); + } + } + break; + } +} + +std::unique_ptr NoGUIPlatform::CreateSDLPlatform() +{ + std::unique_ptr ret(new SDLNoGUIPlatform()); + if (!ret->Initialize()) + return {}; + + return ret; +} diff --git a/src/duckstation-nogui/sdl_nogui_platform.h b/src/duckstation-nogui/sdl_nogui_platform.h new file mode 100644 index 000000000..ea8a1b83f --- /dev/null +++ b/src/duckstation-nogui/sdl_nogui_platform.h @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) + +#pragma once + +#include + +#include "common/windows_headers.h" + +#include "nogui_platform.h" + +#include + +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 GetPlatformWindowInfo() override; + void SetPlatformWindowTitle(std::string title) override; + + std::optional ConvertHostKeyboardStringToCode(const std::string_view& str) override; + std::optional ConvertHostKeyboardCodeToString(u32 code) override; + + void RunMessageLoop() override; + void ExecuteInMessageLoop(std::function 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}; +}; \ No newline at end of file diff --git a/src/duckstation-nogui/win32_key_names.h b/src/duckstation-nogui/win32_key_names.h index fafea7564..ffe701158 100644 --- a/src/duckstation-nogui/win32_key_names.h +++ b/src/duckstation-nogui/win32_key_names.h @@ -1,6 +1,11 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// 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 #include #include diff --git a/src/util/sdl_input_source.cpp b/src/util/sdl_input_source.cpp index 1e401e89e..c6973577e 100644 --- a/src/util/sdl_input_source.cpp +++ b/src/util/sdl_input_source.cpp @@ -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(std::size(s_sdl_button_names)); // Ensure we don't conflict with GC buttons const InputBindingKey key(MakeGenericControllerButtonKey(InputSourceType::SDL, it->player_id, button)); diff --git a/src/util/sdl_input_source.h b/src/util/sdl_input_source.h index 7ed6185d1..25971c7ab 100644 --- a/src/util/sdl_input_source.h +++ b/src/util/sdl_input_source.h @@ -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 {