Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 112 additions & 1 deletion avogadro/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ endif()
include_directories(${CMAKE_CURRENT_BINARY_DIR})

# if we are building statically then we need HDF5 targets
if(NOT BUILD_SHARED_LIBS)
if(NOT BUILD_SHARED_LIBS AND USE_HDF5)
find_package(HDF5 REQUIRED COMPONENTS C)
endif()

Expand Down Expand Up @@ -208,6 +208,29 @@ endif()

add_executable(avogadro WIN32 MACOSX_BUNDLE
${avogadro_srcs} ${ui_srcs} ${rcc_srcs})
if(EMSCRIPTEN)
find_program(AVOGADRO_GZIP_EXECUTABLE gzip)
target_compile_options(avogadro PRIVATE -pthread)
target_link_options(avogadro PRIVATE
-pthread
--bind
"SHELL:-sMIN_WEBGL_VERSION=2"
"SHELL:-sMAX_WEBGL_VERSION=2"
"SHELL:-sEXPORTED_RUNTIME_METHODS=['specialHTMLTargets','JSEvents']")
if(AVOGADRO_GZIP_EXECUTABLE)
add_custom_command(TARGET avogadro POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE_DIR:avogadro>/$<TARGET_FILE_BASE_NAME:avogadro>.wasm"
"$<TARGET_FILE_DIR:avogadro>/$<TARGET_FILE_BASE_NAME:avogadro>.wasm.gz.tmp"
COMMAND "${AVOGADRO_GZIP_EXECUTABLE}" -9 -f
"$<TARGET_FILE_DIR:avogadro>/$<TARGET_FILE_BASE_NAME:avogadro>.wasm.gz.tmp"
COMMAND "${CMAKE_COMMAND}" -E rename
"$<TARGET_FILE_DIR:avogadro>/$<TARGET_FILE_BASE_NAME:avogadro>.wasm.gz.tmp.gz"
"$<TARGET_FILE_DIR:avogadro>/$<TARGET_FILE_BASE_NAME:avogadro>.wasm.gz"
COMMENT "Compressing $<TARGET_FILE_BASE_NAME:avogadro>.wasm"
VERBATIM)
endif()
endif()
target_link_libraries(avogadro Qt::Widgets Qt::Network Qt::Concurrent)
if(WIN32)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /NODEFAULTLIB:MSVCRTD")
Expand Down Expand Up @@ -240,10 +263,98 @@ if(USE_3DCONNEXION AND (WIN32 OR APPLE))
endif()
endif()

if(EMSCRIPTEN)
file(GENERATE
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/index.html"
CONTENT [[<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Avogadro wasm</title>
<style>
html, body, #screen {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="screen"></div>
<script>
var screen = document.getElementById("screen");
var Module = {
qtContainerElements: [screen],
print: console.log,
printErr: console.error
};
</script>
<script src="$<TARGET_FILE_NAME:avogadro>"></script>
</body>
</html>
]])
file(GENERATE
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/serve-wasm.py"
CONTENT [[#!/usr/bin/env python3
from pathlib import Path
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import urlsplit


class Handler(SimpleHTTPRequestHandler):
extensions_map = {
**SimpleHTTPRequestHandler.extensions_map,
".wasm": "application/wasm",
}

def do_GET(self):
parsed = urlsplit(self.path)
if parsed.path.endswith(".wasm"):
accept_encoding = self.headers.get("Accept-Encoding", "")
compressed_path = Path(self.translate_path(parsed.path + ".gz"))
if "gzip" in accept_encoding and compressed_path.is_file():
self.send_response(200)
self.send_header("Content-Type", "application/wasm")
self.send_header("Content-Encoding", "gzip")
self.send_header("Vary", "Accept-Encoding")
self.send_header("Content-Length", str(compressed_path.stat().st_size))
self.end_headers()
with compressed_path.open("rb") as source:
self.copyfile(source, self.wfile)
return

super().do_GET()

def end_headers(self):
self.send_header("Cross-Origin-Opener-Policy", "same-origin")
self.send_header("Cross-Origin-Embedder-Policy", "require-corp")
self.send_header("Cross-Origin-Resource-Policy", "same-origin")
super().end_headers()


if __name__ == "__main__":
ThreadingHTTPServer(("127.0.0.1", 8000), Handler).serve_forever()
]])
endif()

