From 5456d98fdee19bd8bc91da50623fe27524b9448c Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Wed, 19 Aug 2026 16:18:00 -0700 Subject: [PATCH 1/5] add clipboard setter/getter functions --- sdk/include/mods/svc/ui.h | 6 ++++- src/dusk/mods/svc/ui.cpp | 46 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/sdk/include/mods/svc/ui.h b/sdk/include/mods/svc/ui.h index 5dd9066421..4549aa53fc 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* out_text, size_t max_length); + 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/src/dusk/mods/svc/ui.cpp b/src/dusk/mods/svc/ui.cpp index b47959a930..41a70100e7 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,31 @@ void ui_remove_mod(LoadedMod& mod) { } } +ModResult ui_get_clipboard_text(LoadedMod& mod, char* out_text, size_t max_length) { + if (SDL_HasClipboardText()) { + std::string text = SDL_GetClipboardText(); + if (text.empty()) { + return MOD_ERROR; + } + + if (text.size() > max_length - 1) { + text.resize(max_length - 1); + } + + memcpy(out_text, text.c_str(), text.size()); + out_text[text.size()] = '\0'; + } + + 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 +1395,23 @@ ModResult ui_dialog_add_action( return ui_impl::ui_dialog_add_action(*mod, dialog, *action); } +ModResult ui_get_clipboard_text(ModContext* ctx, char* out_text, size_t max_length) { + auto* mod = mod_from_context(ctx); + if (mod == nullptr || out_text == nullptr || max_length == 0) { + return MOD_INVALID_ARGUMENT; + } + + return ui_impl::ui_get_clipboard_text(*mod, out_text, max_length); +} + +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 +1438,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 From ff3dbfa8d50d31c7388887887711bf0d749ccfcf Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Wed, 19 Aug 2026 18:15:42 -0700 Subject: [PATCH 2/5] couple fixes --- sdk/include/mods/svc/ui.h | 2 +- src/dusk/mods/svc/ui.cpp | 27 ++++++++++++++++++--------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/sdk/include/mods/svc/ui.h b/sdk/include/mods/svc/ui.h index 4549aa53fc..e265f306fa 100644 --- a/sdk/include/mods/svc/ui.h +++ b/sdk/include/mods/svc/ui.h @@ -293,7 +293,7 @@ typedef struct UiService { ModResult (*push_toast)(ModContext* ctx, const UiToastDesc* desc); /* Minor version 2 */ - ModResult (*get_clipboard_text)(ModContext* ctx, char* out_text, size_t max_length); + ModResult (*get_clipboard_text)(ModContext* ctx, char* buffer, size_t bufferSize, size_t* outLength); ModResult (*set_clipboard_text)(ModContext* ctx, const char* text); } UiService; diff --git a/src/dusk/mods/svc/ui.cpp b/src/dusk/mods/svc/ui.cpp index 41a70100e7..a3d95e9f1d 100644 --- a/src/dusk/mods/svc/ui.cpp +++ b/src/dusk/mods/svc/ui.cpp @@ -1039,19 +1039,28 @@ void ui_remove_mod(LoadedMod& mod) { } } -ModResult ui_get_clipboard_text(LoadedMod& mod, char* out_text, size_t max_length) { +ModResult ui_get_clipboard_text(LoadedMod& mod, char* buffer, size_t bufferSize, size_t* outLength) { + if (outLength != nullptr) { + *outLength = 0; + } + if (SDL_HasClipboardText()) { - std::string text = SDL_GetClipboardText(); + char* text_ptr = SDL_GetClipboardText(); + std::string text = text_ptr ? text_ptr : ""; + SDL_free(text_ptr); + if (text.empty()) { return MOD_ERROR; } - if (text.size() > max_length - 1) { - text.resize(max_length - 1); + if (bufferSize < text.size() + 1) { + return MOD_INVALID_ARGUMENT; } - memcpy(out_text, text.c_str(), text.size()); - out_text[text.size()] = '\0'; + memcpy(buffer, text.c_str(), text.size() + 1); + buffer[text.size()] = '\0'; + } else { + *buffer = '\0'; } return MOD_OK; @@ -1395,13 +1404,13 @@ ModResult ui_dialog_add_action( return ui_impl::ui_dialog_add_action(*mod, dialog, *action); } -ModResult ui_get_clipboard_text(ModContext* ctx, char* out_text, size_t max_length) { +ModResult ui_get_clipboard_text(ModContext* ctx, char* buffer, size_t bufferSize, size_t* outLength) { auto* mod = mod_from_context(ctx); - if (mod == nullptr || out_text == nullptr || max_length == 0) { + if (mod == nullptr || buffer == nullptr) { return MOD_INVALID_ARGUMENT; } - return ui_impl::ui_get_clipboard_text(*mod, out_text, max_length); + return ui_impl::ui_get_clipboard_text(*mod, buffer, bufferSize, outLength); } ModResult ui_set_clipboard_text(ModContext* ctx, const char* text) { From e40506dfebd6bfa6217f3c020629ac873bcd0eed Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Wed, 19 Aug 2026 18:17:07 -0700 Subject: [PATCH 3/5] actually set outLength --- src/dusk/mods/svc/ui.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/dusk/mods/svc/ui.cpp b/src/dusk/mods/svc/ui.cpp index a3d95e9f1d..561448d138 100644 --- a/src/dusk/mods/svc/ui.cpp +++ b/src/dusk/mods/svc/ui.cpp @@ -1053,6 +1053,10 @@ ModResult ui_get_clipboard_text(LoadedMod& mod, char* buffer, size_t bufferSize, return MOD_ERROR; } + if (outLength != nullptr) { + *outLength = text.size(); + } + if (bufferSize < text.size() + 1) { return MOD_INVALID_ARGUMENT; } From 8525b0b40c31adafd3312d0a4fea17c249465804 Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Wed, 19 Aug 2026 19:06:53 -0700 Subject: [PATCH 4/5] enc's suggestions --- src/dusk/mods/svc/ui.cpp | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/dusk/mods/svc/ui.cpp b/src/dusk/mods/svc/ui.cpp index 561448d138..304c4eb57b 100644 --- a/src/dusk/mods/svc/ui.cpp +++ b/src/dusk/mods/svc/ui.cpp @@ -1044,29 +1044,28 @@ ModResult ui_get_clipboard_text(LoadedMod& mod, char* buffer, size_t bufferSize, *outLength = 0; } + std::string text; if (SDL_HasClipboardText()) { - char* text_ptr = SDL_GetClipboardText(); - std::string text = text_ptr ? text_ptr : ""; - SDL_free(text_ptr); - + char* textPtr = SDL_GetClipboardText(); + text = textPtr != nullptr ? textPtr : ""; + SDL_free(textPtr); if (text.empty()) { return MOD_ERROR; } - - if (outLength != nullptr) { - *outLength = text.size(); - } - - if (bufferSize < text.size() + 1) { - return MOD_INVALID_ARGUMENT; - } - - memcpy(buffer, text.c_str(), text.size() + 1); - buffer[text.size()] = '\0'; - } else { - *buffer = '\0'; } + 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; } @@ -1410,7 +1409,7 @@ ModResult ui_dialog_add_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) { + if (mod == nullptr || (buffer == nullptr && bufferSize != 0)) { return MOD_INVALID_ARGUMENT; } From 06735d13694a0460e69aa6b2465f0725cf853ae2 Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Wed, 19 Aug 2026 19:14:07 -0700 Subject: [PATCH 5/5] c++ wrappers --- sdk/include/mods/svc/ui.hpp | 47 +++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 sdk/include/mods/svc/ui.hpp 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