From b59f9a12704ccf11e733368b3074e597e58d411f Mon Sep 17 00:00:00 2001 From: Stenzek Date: Wed, 20 Nov 2024 12:43:20 +1000 Subject: [PATCH] ImGuiManager: Plumb through clipboard access --- src/duckstation-qt/qthost.cpp | 12 ++++ src/duckstation-regtest/regtest_host.cpp | 5 ++ src/util/host.h | 5 +- src/util/imgui_manager.cpp | 74 +++++++++++++++++++----- 4 files changed, 81 insertions(+), 15 deletions(-) diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 8ead86ef9..50c05715c 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -1909,6 +1909,18 @@ void Host::OpenURL(std::string_view url) QtHost::RunOnUIThread([url = QtUtils::StringViewToQString(url)]() { QtUtils::OpenURL(g_main_window, QUrl(url)); }); } +std::string Host::GetClipboardText() +{ + // Hope this doesn't deadlock... + std::string ret; + QtHost::RunOnUIThread([&ret]() { + QClipboard* clipboard = QGuiApplication::clipboard(); + if (clipboard) + ret = clipboard->text().toStdString(); + }, true); + return ret; +} + bool Host::CopyTextToClipboard(std::string_view text) { QtHost::RunOnUIThread([text = QtUtils::StringViewToQString(text)]() { diff --git a/src/duckstation-regtest/regtest_host.cpp b/src/duckstation-regtest/regtest_host.cpp index 5f60d1a7e..cad29ca38 100644 --- a/src/duckstation-regtest/regtest_host.cpp +++ b/src/duckstation-regtest/regtest_host.cpp @@ -380,6 +380,11 @@ void Host::OpenURL(std::string_view url) // } +std::string Host::GetClipboardText() +{ + return std::string(); +} + bool Host::CopyTextToClipboard(std::string_view text) { return false; diff --git a/src/util/host.h b/src/util/host.h index 654ae86b3..5afe611ab 100644 --- a/src/util/host.h +++ b/src/util/host.h @@ -50,7 +50,10 @@ std::string GetHTTPUserAgent(); /// Opens a URL, using the default application. void OpenURL(std::string_view url); -/// Copies the provided text to the host's clipboard, if present. +/// Returns the current contents of the clipboard as UTF-8 text, if any. +std::string GetClipboardText(); + +/// Copies the provided UTF-8 text to the host's clipboard, if present. bool CopyTextToClipboard(std::string_view text); /// Returns a localized version of the specified string within the specified context. diff --git a/src/util/imgui_manager.cpp b/src/util/imgui_manager.cpp index 7171bd739..f1153d413 100644 --- a/src/util/imgui_manager.cpp +++ b/src/util/imgui_manager.cpp @@ -75,6 +75,10 @@ static bool AddImGuiFonts(bool fullscreen_fonts); static ImFont* AddTextFont(float size, bool full_glyph_range); static ImFont* AddFixedFont(float size); static bool AddIconFonts(float size); +static void SetCommonIOOptions(ImGuiIO& io); +static void SetImKeyState(ImGuiIO& io, u32 key, bool pressed); +static const char* GetClipboardTextImpl(void* userdata); +static void SetClipboardTextImpl(void* userdata, const char* text); static void AddOSDMessage(std::string key, std::string message, float duration, bool is_warning); static void RemoveKeyedOSDMessage(std::string key, bool is_warning); static void ClearOSDMessages(bool clear_warnings); @@ -246,9 +250,6 @@ bool ImGuiManager::Initialize(float global_scale, float screen_margin, Error* er ImGuiIO& io = s_imgui_context->IO; io.IniFilename = nullptr; io.BackendFlags |= ImGuiBackendFlags_HasGamepad | ImGuiBackendFlags_RendererHasVtxOffset; - io.BackendUsingLegacyKeyArrays = 0; - io.BackendUsingLegacyNavInputArray = 0; - io.KeyRepeatDelay = 0.5f; #ifndef __ANDROID__ // Android has no keyboard, nor are we using ImGui for any actual user-interactable windows. io.ConfigFlags |= @@ -256,6 +257,7 @@ bool ImGuiManager::Initialize(float global_scale, float screen_margin, Error* er #else io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad; #endif + SetCommonIOOptions(io); s_window_width = g_gpu_device->HasMainSwapChain() ? static_cast(g_gpu_device->GetMainSwapChain()->GetWidth()) : 0.0f; @@ -456,11 +458,11 @@ void ImGuiManager::SetKeyMap() {ImGuiKey_Space, "Space", nullptr}, {ImGuiKey_Enter, "Return", nullptr}, {ImGuiKey_Escape, "Escape", nullptr}, - {ImGuiKey_LeftCtrl, "LeftCtrl", "Ctrl"}, + {ImGuiKey_LeftCtrl, "LeftControl", "Control"}, {ImGuiKey_LeftShift, "LeftShift", "Shift"}, {ImGuiKey_LeftAlt, "LeftAlt", "Alt"}, {ImGuiKey_LeftSuper, "LeftSuper", "Super"}, - {ImGuiKey_RightCtrl, "RightCtrl", nullptr}, + {ImGuiKey_RightCtrl, "RightControl", nullptr}, {ImGuiKey_RightShift, "RightShift", nullptr}, {ImGuiKey_RightAlt, "RightAlt", nullptr}, {ImGuiKey_RightSuper, "RightSuper", nullptr}, @@ -1091,6 +1093,15 @@ void ImGuiManager::UpdateMousePosition(float x, float y) std::atomic_thread_fence(std::memory_order_release); } +void ImGuiManager::SetCommonIOOptions(ImGuiIO& io) +{ + io.BackendUsingLegacyKeyArrays = 0; + io.BackendUsingLegacyNavInputArray = 0; + io.KeyRepeatDelay = 0.5f; + io.GetClipboardTextFn = GetClipboardTextImpl; + io.SetClipboardTextFn = SetClipboardTextImpl; +} + bool ImGuiManager::ProcessPointerButtonEvent(InputBindingKey key, float value) { if (!s_imgui_context || key.data >= std::size(ImGui::GetIO().MouseDown)) @@ -1116,16 +1127,33 @@ bool ImGuiManager::ProcessPointerAxisEvent(InputBindingKey key, float value) bool ImGuiManager::ProcessHostKeyEvent(InputBindingKey key, float value) { - decltype(s_imgui_key_map)::iterator iter; - if (!s_imgui_context || (iter = s_imgui_key_map.find(key.data)) == s_imgui_key_map.end()) + if (!s_imgui_context) return false; // still update state anyway - s_imgui_context->IO.AddKeyEvent(iter->second, value != 0.0); + SetImKeyState(s_imgui_context->IO, key.data, (value != 0.0f)); return s_imgui_wants_keyboard.load(std::memory_order_acquire); } +void ImGuiManager::SetImKeyState(ImGuiIO& io, u32 key, bool pressed) +{ + const auto iter = s_imgui_key_map.find(key); + if (iter == s_imgui_key_map.end()) + return; + + const ImGuiKey imkey = iter->second; + s_imgui_context->IO.AddKeyEvent(imkey, pressed); + + // modifier keys need to be handled separately + if ((imkey >= ImGuiKey_LeftCtrl && imkey <= ImGuiKey_LeftSuper) || + (imkey >= ImGuiKey_RightCtrl && imkey <= ImGuiKey_RightSuper)) + { + const u32 idx = imkey - ((imkey >= ImGuiKey_RightCtrl) ? ImGuiKey_RightCtrl : ImGuiKey_LeftCtrl); + s_imgui_context->IO.AddKeyEvent(static_cast(static_cast(ImGuiMod_Ctrl) << idx), pressed); + } +} + bool ImGuiManager::ProcessGenericInputEvent(GenericInputBinding key, float value) { static constexpr ImGuiKey key_map[] = { @@ -1167,6 +1195,28 @@ bool ImGuiManager::ProcessGenericInputEvent(GenericInputBinding key, float value return s_imgui_wants_keyboard.load(std::memory_order_acquire); } +const char* ImGuiManager::GetClipboardTextImpl(void* userdata) +{ + const std::string text = Host::GetClipboardText(); + if (text.empty() || text.length() >= std::numeric_limits::max()) + return nullptr; + + const size_t length = text.length(); + GImGui->ClipboardHandlerData.resize(static_cast(length + 1)); + std::memcpy(GImGui->ClipboardHandlerData.Data, text.data(), length); + GImGui->ClipboardHandlerData.Data[length] = 0; + return GImGui->ClipboardHandlerData.Data; +} + +void ImGuiManager::SetClipboardTextImpl(void* userdata, const char* text) +{ + const size_t length = std::strlen(text); + if (length == 0) + return; + + Host::CopyTextToClipboard(std::string_view(text, length)); +} + void ImGuiManager::CreateSoftwareCursorTextures() { for (u32 i = 0; i < static_cast(s_software_cursors.size()); i++) @@ -1341,10 +1391,8 @@ bool ImGuiManager::CreateAuxiliaryRenderWindow(AuxiliaryRenderWindowState* state ImVec2(static_cast(state->swap_chain->GetWidth()), static_cast(state->swap_chain->GetHeight())); state->imgui_context->IO.IniFilename = nullptr; state->imgui_context->IO.BackendFlags |= ImGuiBackendFlags_HasGamepad; - state->imgui_context->IO.BackendUsingLegacyKeyArrays = 0; - state->imgui_context->IO.BackendUsingLegacyNavInputArray = 0; - state->imgui_context->IO.KeyRepeatDelay = 0.5f; state->imgui_context->IO.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange; + SetCommonIOOptions(state->imgui_context->IO); SetStyle(state->imgui_context->Style, state->swap_chain->GetScale()); state->imgui_context->Style.WindowBorderSize = 0.0f; @@ -1472,9 +1520,7 @@ void ImGuiManager::ProcessAuxiliaryRenderWindowInputEvent(Host::AuxiliaryRenderW case Host::AuxiliaryRenderWindowEvent::KeyPressed: case Host::AuxiliaryRenderWindowEvent::KeyReleased: { - const auto iter = s_imgui_key_map.find(param1.uint_param); - if (iter != s_imgui_key_map.end()) - io.AddKeyEvent(iter->second, (event == Host::AuxiliaryRenderWindowEvent::KeyPressed)); + SetImKeyState(io, param1.uint_param, (event == Host::AuxiliaryRenderWindowEvent::KeyPressed)); } break;