install(TARGETS avogadro
RUNTIME DESTINATION ${INSTALL_RUNTIME_DIR}
BUNDLE DESTINATION .
)
if(EMSCRIPTEN)
install(FILES "$<TARGET_FILE_DIR:avogadro>/$<TARGET_FILE_BASE_NAME:avogadro>.wasm"
DESTINATION ${INSTALL_RUNTIME_DIR})
if(AVOGADRO_GZIP_EXECUTABLE)
install(FILES "$<TARGET_FILE_DIR:avogadro>/$<TARGET_FILE_BASE_NAME:avogadro>.wasm.gz"
DESTINATION ${INSTALL_RUNTIME_DIR})
endif()
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/index.html"
DESTINATION ${INSTALL_RUNTIME_DIR})
install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/serve-wasm.py"
DESTINATION ${INSTALL_RUNTIME_DIR})
endif()

# Keep "add_subdirectory(lastinstall)" last: fixup_bundle needs to be
# *after* all other install(TARGETS and install(FILES calls
Expand Down
6 changes: 6 additions & 0 deletions avogadro/aboutdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
#include "avogadroappconfig.h"
#include "ui_aboutdialog.h"

#ifndef Q_OS_WASM
#include <QSslSocket>
#endif

#include <avogadro/core/version.h>

Expand Down Expand Up @@ -38,8 +40,12 @@ AboutDialog::AboutDialog(QWidget* parent_)
m_ui->version->setText(html.arg("20").arg(AvogadroApp_VERSION));
m_ui->libsVersion->setText(html.arg("10").arg(version()));
m_ui->qtVersion->setText(html.arg("10").arg(qVersion()));
#ifdef Q_OS_WASM
m_ui->sslVersion->setText(html.arg("10").arg(tr("Not available")));
#else
m_ui->sslVersion->setText(
html.arg("10").arg(QSslSocket::sslLibraryVersionString()));
#endif

// check for light or dark mode
const QPalette defaultPalette;
Expand Down
34 changes: 32 additions & 2 deletions avogadro/avogadro.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,18 @@
#include <QtCore/QLibraryInfo>
#include <QtCore/QLocale>
#include <QtCore/QOperatingSystemVersion>
#ifndef Q_OS_WASM
#include <QtCore/QProcess>
#endif
#include <QtCore/QSettings>
#include <QtCore/QStandardPaths>
#include <QtCore/QTranslator>

// install a message handler (for Windows)
#include <QFile>
#ifndef Q_OS_WASM
#include <QSslSocket>
#endif
#include <QTextStream>

