diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index 0dc6ccd4fc..e643e3bec9 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -354,6 +354,11 @@ if(OPROFILE_FOUND) target_link_libraries(common PRIVATE OProfile::OProfile) endif() +if(ENABLE_SDL) + target_link_libraries(common PRIVATE SDL3::SDL3) + target_compile_definitions(common PRIVATE -DHAVE_SDL3) +endif() + if(ENABLE_LLVM) find_package(LLVM CONFIG) if(LLVM_FOUND) diff --git a/Source/Core/Common/Keyboard.cpp b/Source/Core/Common/Keyboard.cpp index 346048703f..743b1e8df5 100644 --- a/Source/Core/Common/Keyboard.cpp +++ b/Source/Core/Common/Keyboard.cpp @@ -4,12 +4,25 @@ #include "Common/Keyboard.h" #include +#include #include #ifdef _WIN32 #include +#elif defined(HAVE_SDL3) +#include +#include #endif +#ifdef HAVE_SDL3 +// Will be overridden by Dolphin's SDL InputBackend +u32 Common::KeyboardContext::s_sdl_init_event_type(-1); +u32 Common::KeyboardContext::s_sdl_update_event_type(-1); +u32 Common::KeyboardContext::s_sdl_quit_event_type(-1); +#endif + +#include "Core/Config/MainSettings.h" + namespace { // Crazy ugly @@ -173,59 +186,183 @@ u8 MapVirtualKeyToHID(u8 virtual_key, int keyboard_layout) #else u8 MapVirtualKeyToHID(u8 virtual_key, int keyboard_layout) { + // SDL3 keyboard state uses scan codes already based on HID usage id return virtual_key; } #endif + +std::weak_ptr s_keyboard_context; +std::mutex s_keyboard_context_mutex; + +// Will be updated by DolphinQt's Host: +// - SetRenderHandle +// - SetFullscreen +Common::KeyboardContext::HandlerState s_handler_state{}; } // Anonymous namespace namespace Common { -bool Common::IsVirtualKeyPressed(int virtual_key) +KeyboardContext::KeyboardContext() +{ + if (Config::Get(Config::MAIN_WII_KEYBOARD)) + Init(); +} + +KeyboardContext::~KeyboardContext() +{ + if (Config::Get(Config::MAIN_WII_KEYBOARD)) + Quit(); +} + +void KeyboardContext::Init() +{ +#if !defined(_WIN32) && defined(HAVE_SDL3) + SDL_Event event{s_sdl_init_event_type}; + SDL_PushEvent(&event); + m_keyboard_state = SDL_GetKeyboardState(nullptr); +#endif + m_is_ready = true; +} + +void KeyboardContext::Quit() +{ + m_is_ready = false; +#if !defined(_WIN32) && defined(HAVE_SDL3) + SDL_Event event{s_sdl_quit_event_type}; + SDL_PushEvent(&event); +#endif +} + +void* KeyboardContext::HandlerState::GetHandle() const +{ +#ifdef _WIN32 + if (is_rendering_to_main && !is_fullscreen) + return main_handle; +#endif + return renderer_handle; +} + +void KeyboardContext::NotifyInit() +{ + if (auto self = s_keyboard_context.lock()) + self->Init(); +} + +void KeyboardContext::NotifyHandlerChanged(const KeyboardContext::HandlerState& state) +{ + s_handler_state = state; + if (s_keyboard_context.expired()) + return; +#if !defined(_WIN32) && defined(HAVE_SDL3) + SDL_Event event{s_sdl_update_event_type}; + SDL_PushEvent(&event); +#endif +} + +void KeyboardContext::NotifyQuit() +{ + if (auto self = s_keyboard_context.lock()) + self->Quit(); +} + +void* KeyboardContext::GetWindowHandle() +{ + return s_handler_state.GetHandle(); +} + +std::shared_ptr KeyboardContext::GetInstance() +{ + const std::lock_guard guard(s_keyboard_context_mutex); + std::shared_ptr ptr = s_keyboard_context.lock(); + if (!ptr) + { + ptr = std::shared_ptr(new KeyboardContext); + s_keyboard_context = ptr; + } + return ptr; +} + +HIDPressedState KeyboardContext::GetPressedState(int keyboard_layout) const +{ + return m_is_ready ? HIDPressedState{.modifiers = PollHIDModifiers(), + .pressed_keys = PollHIDPressedKeys(keyboard_layout)} : + HIDPressedState{}; +} + +bool KeyboardContext::IsVirtualKeyPressed(int virtual_key) const { #ifdef _WIN32 return (GetAsyncKeyState(virtual_key) & 0x8000) != 0; +#elif defined(HAVE_SDL3) + if (virtual_key >= SDL_SCANCODE_COUNT) + return false; + return m_keyboard_state[virtual_key]; #else - // TODO: do it for non-Windows platforms + // TODO: Android implementation return false; #endif } -u8 Common::PollHIDModifiers() +u8 KeyboardContext::PollHIDModifiers() const { u8 modifiers = 0; -#ifdef _WIN32 + using VkHidPair = std::pair; + // References: // https://learn.microsoft.com/windows/win32/inputdev/virtual-key-codes + // https://wiki.libsdl.org/SDL3/SDL_Scancode // https://www.usb.org/document-library/device-class-definition-hid-111 + // + // HID modifiers: + // Bit 0 - LEFT CTRL + // Bit 1 - LEFT SHIFT + // Bit 2 - LEFT ALT + // Bit 3 - LEFT GUI + // Bit 4 - RIGHT CTRL + // Bit 5 - RIGHT SHIFT + // Bit 6 - RIGHT ALT + // Bit 7 - RIGHT GUI static const std::vector MODIFIERS_MAP{ - {VK_LCONTROL, 0x01}, // HID modifier: Bit 0 - LEFT CTRL - {VK_LSHIFT, 0x02}, // HID modifier: Bit 1 - LEFT SHIFT - {VK_LMENU, 0x04}, // HID modifier: Bit 2 - LEFT ALT - {VK_LWIN, 0x08}, // HID modifier: Bit 3 - LEFT GUI - {VK_RCONTROL, 0x10}, // HID modifier: Bit 4 - RIGHT CTRL - {VK_RSHIFT, 0x20}, // HID modifier: Bit 5 - RIGHT SHIFT - {VK_RMENU, 0x40}, // HID modifier: Bit 6 - RIGHT ALT - {VK_RWIN, 0x80} // HID modifier: Bit 7 - RIGHT GUI +#ifdef _WIN32 + {VK_LCONTROL, 0x01}, {VK_LSHIFT, 0x02}, {VK_LMENU, 0x04}, + {VK_LWIN, 0x08}, {VK_RCONTROL, 0x10}, {VK_RSHIFT, 0x20}, + {VK_RMENU, 0x40}, {VK_RWIN, 0x80} +#elif defined(HAVE_SDL3) + {SDL_SCANCODE_LCTRL, 0x01}, {SDL_SCANCODE_LSHIFT, 0x02}, {SDL_SCANCODE_LALT, 0x04}, + {SDL_SCANCODE_LGUI, 0x08}, {SDL_SCANCODE_RCTRL, 0x10}, {SDL_SCANCODE_RSHIFT, 0x20}, + {SDL_SCANCODE_RALT, 0x40}, {SDL_SCANCODE_RGUI, 0x80} +#else + // TODO: Android implementation +#endif }; + for (const auto& [virtual_key, hid_modifier] : MODIFIERS_MAP) { if (IsVirtualKeyPressed(virtual_key)) modifiers |= hid_modifier; } -#else - // TODO: Implementation for non-Windows platforms -#endif + return modifiers; } -HIDPressedKeys PollHIDPressedKeys(int keyboard_layout) +HIDPressedKeys KeyboardContext::PollHIDPressedKeys(int keyboard_layout) const { HIDPressedKeys pressed_keys{}; auto it = pressed_keys.begin(); #ifdef _WIN32 - for (std::size_t virtual_key = 0; virtual_key < KEYBOARD_STATE_SIZE; ++virtual_key) + const std::size_t begin = 0; + const std::size_t end = KEYBOARD_STATE_SIZE; +#elif defined(HAVE_SDL3) + const std::size_t begin = SDL_SCANCODE_A; + const std::size_t end = SDL_SCANCODE_LCTRL; +#else + const std::size_t begin = 0; + const std::size_t end = 0; +#endif + + for (std::size_t virtual_key = begin; virtual_key < end; ++virtual_key) { if (!IsVirtualKeyPressed(static_cast(virtual_key))) continue; @@ -234,9 +371,6 @@ HIDPressedKeys PollHIDPressedKeys(int keyboard_layout) if (++it == pressed_keys.end()) break; } -#else - // TODO: Implementation for non-Windows platforms -#endif return pressed_keys; } } // namespace Common diff --git a/Source/Core/Common/Keyboard.h b/Source/Core/Common/Keyboard.h index 220b450c70..adf5591c86 100644 --- a/Source/Core/Common/Keyboard.h +++ b/Source/Core/Common/Keyboard.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include "Common/CommonTypes.h" @@ -17,7 +18,55 @@ enum using HIDPressedKeys = std::array; -bool IsVirtualKeyPressed(int virtual_key); -u8 PollHIDModifiers(); -HIDPressedKeys PollHIDPressedKeys(int keyboard_layout); +struct HIDPressedState +{ + u8 modifiers = 0; + HIDPressedKeys pressed_keys{}; + + auto operator<=>(const HIDPressedState&) const = default; +}; + +class KeyboardContext +{ +public: + ~KeyboardContext(); + + struct HandlerState + { + void* main_handle = nullptr; + void* renderer_handle = nullptr; + bool is_fullscreen = false; + bool is_rendering_to_main = false; + + void* GetHandle() const; + }; + + static void NotifyInit(); + static void NotifyHandlerChanged(const HandlerState& state); + static void NotifyQuit(); + static void* GetWindowHandle(); + static std::shared_ptr GetInstance(); + + HIDPressedState GetPressedState(int keyboard_layout) const; + +#ifdef HAVE_SDL3 + static u32 s_sdl_init_event_type; + static u32 s_sdl_update_event_type; + static u32 s_sdl_quit_event_type; +#endif + +private: + KeyboardContext(); + + void Init(); + void Quit(); + bool IsVirtualKeyPressed(int virtual_key) const; + u8 PollHIDModifiers() const; + HIDPressedKeys PollHIDPressedKeys(int keyboard_layout) const; + + bool m_is_ready = false; +#ifdef HAVE_SDL3 + const bool* m_keyboard_state = nullptr; +#endif +}; } // namespace Common diff --git a/Source/Core/Core/IOS/USB/USB_KBD.cpp b/Source/Core/Core/IOS/USB/USB_KBD.cpp index 9782e772f9..9200b185c5 100644 --- a/Source/Core/Core/IOS/USB/USB_KBD.cpp +++ b/Source/Core/Core/IOS/USB/USB_KBD.cpp @@ -15,7 +15,7 @@ namespace IOS::HLE { -USB_KBD::MessageData::MessageData(MessageType type, const State& state) +USB_KBD::MessageData::MessageData(MessageType type, const Common::HIDPressedState& state) : msg_type{Common::swap32(static_cast(type))}, modifiers{state.modifiers}, pressed_keys{state.pressed_keys} { @@ -38,11 +38,21 @@ std::optional USB_KBD::Open(const OpenRequest& request) m_message_queue = {}; m_previous_state = {}; + m_keyboard_context = Common::KeyboardContext::GetInstance(); // m_message_queue.emplace(MessageType::KeyboardConnect, {}); return Device::Open(request); } +std::optional USB_KBD::Close(u32 fd) +{ + INFO_LOG_FMT(IOS, "USB_KBD: Close"); + + m_keyboard_context.reset(); + + return Device::Close(fd); +} + std::optional USB_KBD::Write(const ReadWriteRequest& request) { // Stubbed. @@ -64,11 +74,13 @@ std::optional USB_KBD::IOCtl(const IOCtlRequest& request) void USB_KBD::Update() { - if (!Config::Get(Config::MAIN_WII_KEYBOARD) || Core::WantsDeterminism() || !m_is_active) + if (!Config::Get(Config::MAIN_WII_KEYBOARD) || Core::WantsDeterminism() || !m_is_active || + !m_keyboard_context) + { return; + } - const State current_state{.modifiers = Common::PollHIDModifiers(), - .pressed_keys = Common::PollHIDPressedKeys(m_keyboard_layout)}; + const auto current_state = m_keyboard_context->GetPressedState(m_keyboard_layout); if (current_state == m_previous_state) return; diff --git a/Source/Core/Core/IOS/USB/USB_KBD.h b/Source/Core/Core/IOS/USB/USB_KBD.h index 76feb34485..31ac3ee77a 100644 --- a/Source/Core/Core/IOS/USB/USB_KBD.h +++ b/Source/Core/Core/IOS/USB/USB_KBD.h @@ -19,6 +19,7 @@ public: USB_KBD(EmulationKernel& ios, const std::string& device_name); std::optional Open(const OpenRequest& request) override; + std::optional Close(u32 fd) override; std::optional Write(const ReadWriteRequest& request) override; std::optional IOCtl(const IOCtlRequest& request) override; void Update() override; @@ -31,14 +32,6 @@ private: Event = 2 }; - struct State - { - u8 modifiers = 0; - Common::HIDPressedKeys pressed_keys{}; - - auto operator<=>(const State&) const = default; - }; - #pragma pack(push, 1) struct MessageData { @@ -48,13 +41,14 @@ private: u8 unk2 = 0; Common::HIDPressedKeys pressed_keys{}; - MessageData(MessageType msg_type, const State& state); + MessageData(MessageType msg_type, const Common::HIDPressedState& state); }; static_assert(std::is_trivially_copyable_v, "MessageData must be trivially copyable, as it's copied into emulated memory."); #pragma pack(pop) std::queue m_message_queue; - State m_previous_state; + Common::HIDPressedState m_previous_state; int m_keyboard_layout = Common::KBD_LAYOUT_QWERTY; + std::shared_ptr m_keyboard_context; }; } // namespace IOS::HLE diff --git a/Source/Core/DolphinQt/Host.cpp b/Source/Core/DolphinQt/Host.cpp index 46b0aa8963..169f2772c0 100644 --- a/Source/Core/DolphinQt/Host.cpp +++ b/Source/Core/DolphinQt/Host.cpp @@ -16,6 +16,7 @@ #endif #include "Common/Common.h" +#include "Common/Keyboard.h" #include "Core/Config/MainSettings.h" #include "Core/ConfigManager.h" @@ -62,6 +63,11 @@ void Host::SetRenderHandle(void* handle) { m_render_to_main = Config::Get(Config::MAIN_RENDER_TO_MAIN); + Common::KeyboardContext::NotifyHandlerChanged({.main_handle = m_main_window_handle.load(), + .renderer_handle = handle, + .is_fullscreen = m_render_fullscreen.load(), + .is_rendering_to_main = m_render_to_main.load()}); + if (m_render_handle == handle) return; @@ -176,6 +182,11 @@ void Host::SetRenderFullscreen(bool fullscreen) { m_render_fullscreen = fullscreen; + Common::KeyboardContext::NotifyHandlerChanged({.main_handle = m_main_window_handle.load(), + .renderer_handle = m_render_handle.load(), + .is_fullscreen = fullscreen, + .is_rendering_to_main = m_render_to_main.load()}); + if (g_gfx && g_gfx->IsFullscreen() != fullscreen && g_ActiveConfig.ExclusiveFullscreenEnabled()) { RunWithGPUThreadInactive([fullscreen] { g_gfx->SetFullscreen(fullscreen); }); diff --git a/Source/Core/DolphinQt/Settings.cpp b/Source/Core/DolphinQt/Settings.cpp index cb76f0f5bf..15ad3f153b 100644 --- a/Source/Core/DolphinQt/Settings.cpp +++ b/Source/Core/DolphinQt/Settings.cpp @@ -24,6 +24,7 @@ #include "Common/Config/Config.h" #include "Common/Contains.h" #include "Common/FileUtil.h" +#include "Common/Keyboard.h" #include "Common/StringUtil.h" #include "Core/AchievementManager.h" @@ -789,6 +790,10 @@ void Settings::SetUSBKeyboardConnected(bool connected) if (IsUSBKeyboardConnected() != connected) { Config::SetBaseOrCurrent(Config::MAIN_WII_KEYBOARD, connected); + if (connected) + Common::KeyboardContext::NotifyInit(); + else + Common::KeyboardContext::NotifyQuit(); emit USBKeyboardConnectionChanged(connected); } } diff --git a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp index 8e4043f4a9..515d944624 100644 --- a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp +++ b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp @@ -3,6 +3,7 @@ #include "InputCommon/ControllerInterface/SDL/SDL.h" +#include #include #include #include @@ -14,12 +15,86 @@ #include #include "Common/Event.h" +#include "Common/Keyboard.h" #include "Common/Logging/Log.h" #include "Common/ScopeGuard.h" +#include "Core/Host.h" #include "InputCommon/ControllerInterface/ControllerInterface.h" #include "InputCommon/ControllerInterface/SDL/SDLGamepad.h" +namespace +{ +using UniqueSDLWindow = std::unique_ptr; + +// Based on sdl2-compat 76eb981a4c376bcaf615c0af37d46512ba45cfb8 +SDL_Window* SDL_CreateWindowFrom(void* handle) +{ + SDL_PropertiesID props = SDL_CreateProperties(); + if (!props) + return nullptr; + + if (const char* hint = SDL_GetHint("SDL_VIDEO_WINDOW_SHARE_PIXEL_FORMAT"); hint) + { + // This hint is a pointer (in string form) of the address of + // the window to share a pixel format with + SDL_Window* other_window = nullptr; + if (SDL_sscanf(hint, "%p", &other_window)) + { + void* ptr = SDL_GetPointerProperty(SDL_GetWindowProperties(other_window), + SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr); + SDL_SetPointerProperty(props, SDL_PROP_WINDOW_CREATE_WIN32_PIXEL_FORMAT_HWND_POINTER, ptr); + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true); + } + } + + if (SDL_GetHintBoolean("SDL_VIDEO_FOREIGN_WINDOW_OPENGL", false)) + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true); + + if (SDL_GetHintBoolean("SDL_VIDEO_FOREIGN_WINDOW_VULKAN", false)) + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_VULKAN_BOOLEAN, true); + + SDL_SetPointerProperty(props, "sdl2-compat.external_window", handle); + SDL_Window* window = SDL_CreateWindowWithProperties(props); + SDL_DestroyProperties(props); + + // SDL3 has per-window text input, so we must enable on this window if it's active + if (SDL_EventEnabled(SDL_EVENT_TEXT_INPUT)) + { + props = SDL_CreateProperties(); + SDL_SetNumberProperty(props, SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT); + SDL_SetNumberProperty(props, SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER, SDL_CAPITALIZE_NONE); + SDL_SetBooleanProperty(props, SDL_PROP_TEXTINPUT_AUTOCORRECT_BOOLEAN, false); + SDL_StartTextInputWithProperties(window, props); + SDL_DestroyProperties(props); + } + + return window; +} + +std::optional UpdateKeyboardHandle(UniqueSDLWindow* unique_window) +{ + std::optional error; + + void* keyboard_handle = Common::KeyboardContext::GetWindowHandle(); + SDL_Window* keyboard_window = SDL_CreateWindowFrom(keyboard_handle); + if (keyboard_window == nullptr) + error = SDL_GetError(); + + unique_window->reset(keyboard_window); + if (error.has_value()) + return error; + + // SDL aggressive hooking might make the window borderless sometimes + if (!Host_RendererIsFullscreen()) + { + SDL_SetWindowFullscreen(keyboard_window, 0); + SDL_SetWindowBordered(keyboard_window, true); + } + return error; +} +} // namespace + namespace ciface::SDL { @@ -40,6 +115,7 @@ private: Uint32 m_stop_event_type; Uint32 m_populate_event_type; std::thread m_hotplug_thread; + UniqueSDLWindow m_keyboard_window{nullptr, SDL_DestroyWindow}; }; std::unique_ptr CreateInputBackend(ControllerInterface* controller_interface) @@ -155,7 +231,7 @@ InputBackend::InputBackend(ControllerInterface* controller_interface) return; } - const Uint32 custom_events_start = SDL_RegisterEvents(2); + const Uint32 custom_events_start = SDL_RegisterEvents(5); if (custom_events_start == static_cast(-1)) { ERROR_LOG_FMT(CONTROLLERINTERFACE, "SDL failed to register custom events"); @@ -163,6 +239,9 @@ InputBackend::InputBackend(ControllerInterface* controller_interface) } m_stop_event_type = custom_events_start; m_populate_event_type = custom_events_start + 1; + Common::KeyboardContext::s_sdl_init_event_type = custom_events_start + 2; + Common::KeyboardContext::s_sdl_update_event_type = custom_events_start + 3; + Common::KeyboardContext::s_sdl_quit_event_type = custom_events_start + 4; // Drain all of the events and add the initial joysticks before returning. Otherwise, the // individual joystick events as well as the custom populate event will be handled _after_ @@ -285,6 +364,40 @@ bool InputBackend::HandleEventAndContinue(const SDL_Event& e) { return false; } + else if (e.type == Common::KeyboardContext::s_sdl_init_event_type) + { + if (!SDL_InitSubSystem(SDL_INIT_VIDEO)) + { + ERROR_LOG_FMT(IOS_USB, "SDL failed to init subsystem to capture keyboard input: {}", + SDL_GetError()); + return true; + } + + if (const auto error = UpdateKeyboardHandle(&m_keyboard_window); error.has_value()) + { + ERROR_LOG_FMT(IOS_USB, "SDL failed to attach window to capture keyboard input: {}", *error); + return true; + } + } + else if (e.type == Common::KeyboardContext::s_sdl_update_event_type) + { + if (!SDL_WasInit(SDL_INIT_VIDEO)) + return true; + + // Release previous SDLWindow + m_keyboard_window.reset(); + + if (const auto error = UpdateKeyboardHandle(&m_keyboard_window); error.has_value()) + { + ERROR_LOG_FMT(IOS_USB, "SDL failed to switch window to capture keyboard input: {}", *error); + return true; + } + } + else if (e.type == Common::KeyboardContext::s_sdl_quit_event_type) + { + m_keyboard_window.reset(); + SDL_QuitSubSystem(SDL_INIT_VIDEO); + } return true; }