Skip to content
  • 0 Votes
    5 Posts
    679 Views
    J

    @Gilboonet it's been a while since you've asked the question, but as I found it then I'll just reply for future generations :)

    Basically you can't directly access any files on your devices from WASM. You need to use Browser API to achieve this, so the only option is to call native browser file picker for open & browser download for saving. You can pick which file to open, but when you save, then it's gonna be downloaded by a browser like any other file from a web (yes, it'll be saved in Downloads folder).

    It seems that Qt also supports file picking on WASM but unfortunately I didn't have a chance to test it yet: https://doc.qt.io/qt-6/qfiledialog.html#getOpenFileContent

    Here's a great example on how to use native file picker in Qt project:

    https://github.com/msorvig/qt-webassembly-examples/tree/master/gui_localfiles

    Here's my code, heavily based on above example, if you're interested:

    std::function<void(const QByteArray &, const QString &)> fileDataReadyCallback; extern "C" EMSCRIPTEN_KEEPALIVE void wasmFileDataReadyCallback(char *content, size_t contentSize, const char *fileName) { if (fileDataReadyCallback == nullptr) return; QByteArray data(content, contentSize); free(content); fileDataReadyCallback(data, QString::fromUtf8(fileName)); fileDataReadyCallback = nullptr; } void WasmPlatformIntegration::openFile() { openFile("*", [](const QByteArray &content, const QString &fileName) { qDebug() << "load file" << fileName << "size" << content.size(); }); } void WasmPlatformIntegration::openFile(const QString &fileNameFilter, std::function<void(const QByteArray &, const QString &)> fileDataReady) { if (::fileDataReadyCallback) puts("Warning: Concurrent loadFile() calls are not supported. Cancelling " "earlier call"); ::fileDataReadyCallback = nullptr; ::wasmFileDataReadyCallback(nullptr, 0, nullptr); ::fileDataReadyCallback = fileDataReady; EM_ASM_( { const fileNameFilter = UTF8ToString($0); // Crate file file input which whil display the native file dialog let fileElement = document.createElement("input"); document.body.appendChild(fileElement); fileElement.type = "file"; fileElement.style = "display:none"; fileElement.accept = fileNameFilter; fileElement.onchange = function(event) { const files = event.target.files; // Read files for (var i = 0; i < files.length; i++) { const file = files[i]; var reader = new FileReader(); reader.onload = function() { const name = file.name; let contentArray = new Uint8Array(reader.result); const contentSize = reader.result.byteLength; const heapPointer = _malloc(contentSize); const heapBytes = new Uint8Array(Module.HEAPU8.buffer, heapPointer, contentSize); heapBytes.set(contentArray); // Null out the first data copy to enable GC reader = null; contentArray = null; // Call the C++ file data ready callback ccall("wasmFileDataReadyCallback", null, [ "number", "number", "string" ], [ heapPointer, contentSize, name ]); }; reader.readAsArrayBuffer(file); } // Clean up document document.body.removeChild(fileElement); }; // onchange callback // Trigger file dialog open fileElement.click(); }, fileNameFilter.toUtf8().constData()); } void WasmPlatformIntegration::saveFile(const QByteArray &content, const QString &fileNameHint) { EM_ASM_( { const contentPointer = $0; const contentLength = $1; const fileNameHint = UTF8ToString($2); const fileContent = Module.HEAPU8.subarray(contentPointer, contentPointer + contentLength); const arrayBuffer = fileContent.slice(); const uint8Array = new Uint8Array(arrayBuffer); uint8Array.fill(255); const fileblob = new Blob([arrayBuffer]); // Create a hidden download link and click it programatically let link = document.createElement("a"); document.body.appendChild(link); link.download = fileNameHint; link.href = window.URL.createObjectURL(fileblob); link.style = "display:none"; link.click(); document.body.removeChild(link); }, content.constData(), content.size(), fileNameHint.toUtf8().constData()); }