diff --git a/sdk/include/mods/svc/ui.h b/sdk/include/mods/svc/ui.h index 5dd9066421..e265f306fa 100644 --- a/sdk/include/mods/svc/ui.h +++ b/sdk/include/mods/svc/ui.h @@ -9,7 +9,7 @@ #define UI_SERVICE_ID "dev.twilitrealm.dusklight.ui" #define UI_SERVICE_MAJOR 1u -#define UI_SERVICE_MINOR 1u +#define UI_SERVICE_MINOR 2u /* * UI primitives: a panel inside the host Mods window, mod-owned windows, dialogs, toasts, @@ -291,6 +291,10 @@ typedef struct UiService { /* Enqueue a toast notification. */ ModResult (*push_toast)(ModContext* ctx, const UiToastDesc* desc); + + /* Minor version 2 */ + ModResult (*get_clipboard_text)(ModContext* ctx, char* buffer, size_t bufferSize, size_t* outLength); + ModResult (*set_clipboard_text)(ModContext* ctx, const char* text); } UiService; MOD_DECLARE_SERVICE(UiService, svc_ui, UI_SERVICE_ID, UI_SERVICE_MAJOR, UI_SERVICE_MINOR); diff --git a/sdk/include/mods/svc/ui.hpp b/sdk/include/mods/svc/ui.hpp new file mode 100644 index 0000000000..7994cad1dc --- /dev/null +++ b/sdk/include/mods/svc/ui.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include + +#include +#include + +namespace mods::ui { + +inline ModResult get_clipboard_text(std::string& outText) { + outText.clear(); + + size_t textLength = 0; + auto result = svc_ui->get_clipboard_text(mod_ctx, nullptr, 0, &textLength); + if (result != MOD_OK) { + return result; + } + + for (int attempt = 0; attempt < 3; ++attempt) { + std::string buffer(textLength + 1, '\0'); + size_t actualLength = 0; + + result = svc_ui->get_clipboard_text( + mod_ctx, buffer.data(), buffer.size(), &actualLength); + + if (result == MOD_OK) { + buffer.resize(actualLength); + outText = std::move(buffer); + return MOD_OK; + } + + // Retry if the clipboard grew between the two calls. + if (result != MOD_INVALID_ARGUMENT || actualLength <= textLength) { + return result; + } + + textLength = actualLength; + } + + return MOD_ERROR; +} + +inline ModResult set_clipboard_text(const std::string& text) { + return svc_ui->set_clipboard_text(mod_ctx, text.c_str()); +} + +} // namespace mods::ui \ No newline at end of file diff --git a/src/dusk/mods/svc/ui.cpp b/src/dusk/mods/svc/ui.cpp index b47959a930..304c4eb57b 100644 --- a/src/dusk/mods/svc/ui.cpp +++ b/src/dusk/mods/svc/ui.cpp @@ -29,6 +29,8 @@ #include #include +#include "SDL3/SDL_clipboard.h" + namespace dusk::mods::svc::ui_impl { namespace { @@ -1037,6 +1039,43 @@ void ui_remove_mod(LoadedMod& mod) { } } +ModResult ui_get_clipboard_text(LoadedMod& mod, char* buffer, size_t bufferSize, size_t* outLength) { + if (outLength != nullptr) { + *outLength = 0; + } + + std::string text; + if (SDL_HasClipboardText()) { + char* textPtr = SDL_GetClipboardText(); + text = textPtr != nullptr ? textPtr : ""; + SDL_free(textPtr); + if (text.empty()) { + return MOD_ERROR; + } + } + + if (outLength != nullptr) { + *outLength = text.size(); + } + + if (buffer == nullptr) { + return MOD_OK; + } + if (bufferSize < text.size() + 1) { + return MOD_INVALID_ARGUMENT; + } + + memcpy(buffer, text.c_str(), text.size() + 1); + return MOD_OK; +} + +ModResult ui_set_clipboard_text(LoadedMod& mod, const char* text) { + if (!SDL_SetClipboardText(text)) { + return MOD_ERROR; + } + return MOD_OK; +} + } // namespace dusk::mods::svc::ui_impl namespace dusk::mods::svc { @@ -1368,6 +1407,23 @@ ModResult ui_dialog_add_action( return ui_impl::ui_dialog_add_action(*mod, dialog, *action); } +ModResult ui_get_clipboard_text(ModContext* ctx, char* buffer, size_t bufferSize, size_t* outLength) { + auto* mod = mod_from_context(ctx); + if (mod == nullptr || (buffer == nullptr && bufferSize != 0)) { + return MOD_INVALID_ARGUMENT; + } + + return ui_impl::ui_get_clipboard_text(*mod, buffer, bufferSize, outLength); +} + +ModResult ui_set_clipboard_text(ModContext* ctx, const char* text) { + auto* mod = mod_from_context(ctx); + if (mod == nullptr || text == nullptr) { + return MOD_INVALID_ARGUMENT; + } + return ui_impl::ui_set_clipboard_text(*mod, text); +} + constexpr UiService s_uiService{ .header = SERVICE_HEADER(UiService, UI_SERVICE_MAJOR, UI_SERVICE_MINOR), .register_mods_panel = ui_register_mods_panel, @@ -1394,6 +1450,8 @@ constexpr UiService s_uiService{ .register_menu_tab = ui_register_menu_tab, .unregister_menu_tab = ui_unregister_menu_tab, .push_toast = ui_push_toast, + .get_clipboard_text = ui_get_clipboard_text, + .set_clipboard_text = ui_set_clipboard_text, }; } // namespace