-
Notifications
You must be signed in to change notification settings - Fork 104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Copy Paste in emscripten #3
Comments
Reading more into this it seems to be related to the security model of WASM in the browser. Not easily fixed... |
Hello, I will study this in the next weeks. |
If you can post some info I'll be happy to jump down that rabbit hole with you... |
Think I found a workaround... The following code works! <style>
.emscripten {
/*...*/
z-index: 1000;
}
</style>
<canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()"></canvas>
<textarea id="clipping" style="width:0;height:0;border:0" aria-hidden="true"></textarea>
<script>
async function copy(text) {
document.getElementById("clipping").focus();
const rtn = await navigator.clipboard.writeText(text);
document.getElementById("canvas").focus();
}
async function paste() {
document.getElementById("clipping").focus();
const rtn = await navigator.clipboard.readText();
document.getElementById("canvas").focus();
return rtn;
}
async function test() {
document.body.focus()
await copy(Math.random())
alert(await paste())
}
test();
</script> The key is that you have to textarea element focusible which means it cannot be hidden or invisible. However, other elements can completely obscure the view so it is essentially hidden. I've also made the textarea zero width, height and border so that you don't even see this element. I've also added aria-hidden="true" to avoid screen readers barking when you do something. I know Imgui isn't going to be accessible on the web without a lot of tinkering. But I figured you could at least start down that path... In C++ I think it would look something like this: EM_JS(void, copy, (const char* str), {
Asyncify.handleAsync(async () => {
document.getElementById("clipping").focus();
const rtn = navigator.clipboard.writeText(str);
document.getElementById("canvas").focus();
});
});
EM_JS(char*, paste, (), {
Asyncify.handleAsync(async () => {
document.getElementById("clipping").focus();
const str = navigator.clipboard.readText();
document.getElementById("canvas").focus();
const size = (Module.lengthBytesUTF8(str) + 1);
const rtn = Module._malloc(size);
Module.stringToUTF8(str, rtn, size);
return rtn;
});
}); I don't have a build environment to test this out at present. But at least this should get you closer to the mark... |
Many many thanks for your work! I'm back from holiday and I was away from keyboard for a while, so that I am sorry to answer late. Copy and paste is not that easy under emscripten as I wrote you before. I had a lengthy discussion about this with Andre Weissflog, who had already encountered this problem in his quite nice sokol libraries. We discussed this in the context of "imgui manual" here Basically I was able to partially solve this inside imgui manual:
I did not (yet) find a way to do it generically from inside hello_imgui, so that the application code (i.e the ImGui Manual window) needs to catch the "Ctrl-C" events manually. Anyhow, here are some hints on how this is partially done inside imgui manual and sokol:
I did not have time to work on porting this back to hello_imgui. If you are willing to help, I would appreciate it very much. Thanks |
On a side note, I saw that you are trying to use emscripten asyncify options. For example, line 8 could become:
|
The Sokol approach is deprecated in most browsers. (Line 17 has a note on that...) The reason for the deprecation was tat the security concerns are so massive for the old approach as you mentioned above... I don't think adding Asyncify to the build is at all a bad thing. Many new JS APIs are asynchronous and return promises. (webcams and fetch are two examples that come to mind...) Thus, many serious apps will need Asyncify anyway. The code above calls the permissions dialog the first time you try to paste something. (copy always works fine...) My thought is that if the permission is denied you can fail gracefully but optionally return something to let you know you don't have proper access to the system clipboard so that the app can respond appropriately. I'll look into the genericizing thing, but I think you may be right that the event needs to be caught by the imgui_impl_xxx.cpp. We should probably ask a questions upstream to make sure we are tracking with the general vision of Dear Imgui. I'll leave this open and get back to you... |
Dear future visitor from Google, The functions posted above for C++ are almost correct, they are a great starting point. I've tested them and fixed them up: EM_JS(void, copy, (const char* str), {
Asyncify.handleAsync(async () => {
document.getElementById("clipping").focus();
const rtn = await navigator.clipboard.writeText(UTF8ToString(str));
document.getElementById("canvas").focus();
});
});
EM_JS(char*, paste, (), {
return Asyncify.handleAsync(async () => {
document.getElementById("clipping").focus();
const str = await navigator.clipboard.readText();
document.getElementById("canvas").focus();
const size = lengthBytesUTF8(str) + 1;
const rtn = _malloc(size);
stringToUTF8(str, rtn, size);
return rtn;
});
}); I'd also recommend putting a |
There's now a simple header-only library to achieve copy and paste from Emscripten in the browser: https://github.com/Armchair-Software/emscripten-browser-clipboard This doesn't require you to modify the HTML of your page, add any text elements, or do anything other than include a single header. Here's an example of putting it to use with ImGui: #include <emscripten_browser_clipboard.h>
#include <imgui/imgui.h>
#include <iostream>
std::string content; // this stores the content for our internal clipboard
char const *get_content_for_imgui(void *user_data [[maybe_unused]]) {
/// Callback for imgui, to return clipboard content
std::cout << "ImGui requested clipboard content, returning " << std::quoted(content) << std::endl;
return content.c_str();
}
void set_content_from_imgui(void *user_data [[maybe_unused]], char const *text) {
/// Callback for imgui, to set clipboard content
content = text;
std::cout << "ImGui setting clipboard content to " << std::quoted(content) << std::endl;
emscripten_browser_clipboard::copy(content); // send clipboard data to the browser
}
// ...
emscripten_browser_clipboard::paste([](std::string const &paste_data, void *callback_data [[maybe_unused]]){
/// Callback to handle clipboard paste from browser
std::cout << "Clipboard updated from paste data: " << std::quoted(paste_data) << std::endl;
content = std::move(paste_data);
});
// set ImGui callbacks for clipboard access:
ImGuiIO &imgui_io = ImGui::GetIO();
imgui_io.GetClipboardTextFn = get_content_for_imgui;
imgui_io.SetClipboardTextFn = set_content_from_imgui; |
Hello @pthom, @floooh, @slowriot, Thanks for the awesome libraries. I love that I could fit visual6502remix on a 3.5" floppy. I have been tinkering with ImGui/ImPlot and most recently adopted @slowriot's solution to carry out Copy and Paste. It works for me with the weird caveat that I have to do paste with a mouse middle button click, not with Ctrl+V. Hitting Ctrl+V doesn't fire JS
I can see that Ctrl+V does not fire the On the other hand imgui_manual.html with Sokol backend and visual6502remix log the Could it be that SDL backend in the aforementioned reference apps eats the keyboard events so there is no I am eyeballing the SDL Emscripten config in HelloImGui and I am none the wiser. Naive Thx for hints and also greetings to all who stumble on this thread in their Internet search engine of choice. Edit:As usual. Writing it all down helped a bit. Adding
helps, you can fire
and the navigate to Code tab and try to type something. Credit for the |
Hi @Karm Let's continue on this thread in the hope of helping future visitors that try to have a working copy-paste with emscripten. You are definitely onto something! What you describe is strange, indeed. I could reproduce your issue using Firefox and Chrome under Linux and Windows. However, it does work under MacOS (with Command-V instead of Ctrl-V). As far as your workaround is concerned, there is a way to let it only handle Ctrl-V (and thus to preserve // Only stop propagation for Ctrl-V
window.addEventListener('keydown', function(event){
if (event.ctrlKey && event.key == 'v')
event.stopImmediatePropagation();
}, true);
// Log paste events
document.addEventListener(
"paste", (event) => {
console.log('paste event');
}); Now, for more explanation; I suspect that this part of the javascript code emitted by emscripten might be part of the reason of these issues: // code emitted by emscripten
function registerKeyEventCallback(target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) {
if (!JSEvents.keyEvent)
JSEvents.keyEvent = _malloc(176);
var keyEventHandlerFunc = function(e) {
assert(e);
var keyEventData = JSEvents.keyEvent;
HEAPF64[keyEventData >> 3] = e.timeStamp;
var idx = keyEventData >> 2;
HEAP32[idx + 2] = e.location;
HEAP32[idx + 3] = e.ctrlKey;
HEAP32[idx + 4] = e.shiftKey;
HEAP32[idx + 5] = e.altKey;
HEAP32[idx + 6] = e.metaKey;
HEAP32[idx + 7] = e.repeat;
HEAP32[idx + 8] = e.charCode;
HEAP32[idx + 9] = e.keyCode;
HEAP32[idx + 10] = e.which;
stringToUTF8(e.key || "", keyEventData + 44, 32);
stringToUTF8(e.code || "", keyEventData + 76, 32);
stringToUTF8(e.char || "", keyEventData + 108, 32);
stringToUTF8(e.locale || "", keyEventData + 140, 32);
if (getWasmTableEntry(callbackfunc)(eventTypeId, keyEventData, userData))
e.preventDefault() // is this the reason why Ctrl-V is not handled by the browser?
};
var eventHandler = {
target: findEventTarget(target),
allowsDeferredCalls: true,
eventTypeString: eventTypeString,
callbackfunc: callbackfunc,
handlerFunc: keyEventHandlerFunc,
useCapture: useCapture
};
JSEvents.registerOrRemoveHandler(eventHandler)
} |
@pthom Thanks for the reply. I can confirm that if I do this to my Emscripten SDK installation:
...and re-run my CMake based CLion build, It Works ™️ just fine now 😄 I have no JS experience and I have a hunch that opening a PR to Emscripten GitHub with this patch would just propagate a hack that is omitting a variety of corner cases (Cmd on Mac? 'v' with Capslock on?). What do you think would be the best course of action for Copy-Paste capability to finally cease to be an issue now? |
@Karm : Congrats, this is quite a thorough analysis! You may be onto something inside emscripten! May be you should open an issue in their repository, and mention the solution you have. If someone stumbles upon your issue and reacts/analyzes it, this would be nice. |
I have been following this thread for a long while as a valuable resource on getting copy/paste working in my app. So I wanted to shared how I connected it up to ImGui for the time being: It started out just like the @slowriot 's library suggests by connecting to the ImGui clipboard callback functions, and adding @Karm 's javascript event handler for the keydown event:
Copying now works, but then ImGui does not receive the required key events to paste. Unfortunately there isn't a great way to trigger the paste as each widget checks the io for the paste key combo, so I had to manually add the event with "AddKeyEvent". Luckily the browser delivers this event between ImGui frames, but beware this may not always be the case. (Will also need to check for mac and use command key instead) This works, but the paste will then be repeating, so after a render cycle we check if we manually triggered CTRL + V and then undo it:
I really do not like how fragile this way of faking inputs then cleaning up after feels, but it seems to work for now. |
...and I came back to the project, with a new system, set it all up again, built it and it still doesn't work without that workaround in emscripten-core/emscripten#19510
|
And I open Sokol app like https://floooh.github.io/visual6502remix/, hit Alt+A, assembly edit window opens, and you can copy-paste to and from that window on Linux, Windows, Firefox, Chrome and also in Safari on Mac. |
…#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: - pthom/hello_imgui#3 - emscripten-core/emscripten#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 <[email protected]>
Does anyone know of a solution that takes the approach of mirroring all rendered text to hidden html? It seems like it would be ideal for accessibility, and retains the familiar experience of interacting with html text. Technically I think it would be possible to get the coordinates of every string rendered by imgui, and then each frame update the html so invisible text is displayed at the exact same positions. Thoughts? |
You can check this repo https://github.com/zhobo63/imgui-ts which use a overlay html element to overlay the Imgui::input. In my opinion, you can use text html element to overlay the Imgui::text, so then the text is selectable in browser. |
Tested the webapp on ChromeOS everything worked great. Resize is clean anti-alias is great. However, copy/paste did not seem to work at all. I don't know if this is a feature of your wrapper or something on my side. But Thought it would be good to report.
The text was updated successfully, but these errors were encountered: