From b5ed65ddd6bfc47f7cdd7fb17299834730308594 Mon Sep 17 00:00:00 2001 From: SternXD Date: Thu, 4 Jun 2026 20:59:54 -0400 Subject: [PATCH] Qt/ImGui: Add clipboard support and fix modfier keys Signed-off-by: SternXD --- pcsx2-gsrunner/Main.cpp | 5 ++++ pcsx2-qt/QtHost.cpp | 31 ++++++++++++++++++++ pcsx2/Host.h | 3 ++ pcsx2/ImGui/ImGuiManager.cpp | 54 +++++++++++++++++++++++++++++++---- tests/ctest/core/StubHost.cpp | 5 ++++ 5 files changed, 92 insertions(+), 6 deletions(-) diff --git a/pcsx2-gsrunner/Main.cpp b/pcsx2-gsrunner/Main.cpp index bb55f0ea6f..17bf1c398f 100644 --- a/pcsx2-gsrunner/Main.cpp +++ b/pcsx2-gsrunner/Main.cpp @@ -222,6 +222,11 @@ bool Host::CopyTextToClipboard(const std::string_view text) return false; } +std::string Host::GetTextFromClipboard() +{ + return std::string(); +} + void Host::BeginTextInput() { // noop diff --git a/pcsx2-qt/QtHost.cpp b/pcsx2-qt/QtHost.cpp index 2c46f61d80..91305b51a1 100644 --- a/pcsx2-qt/QtHost.cpp +++ b/pcsx2-qt/QtHost.cpp @@ -77,6 +77,7 @@ namespace QtHost static void SaveSettings(); static void HookSignals(); static void RegisterTypes(); + static void InitializeClipboard(); static bool RunSetupWizard(); std::optional DownloadFile(QWidget* parent, const QString& title, std::string url, std::vector* data); } // namespace QtHost @@ -96,6 +97,8 @@ static bool s_run_setup_wizard = false; static bool s_cleanup_after_update = false; static bool s_boot_and_debug = false; static std::atomic_int s_vm_locked_with_dialog = 0; +static std::string s_clipboard_cache; +static std::mutex s_clipboard_cache_mutex; ////////////////////////////////////////////////////////////////////////// // CPU Thread @@ -1666,6 +1669,12 @@ bool Host::CopyTextToClipboard(const std::string_view text) return true; } +std::string Host::GetTextFromClipboard() +{ + std::lock_guard lock(s_clipboard_cache_mutex); + return s_clipboard_cache; +} + void Host::BeginTextInput() { QInputMethod* method = qApp->inputMethod(); @@ -2351,6 +2360,26 @@ void QtHost::RegisterTypes() qRegisterMetaType(); } +void QtHost::InitializeClipboard() +{ + QClipboard* clipboard = QGuiApplication::clipboard(); + if (clipboard) + { + { + std::lock_guard lock(s_clipboard_cache_mutex); + s_clipboard_cache = clipboard->text().toStdString(); + } + QObject::connect(clipboard, &QClipboard::dataChanged, []() { + QClipboard* cb = QGuiApplication::clipboard(); + if (cb) + { + std::lock_guard lock(s_clipboard_cache_mutex); + s_clipboard_cache = cb->text().toStdString(); + } + }); + } +} + bool QtHost::RunSetupWizard() { SetupWizardDialog dialog; @@ -2399,6 +2428,8 @@ int main(int argc, char* argv[]) PCSX2MainApplication app(argc, argv); + QtHost::InitializeClipboard(); + #ifndef _WIN32 if (!PerformEarlyHardwareChecks()) return EXIT_FAILURE; diff --git a/pcsx2/Host.h b/pcsx2/Host.h index 492e09c78c..a487ffed72 100644 --- a/pcsx2/Host.h +++ b/pcsx2/Host.h @@ -75,6 +75,9 @@ namespace Host /// Copies the provided text to the host's clipboard, if present. bool CopyTextToClipboard(const std::string_view text); + /// Returns the current text from the host's clipboard, if present. + std::string GetTextFromClipboard(); + /// Requests settings reset. Can be called from any thread, will call back and apply on the CPU thread. bool RequestResetSettings(bool folders, bool core, bool controllers, bool hotkeys, bool ui); diff --git a/pcsx2/ImGui/ImGuiManager.cpp b/pcsx2/ImGui/ImGuiManager.cpp index b5650ca3ba..470f7aae80 100644 --- a/pcsx2/ImGui/ImGuiManager.cpp +++ b/pcsx2/ImGui/ImGuiManager.cpp @@ -148,6 +148,17 @@ bool ImGuiManager::Initialize() io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad; io.KeyRepeatDelay = 0.5f; + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.Platform_GetClipboardTextFn = [](ImGuiContext* ctx) -> const char* { + static thread_local std::string s_clipboard_text; + s_clipboard_text = Host::GetTextFromClipboard(); + return s_clipboard_text.c_str(); + }; + platform_io.Platform_SetClipboardTextFn = [](ImGuiContext* ctx, const char* text) { + if (text) + Host::CopyTextToClipboard(text); + }; + g.ConfigNavWindowingKeyNext = ImGuiKey_None; g.ConfigNavWindowingKeyPrev = ImGuiKey_None; g.ConfigNavWindowingWithGamepad = false; @@ -398,10 +409,10 @@ void ImGuiManager::SetKeyMap() static constexpr KeyMapping mapping[] = {{ImGuiKey_LeftArrow, "Left"}, {ImGuiKey_RightArrow, "Right"}, {ImGuiKey_UpArrow, "Up"}, {ImGuiKey_DownArrow, "Down"}, {ImGuiKey_PageUp, "PageUp"}, {ImGuiKey_PageDown, "PageDown"}, {ImGuiKey_Home, "Home"}, {ImGuiKey_End, "End"}, {ImGuiKey_Insert, "Insert"}, {ImGuiKey_Delete, "Delete"}, {ImGuiKey_Backspace, "Backspace"}, - {ImGuiKey_Space, "Space"}, {ImGuiKey_Enter, "Return"}, {ImGuiKey_Escape, "Escape"}, {ImGuiKey_LeftCtrl, "LeftCtrl", "Ctrl"}, - {ImGuiKey_LeftShift, "LeftShift", "Shift"}, {ImGuiKey_LeftAlt, "LeftAlt", "Alt"}, {ImGuiKey_LeftSuper, "LeftSuper", "Super"}, + {ImGuiKey_Space, "Space"}, {ImGuiKey_Enter, "Return"}, {ImGuiKey_Escape, "Escape"}, {ImGuiKey_LeftCtrl, "LeftCtrl", "Control"}, + {ImGuiKey_LeftShift, "LeftShift", "Shift"}, {ImGuiKey_LeftAlt, "LeftAlt", "Alt"}, {ImGuiKey_LeftSuper, "Meta", "Super_L"}, {ImGuiKey_RightCtrl, "RightCtrl"}, {ImGuiKey_RightShift, "RightShift"}, {ImGuiKey_RightAlt, "RightAlt"}, - {ImGuiKey_RightSuper, "RightSuper"}, {ImGuiKey_Menu, "Menu"}, {ImGuiKey_0, "0"}, {ImGuiKey_1, "1"}, {ImGuiKey_2, "2"}, + {ImGuiKey_RightSuper, "Super_R"}, {ImGuiKey_Menu, "Menu"}, {ImGuiKey_0, "0"}, {ImGuiKey_1, "1"}, {ImGuiKey_2, "2"}, {ImGuiKey_3, "3"}, {ImGuiKey_4, "4"}, {ImGuiKey_5, "5"}, {ImGuiKey_6, "6"}, {ImGuiKey_7, "7"}, {ImGuiKey_8, "8"}, {ImGuiKey_9, "9"}, {ImGuiKey_A, "A"}, {ImGuiKey_B, "B"}, {ImGuiKey_C, "C"}, {ImGuiKey_D, "D"}, {ImGuiKey_E, "E"}, {ImGuiKey_F, "F"}, {ImGuiKey_G, "G"}, {ImGuiKey_H, "H"}, {ImGuiKey_I, "I"}, {ImGuiKey_J, "J"}, {ImGuiKey_K, "K"}, {ImGuiKey_L, "L"}, {ImGuiKey_M, "M"}, {ImGuiKey_N, "N"}, @@ -1122,14 +1133,45 @@ bool ImGuiManager::ProcessPointerAxisEvent(InputBindingKey key, float value) return s_imgui_wants_mouse.load(std::memory_order_acquire); } +static ImGuiKey GetModifierForKey(ImGuiKey key) +{ + static constexpr std::pair modifier_map[] = { + {ImGuiKey_LeftCtrl, ImGuiMod_Ctrl}, + {ImGuiKey_RightCtrl, ImGuiMod_Ctrl}, + {ImGuiKey_LeftShift, ImGuiMod_Shift}, + {ImGuiKey_RightShift, ImGuiMod_Shift}, + {ImGuiKey_LeftAlt, ImGuiMod_Alt}, + {ImGuiKey_RightAlt, ImGuiMod_Alt}, + {ImGuiKey_LeftSuper, ImGuiMod_Super}, + {ImGuiKey_RightSuper, ImGuiMod_Super}, + }; + + for (const auto& [k, mod] : modifier_map) + { + if (key == k) + return mod; + } + + return ImGuiKey_None; +} + bool ImGuiManager::ProcessHostKeyEvent(InputBindingKey key, float value) { - decltype(s_imgui_key_map)::iterator iter; - if (!ImGui::GetCurrentContext() || (iter = s_imgui_key_map.find(key.data)) == s_imgui_key_map.end()) + if (!ImGui::GetCurrentContext()) + return false; + + const auto iter = s_imgui_key_map.find(key.data); + if (iter == s_imgui_key_map.end()) return false; // still update state anyway - MTGS::RunOnGSThread([imkey = iter->second, down = (value != 0.0f)]() { ImGui::GetIO().AddKeyEvent(imkey, down); }); + MTGS::RunOnGSThread([imkey = iter->second, down = (value != 0.0f)]() { + ImGuiIO& io = ImGui::GetIO(); + io.AddKeyEvent(imkey, down); + + if (const ImGuiKey mod = GetModifierForKey(imkey); mod != ImGuiKey_None) + io.AddKeyEvent(mod, down); + }); return s_imgui_wants_keyboard.load(std::memory_order_acquire); } diff --git a/tests/ctest/core/StubHost.cpp b/tests/ctest/core/StubHost.cpp index 431dcb8aa1..e5f5676efa 100644 --- a/tests/ctest/core/StubHost.cpp +++ b/tests/ctest/core/StubHost.cpp @@ -65,6 +65,11 @@ bool Host::CopyTextToClipboard(const std::string_view text) return false; } +std::string Host::GetTextFromClipboard() +{ + return std::string(); +} + void Host::BeginTextInput() { }