1
0
mirror of synced 2024-11-28 09:30:51 +01:00

feat: Support Copy/Paste on WASM build outside the application border (#1542)

### Problem description
WASM build does not support copy/paste beyond the application. Meaning,
there's no practical way of sending text back and forth across the
application border.

There are lengthy threads why this is a technical challenge in
WASM/Browser world, e.g:
- https://github.com/pthom/hello_imgui/issues/3
- https://github.com/emscripten-core/emscripten/pull/19510

### Implementation description
Implements a workaround solution as Header only C++ library, as proposed
and implemented at:
https://github.com/Armchair-Software/emscripten-browser-clipboard

Maybe there are cleaner ways of achieving the functionality. Definitely
would like to have some discussion around this. 👀

ℹ️ The proposed PR "works for me" on Windows, using CTRL-C/V shortcuts
to copy text from and to the application. On MacOS the system shortcut
to Paste is different from what ImHex has defined. This results in
system Paste shortcut of command-V triggering the browser callback to
synchronise the application clipboard, but no actual Paste takes place
within ImHex.

If there would be a clean way to trigger the paste command, that would
be wonderful (or get the context and references to write the data to the
cursor, but I was unable to find a clean solution). The only proposed
solutions in the referenced threads were about triggering paste event
internally via Key events. This seemed wonky 🙃 , so is not currently
implemented. At the moment the paste on MacOS is command+V followed by
control+V.

### Additional things
This is definitely a stopgap solution before the ImGui and Emscripten
take a more proper solution in enabling Copy/Paste outside the
application borders. However, I feel like this is a must have capability
to make the WASM build more useful, not just for trying out ImHex.

Cheers! 🍻

---------

Co-authored-by: Nik <werwolv98@gmail.com>
This commit is contained in:
Sten Feldman 2024-02-13 21:30:18 +02:00 committed by GitHub
parent 56b2e09b01
commit d70f7422b7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 125 additions and 0 deletions

View File

@ -0,0 +1,99 @@
#ifndef EMSCRIPTEN_BROWSER_CLIPBOARD_H_INCLUDED
#define EMSCRIPTEN_BROWSER_CLIPBOARD_H_INCLUDED
#include <string>
#include <emscripten.h>
#define _EM_JS_INLINE(ret, c_name, js_name, params, code) \
extern "C" { \
ret c_name params EM_IMPORT(js_name); \
EMSCRIPTEN_KEEPALIVE \
__attribute__((section("em_js"), aligned(1))) inline char __em_js__##js_name[] = \
#params "<::>" code; \
}
#define EM_JS_INLINE(ret, name, params, ...) _EM_JS_INLINE(ret, name, name, params, #__VA_ARGS__)
namespace emscripten_browser_clipboard {
/////////////////////////////////// Interface //////////////////////////////////
using paste_handler = void(*)(std::string const&, void*);
using copy_handler = char const*(*)(void*);
inline void paste(paste_handler callback, void *callback_data = nullptr);
inline void copy(copy_handler callback, void *callback_data = nullptr);
inline void copy(std::string const &content);
///////////////////////////////// Implementation ///////////////////////////////
namespace {
EM_JS_INLINE(void, paste_js, (paste_handler callback, void *callback_data), {
/// Register the given callback to handle paste events. Callback data pointer is passed through to the callback.
/// Paste handler callback signature is:
/// void my_handler(std::string const &paste_data, void *callback_data = nullptr);
document.addEventListener('paste', (event) => {
Module["ccall"]('paste_return', 'number', ['string', 'number', 'number'], [event.clipboardData.getData('text/plain'), callback, callback_data]);
});
});
EM_JS_INLINE(void, copy_js, (copy_handler callback, void *callback_data), {
/// Register the given callback to handle copy events. Callback data pointer is passed through to the callback.
/// Copy handler callback signature is:
/// char const *my_handler(void *callback_data = nullptr);
document.addEventListener('copy', (event) => {
const content_ptr = Module["ccall"]('copy_return', 'number', ['number', 'number'], [callback, callback_data]);
event.clipboardData.setData('text/plain', UTF8ToString(content_ptr));
event.preventDefault();
});
});
EM_JS_INLINE(void, copy_async_js, (char const *content_ptr), {
/// Attempt to copy the provided text asynchronously
navigator.clipboard.writeText(UTF8ToString(content_ptr));
});
}
inline void paste(paste_handler callback, void *callback_data) {
/// C++ wrapper for javascript paste call
paste_js(callback, callback_data);
}
inline void copy(copy_handler callback, void *callback_data) {
/// C++ wrapper for javascript copy call
copy_js(callback, callback_data);
}
inline void copy(std::string const &content) {
/// C++ wrapper for javascript copy call
copy_async_js(content.c_str());
}
namespace {
extern "C" {
EMSCRIPTEN_KEEPALIVE inline int paste_return(char const *paste_data, paste_handler callback, void *callback_data);
EMSCRIPTEN_KEEPALIVE inline int paste_return(char const *paste_data, paste_handler callback, void *callback_data) {
/// Call paste callback - this function is called from javascript when the paste event occurs
callback(paste_data, callback_data);
return 1;
}
EMSCRIPTEN_KEEPALIVE inline char const *copy_return(copy_handler callback, void *callback_data);
EMSCRIPTEN_KEEPALIVE inline char const *copy_return(copy_handler callback, void *callback_data) {
/// Call paste callback - this function is called from javascript when the paste event occurs
return callback(callback_data);
}
}
}
}
#endif // EMSCRIPTEN_BROWSER_CLIPBOARD_H_INCLUDED

View File

@ -107,6 +107,11 @@
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#include <emscripten/html5.h>
// IMHEX PATCH BEGIN
#include <emscripten_browser_clipboard.h>
static std::string clipboardContent;
// IMHEX PATCH END
#endif
// We gather version tests as define in order to easily see which features are version-dependent.
@ -198,12 +203,24 @@ static void ImGui_ImplGlfw_ShutdownPlatformInterface();
// Functions
static const char* ImGui_ImplGlfw_GetClipboardText(void* user_data)
{
// IMHEX PATCH BEGIN
#ifdef __EMSCRIPTEN__
return clipboardContent.c_str();
#else
return glfwGetClipboardString((GLFWwindow*)user_data);
#endif
// IMHEX PATCH END
}
static void ImGui_ImplGlfw_SetClipboardText(void* user_data, const char* text)
{
glfwSetClipboardString((GLFWwindow*)user_data, text);
// IMHEX PATCH BEGIN
#ifdef __EMSCRIPTEN__
clipboardContent = text;
emscripten_browser_clipboard::copy(clipboardContent);
#endif
// IMHEX PATCH END
}
static ImGuiKey ImGui_ImplGlfw_KeyToImGuiKey(int key)
@ -594,6 +611,15 @@ static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, Glfw
bd->Time = 0.0;
bd->WantUpdateMonitors = true;
// IMHEX PATCH BEGIN
#ifdef __EMSCRIPTEN__
// Callback to handle clipboard paste from browser
emscripten_browser_clipboard::paste([](std::string const &paste_data, void *callback_data [[maybe_unused]]) {
clipboardContent = std::move(paste_data);
});
#endif
// IMHEX PATCH END
io.SetClipboardTextFn = ImGui_ImplGlfw_SetClipboardText;
io.GetClipboardTextFn = ImGui_ImplGlfw_GetClipboardText;
io.ClipboardUserData = bd->Window;