Initial Commit

This commit is contained in:
Yousef Khadadeh 2025-07-06 20:43:53 +03:00
commit 6088fc29ed
978 changed files with 353647 additions and 0 deletions

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Armchair-Software
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,96 @@
#pragma once
#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&&, 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 detail {
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"]('emscripten_browser_clipboard_detail_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"]('emscripten_browser_clipboard_detail_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));
});
} // namespace detail
inline void paste(paste_handler callback, void *callback_data) {
/// C++ wrapper for javascript paste call
detail::paste_js(callback, callback_data);
}
inline void copy(copy_handler callback, void *callback_data) {
/// C++ wrapper for javascript copy call
detail::copy_js(callback, callback_data);
}
inline void copy(std::string const &content) {
/// C++ wrapper for javascript copy call
detail::copy_async_js(content.c_str());
}
namespace detail {
extern "C" {
EMSCRIPTEN_KEEPALIVE inline int emscripten_browser_clipboard_detail_paste_return(char const *paste_data, paste_handler callback, void *callback_data);
EMSCRIPTEN_KEEPALIVE inline int emscripten_browser_clipboard_detail_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 *emscripten_browser_clipboard_detail_copy_return(copy_handler callback, void *callback_data);
EMSCRIPTEN_KEEPALIVE inline char const *emscripten_browser_clipboard_detail_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);
}
}
} // namespace detail
} // namespace emscripten_browser_clipboard

View file

@ -0,0 +1,130 @@
#pragma once
#include <string>
#include <string_view>
#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_file {
/////////////////////////////////// Interface //////////////////////////////////
using upload_handler = void(*)(std::string const&, std::string const&, std::string_view buffer, void*);
inline void upload(std::string const &accept_types, upload_handler callback, void *callback_data = nullptr);
inline void download(std::string const &filename, std::string const &mime_type, std::string_view buffer);
///////////////////////////////// Implementation ///////////////////////////////
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-variable-declarations"
EM_JS_INLINE(void, upload, (char const *accept_types, upload_handler callback, void *callback_data), {
/// Prompt the browser to open the file selector dialogue, and pass the file to the given handler
/// Accept-types are in the format ".png,.jpeg,.jpg" as per https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept
/// Upload handler callback signature is:
/// void my_handler(std::string const &filename, std::string const &mime_type, std::string_view buffer, void *callback_data = nullptr);
globalThis["open_file"] = function(e) {
const file_reader = new FileReader();
file_reader.onload = (event) => {
const uint8Arr = new Uint8Array(event.target.result);
const data_ptr = Module["_malloc"](uint8Arr.length);
const data_on_heap = new Uint8Array(Module["HEAPU8"].buffer, data_ptr, uint8Arr.length);
data_on_heap.set(uint8Arr);
Module["ccall"]('upload_file_return', 'number', ['string', 'string', 'number', 'number', 'number', 'number'], [event.target.filename, event.target.mime_type, data_on_heap.byteOffset, uint8Arr.length, callback, callback_data]);
Module["_free"](data_ptr);
};
file_reader.filename = e.target.files[0].name;
file_reader.mime_type = e.target.files[0].type;
file_reader.readAsArrayBuffer(e.target.files[0]);
};
var file_selector = document.createElement('input');
file_selector.setAttribute('type', 'file');
file_selector.setAttribute('onchange', 'globalThis["open_file"](event)');
/// The 'cancel' event is fired when the user cancels the currently open dialog.
/// In this case, the upload handler will get the empty string_view.
/// See https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/cancel_event
file_selector.addEventListener('cancel', () => {
Module["ccall"]('upload_file_return', 'number', ['string', 'string', 'number', 'number', 'number', 'number'], ["", "", 0, 0, callback, callback_data]);
});
file_selector.setAttribute('accept', UTF8ToString(accept_types));
/// file_selector.click() approach doesn't work in Safari (tested with native desktop v. 17.5 and iPhone/iPad simulators).
/// It seems that the Safari browser limits programmatical clicking in our case.
/// As a workaround, we create <dialog> where the user manually clicks on <input>.
var is_safari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
if (is_safari) {
var dialog = document.createElement('dialog');
dialog.setAttribute('id', 'EmJsFileDialog');
var desc = document.createElement('p');
desc.innerText = 'Please choose a file. Allowed extension(s): ' + UTF8ToString(accept_types);
dialog.appendChild(desc);
/// We should recreate <dialog> every call; it is the most natural way to reset input.value.
/// Otherwise, if the user re-selects the same file, it triggers the 'cancel' event instead of 'onchange'.
file_selector.setAttribute('onclick', 'var dg = document.getElementById("EmJsFileDialog"); dg.close(); dg.remove()');
dialog.appendChild(file_selector);
document.body.append(dialog);
dialog.showModal();
} else {
/// Not a Safari browser, so file_selector.click() is ok.
file_selector.click();
}
});
#pragma GCC diagnostic pop
inline void upload(std::string const &accept_types, upload_handler callback, void *callback_data) {
/// C++ wrapper for javascript upload call
upload(accept_types.c_str(), callback, callback_data);
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-variable-declarations"
EM_JS_INLINE(void, download, (char const *filename, char const *mime_type, void const *buffer, size_t buffer_size), {
/// Offer a buffer in memory as a file to download, specifying download filename and mime type
var a = document.createElement('a');
a.download = UTF8ToString(filename);
a.href = URL.createObjectURL(new Blob([new Uint8Array(Module["HEAPU8"].buffer, buffer, buffer_size)], {type: UTF8ToString(mime_type)}));
a.click();
});
#pragma GCC diagnostic pop
inline void download(std::string const &filename, std::string const &mime_type, std::string_view buffer) {
/// C++ wrapper for javascript download call, accepting a string_view
download(filename.c_str(), mime_type.c_str(), buffer.data(), buffer.size());
}
namespace {
extern "C" {
EMSCRIPTEN_KEEPALIVE inline int upload_file_return(char const *filename, char const *mime_type, char *buffer, size_t buffer_size, upload_handler callback, void *callback_data);
EMSCRIPTEN_KEEPALIVE inline int upload_file_return(char const *filename, char const *mime_type, char *buffer, size_t buffer_size, upload_handler callback, void *callback_data) {
/// Load a file - this function is called from javascript when the file upload is activated
/// The file was not uploaded.
/// We must process this case separately because std::string_view(nullptr, 0) results in UB.
/// <The behavior is undefined if [s, s + count) is not a valid range
/// (even though the constructor may not access any of the elements of this range)>
/// https://en.cppreference.com/w/cpp/string/basic_string_view/basic_string_view
if (! buffer || buffer_size == 0) {
callback(filename, mime_type, std::string_view(), callback_data);
return 1;
}
/// Ok
callback(filename, mime_type, {buffer, buffer_size}, callback_data);
return 1;
}
}
}
}