#include <avogadro/core/version.h>
Expand Down Expand Up @@ -104,7 +108,17 @@ void myMessageOutput(QtMsgType type, const QMessageLogContext& context,
// Taken from https://github.com/openscad/openscad/pull/6711
void configureOpenGLContext()
{
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
#if defined(Q_OS_WASM)
auto format = QSurfaceFormat::defaultFormat();
format.setRenderableType(QSurfaceFormat::OpenGLES);
format.setVersion(3, 0);
format.setProfile(QSurfaceFormat::NoProfile);
if (format.depthBufferSize() < 24)
format.setDepthBufferSize(24);
if (format.stencilBufferSize() < 8)
format.setStencilBufferSize(8);
QSurfaceFormat::setDefaultFormat(format);
#elif defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)

if (qEnvironmentVariableIsEmpty("QT_OPENGL")) {
QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
Expand Down Expand Up @@ -172,7 +186,9 @@ int main(int argc, char* argv[])
qDebug() << "Avogadroapp version: " << AvogadroApp_VERSION;
qDebug() << "Avogadrolibs version: " << Avogadro::version();
qDebug() << "Qt version: " << qVersion();
#ifndef Q_OS_WASM
qDebug() << "SSL version: " << QSslSocket::sslLibraryVersionString();
#endif

Avogadro::Application app(argc, argv);

Expand Down Expand Up @@ -204,13 +220,21 @@ int main(int argc, char* argv[])

QStringList translationPaths;
// check environment variable and local paths
#ifdef Q_OS_WASM
const QString translationsEnv = qEnvironmentVariable("AVOGADRO_TRANSLATIONS");
if (!translationsEnv.isEmpty()) {
foreach (const QString& path, translationsEnv.split(':'))
translationPaths << path;
}
#else
foreach (const QString& variable, QProcess::systemEnvironment()) {
QStringList split1 = variable.split('=');
if (split1[0] == "AVOGADRO_TRANSLATIONS") {
foreach (const QString& path, split1[1].split(':'))
translationPaths << path;
}
}
#endif

translationPaths << QLibraryInfo::location(QLibraryInfo::TranslationsPath);
translationPaths << QCoreApplication::applicationDirPath() +
Expand Down Expand Up @@ -342,8 +366,14 @@ int main(int argc, char* argv[])
#if defined(Q_OS_MAC)
defaultFormat.setAlphaBufferSize(8);
#endif
#if defined(Q_OS_WASM)
defaultFormat.setRenderableType(QSurfaceFormat::OpenGLES);
defaultFormat.setVersion(3, 0);
defaultFormat.setProfile(QSurfaceFormat::NoProfile);
#else
defaultFormat.setVersion(4, 0);
defaultFormat.setProfile(QSurfaceFormat::CoreProfile);
#endif
QSurfaceFormat::setDefaultFormat(defaultFormat);

QStringList fileNames;
Expand Down Expand Up @@ -386,7 +416,7 @@ int main(int argc, char* argv[])
#endif
window.show();

#ifdef Avogadro_ENABLE_RPC
#if defined(Avogadro_ENABLE_RPC) && !defined(Q_OS_WASM)
// create rpc listener
Avogadro::RpcListener listener;
listener.start();
Expand Down
11 changes: 11 additions & 0 deletions avogadro/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QMimeData>
#ifndef Q_OS_WASM
#include <QtCore/QProcess>
#endif
#include <QtCore/QRandomGenerator>
#include <QtCore/QSettings>
#include <QtCore/QSortFilterProxyModel>
Expand Down Expand Up @@ -323,6 +325,7 @@ MainWindow::MainWindow(const QStringList& fileNames, bool disableSettings)
}
}

#ifndef Q_OS_WASM
// Clean the pixi cache if it's been more than 30 days
QSettings settings;
QDateTime lastCleaned = settings.value("pixi/lastCacheClean").toDateTime();
Expand All @@ -342,6 +345,7 @@ MainWindow::MainWindow(const QStringList& fileNames, bool disableSettings)
settings.setValue("pixi/lastCacheClean", now);
}
}
#endif

// Scan for pyproject.toml-based plugin packages.
loadPackages();
Expand Down Expand Up @@ -1802,6 +1806,12 @@ QImage MainWindow::renderToImage(const QSize& size)
{
QImage exportImage(size, QImage::Format_ARGB32);

#ifdef Q_OS_WASM
qWarning("Image export is not implemented for the WebAssembly OpenGL window.");
return exportImage;
#endif

#ifndef Q_OS_WASM
auto* glWidget =
qobject_cast<QOpenGLWidget*>(m_multiViewWidget->activeWidget());

Expand Down Expand Up @@ -1850,6 +1860,7 @@ QImage MainWindow::renderToImage(const QSize& size)
if (ok)
exportImage.setText("CML", tmpCml.c_str());
}
#endif

return exportImage;
}
Expand Down
Loading