Skip to content

Commit

Permalink
feat(Web): Preliminary support for running qTox in the browser.
Browse files Browse the repository at this point in the history
  • Loading branch information
iphydf committed Jan 28, 2025
1 parent 464939a commit 2065db2
Show file tree
Hide file tree
Showing 13 changed files with 128 additions and 26 deletions.
28 changes: 28 additions & 0 deletions .github/workflows/build-test-deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,34 @@ jobs:
- name: Build qTox
run: .ci-scripts/build-qtox-macos.sh user "${{ matrix.arch }}" "12.0"

build-wasm:
name: WebAssembly
needs: [update-nightly-tag]
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Cache compiler output
uses: actions/cache@v4
with:
path: ".cache/ccache"
key: ${{ github.job }}-ccache
- name: Run build
run: docker compose run --rm wasm-builder platform/wasm/build.sh
- name: Upload zip
uses: actions/upload-artifact@v4
with:
name: qtox-wasm.zip
path: |
_build-wasm/qtloader.js
_build-wasm/qtlogo.svg
_build-wasm/qtox.html
_build-wasm/qtox.js
_build-wasm/qtox.wasm
build-windows:
name: Windows
needs: [update-nightly-tag]
Expand Down
11 changes: 9 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,11 @@ endif()
# Apply resource compilation options.
set(CMAKE_AUTORCC_OPTIONS ${AUTORCC_OPTIONS})

# Disable exceptions (Qt doesn't use them, we don't need them).
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions")
# Emscripten needs -fexceptions at link time.
if(NOT EMSCRIPTEN)
# Disable exceptions (Qt doesn't use them, we don't need them).
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions")
endif()

# Hardening flags (ASLR, warnings, etc)
set(CMAKE_POSITION_INDEPENDENT_CODE True)
Expand Down Expand Up @@ -704,6 +707,10 @@ if(FULLY_STATIC)
target_link_options(${PROJECT_NAME} PRIVATE -static-pie)
endif()

if(EMSCRIPTEN)
target_link_options(${PROJECT_NAME} PRIVATE -sASYNCIFY -lidbfs.js)
endif()

if(BUILD_TESTING)
include(Testing)
endif()
Expand Down
10 changes: 5 additions & 5 deletions audio/src/backend/openal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

#include <cassert>

#if defined(QT_STATIC)
#if defined(QT_STATIC) && !defined(Q_OS_WASM)
extern "C"
{
typedef void alsoftLogCallback(void* userptr, char level, const char* message, int length) noexcept;
Expand All @@ -28,7 +28,7 @@ extern "C"
* @note This function is only available in statically linked builds where we know for sure we
* link against openal-soft.
*/
void alsoft_set_log_callback(alsoftLogCallback* callback, void* userptr) noexcept;
void al_set_log_callback(alsoftLogCallback* callback, void* userptr) noexcept;
}
#endif

Expand Down Expand Up @@ -58,7 +58,7 @@ constexpr unsigned int BUFFER_COUNT = 16;
constexpr uint32_t AUDIO_CHANNELS = 2;

namespace logcat {
#if defined(QT_STATIC)
#if defined(QT_STATIC) && !defined(Q_OS_WASM)
Q_LOGGING_CATEGORY(openal, "openal")
#endif
Q_LOGGING_CATEGORY(audio, "qtox.audio")
Expand All @@ -69,8 +69,8 @@ OpenAL::OpenAL(IAudioSettings& _settings)
: settings{_settings}
, audioThread{new QThread}
{
#if defined(QT_STATIC)
alsoft_set_log_callback(
#if defined(QT_STATIC) && !defined(Q_OS_WASM)
al_set_log_callback(
[](void* userptr, char level, const char* message, int length) noexcept {
std::ignore = userptr;
// OpenAL passes .data() and .size() to the callback,
Expand Down
8 changes: 7 additions & 1 deletion cmake/Dependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ search_dependency(LIBQRENCODE PACKAGE libqrencode)
search_dependency(LIBSWSCALE PACKAGE libswscale)
search_dependency(SQLCIPHER PACKAGE sqlcipher)

if(EMSCRIPTEN)
search_dependency(TOMCRYPT LIBRARY tomcrypt)
endif()

if(APPLE)
search_dependency(LIBCRYPTO PACKAGE libcrypto)
endif()
Expand Down Expand Up @@ -212,7 +216,9 @@ if(NOT WIN32)
endif()

if(QT_FEATURE_static)
add_dependency(Qt6::QOffscreenIntegrationPlugin)
if(NOT EMSCRIPTEN)
add_dependency(Qt6::QOffscreenIntegrationPlugin)
endif()
if(LINUX)
add_dependency(
Qt6::QLinuxFbIntegrationPlugin
Expand Down
3 changes: 3 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,6 @@ services:
windows_builder.x86_64:
image: toxchat/qtox:windows-builder.x86_64
<<: *shared_params
wasm_builder:
image: toxchat/qtox:wasm-builder
<<: *shared_params
17 changes: 17 additions & 0 deletions platform/wasm/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash

source "/work/emsdk/emsdk_env.sh"

export PKG_CONFIG_PATH="/work/lib/pkgconfig"

emcmake cmake \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DCMAKE_FIND_ROOT_PATH="/work;/work/qt" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DSTRICT_OPTIONS=ON \
-DBUILD_TESTING=OFF \
-GNinja \
-B_build-wasm \
-H.

cmake --build _build-wasm
39 changes: 38 additions & 1 deletion src/appmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,34 @@

#include <memory>

#ifdef Q_OS_WASM
#include <emscripten.h>

namespace {
const QString wasmConfigPath = QStringLiteral("/home/web_user/qTox");

bool mountIndexedDbFilesystem()
{
EM_ASM(console.log('Creating config directory: /home/web_user/qTox');
FS.mkdir('/home/web_user/qTox');
// TODO(iphydf): Figure out why this blocks profile creation.
// console.log('Mounting IndexedDB filesystem to /home/web_user/qTox');
// FS.mount(IDBFS, {}, '/home/web_user/qTox');
// console.log('Syncing filesystem');
// FS.syncfs(true, function(err) {
// if (err) {
// console.error('Failed to sync filesystem:', err);
// } else {
// console.log('Filesystem mounted');
// }
// });
);

return true;
}
} // namespace
#endif

namespace {
// logMessageHandler and associated data must be static due to qInstallMessageHandler's
// inability to register a void* to get back to a class
Expand Down Expand Up @@ -249,6 +277,10 @@ int AppManager::startGui(QCommandLineParser& parser)
logFileFile.storeRelaxed(mainLogFilePtr); // atomically set the logFile
#endif

#ifdef Q_OS_WASM
mountIndexedDbFilesystem();
#endif

// Windows platform plugins DLL hell fix
QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath());
QApplication::addLibraryPath("platforms");
Expand Down Expand Up @@ -421,12 +453,17 @@ int AppManager::run()
{{"u", "update-check"}, tr("Checks whether this program is running the latest qTox version.")},
#endif // UPDATE_CHECK_ENABLED
});
#ifdef Q_OS_WASM
// Set to portable mode and TCP-only for WASM.
parser.process({"qtox", "-D", wasmConfigPath, "-U", "off", "-L", "off"});
#else
parser.process(*qapp);
#endif

if (parser.isSet("portable")) {
// We don't go through settings here, because we're not making qTox
// portable (which moves files around). Instead, we start up in
// portable mode as a one-off.
// portable mode from the beginning without having to move any files.
settings->getPaths().setPortable(true);
settings->getPaths().setPortablePath(parser.value("portable"));
}
Expand Down
4 changes: 3 additions & 1 deletion src/core/toxlogger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ void onLogMessage(Tox* tox, Tox_Log_Level level, const char* file, uint32_t line

switch (level) {
case TOX_LOG_LEVEL_TRACE:
return; // trace level generates too much noise to enable by default
// trace level generates too much noise to enable by default
QMessageLogger(file, line, func).debug(toxcore) << message;
return;
case TOX_LOG_LEVEL_DEBUG:
QMessageLogger(file, line, func).debug(toxcore) << message;
break;
Expand Down
22 changes: 11 additions & 11 deletions src/ipc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
#endif

namespace {
#ifndef ANDROID
#if QT_CONFIG(sharedmemory)
#ifdef Q_OS_WIN
const char* getCurUsername()
{
Expand Down Expand Up @@ -58,7 +58,7 @@ QString getIpcKey()

IPC::IPC(uint32_t profileId_)
: profileId{profileId_}
#ifndef ANDROID
#if QT_CONFIG(sharedmemory)
, globalMemory{getIpcKey()}
#endif
{
Expand All @@ -81,7 +81,7 @@ IPC::IPC(uint32_t profileId_)
std::uniform_int_distribution<uint64_t> distribution;
globalId = distribution(rng);
qDebug() << "Our global IPC ID is" << globalId;
#ifdef ANDROID
#if !QT_CONFIG(sharedmemory)
return;
#else
if (globalMemory.create(sizeof(IPCMemory))) {
Expand All @@ -108,7 +108,7 @@ IPC::IPC(uint32_t profileId_)

IPC::~IPC()
{
#ifndef ANDROID
#if QT_CONFIG(sharedmemory)
if (!globalMemory.lock()) {
qWarning() << "Failed to lock in ~IPC";
return;
Expand Down Expand Up @@ -139,7 +139,7 @@ time_t IPC::postEvent(const QString& name, const QByteArray& data, uint32_t dest
return 0;
}

#ifdef ANDROID
#if !QT_CONFIG(sharedmemory)
std::ignore = dest;
return 0;
#else
Expand Down Expand Up @@ -175,7 +175,7 @@ time_t IPC::postEvent(const QString& name, const QByteArray& data, uint32_t dest

bool IPC::isCurrentOwner()
{
#ifdef ANDROID
#if !QT_CONFIG(sharedmemory)
return false;
#else
if (globalMemory.lock()) {
Expand Down Expand Up @@ -207,7 +207,7 @@ void IPC::unregisterEventHandler(const QString& name)

bool IPC::isEventAccepted(time_t time)
{
#ifdef ANDROID
#if !QT_CONFIG(sharedmemory)
std::ignore = time;
return false;
#else
Expand Down Expand Up @@ -250,7 +250,7 @@ bool IPC::waitUntilAccepted(time_t postTime, int32_t timeout /*=-1*/)

bool IPC::isAttached() const
{
#ifdef ANDROID
#if !QT_CONFIG(sharedmemory)
return false;
#else
return globalMemory.isAttached();
Expand Down Expand Up @@ -305,7 +305,7 @@ bool IPC::runEventHandler(IPCEventHandler handler, const QByteArray& arg, void*

void IPC::processEvents()
{
#ifdef ANDROID
#if !QT_CONFIG(sharedmemory)
timer.start();
return;
#else
Expand Down Expand Up @@ -368,7 +368,7 @@ void IPC::processEvents()
*/
bool IPC::isCurrentOwnerNoLock()
{
#ifdef ANDROID
#if !QT_CONFIG(sharedmemory)
return false;
#else
const void* const data = globalMemory.data();
Expand All @@ -382,7 +382,7 @@ bool IPC::isCurrentOwnerNoLock()

IPC::IPCMemory* IPC::global()
{
#ifdef ANDROID
#if !QT_CONFIG(sharedmemory)
return nullptr;
#else
return static_cast<IPCMemory*>(globalMemory.data());
Expand Down
2 changes: 1 addition & 1 deletion src/ipc.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public slots:
QTimer timer;
uint64_t globalId;
uint32_t profileId;
#ifndef ANDROID
#if QT_CONFIG(sharedmemory)
QSharedMemory globalMemory;
#endif
mutable std::mutex eventHandlersMutex;
Expand Down
7 changes: 4 additions & 3 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,17 @@ int main(int argc, char* argv[])
}

#ifdef QT_STATIC
// All platforms support offscreen rendering for testing.
Q_IMPORT_PLUGIN(QOffscreenIntegrationPlugin)

#if defined(Q_OS_LINUX)
Q_IMPORT_PLUGIN(QLinuxFbIntegrationPlugin)
Q_IMPORT_PLUGIN(QOffscreenIntegrationPlugin)
Q_IMPORT_PLUGIN(QVncIntegrationPlugin)
Q_IMPORT_PLUGIN(QWaylandIntegrationPlugin)
Q_IMPORT_PLUGIN(QXcbIntegrationPlugin)
#elif defined(Q_OS_MACOS)
Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin)
Q_IMPORT_PLUGIN(QOffscreenIntegrationPlugin)
#elif defined(Q_OS_WASM)
Q_IMPORT_PLUGIN(QWasmIntegrationPlugin)
#else
#error "No static linking supported for platform"
#endif
Expand Down
1 change: 1 addition & 0 deletions src/persistence/profilelocker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include <QDebug>
#include <QDir>
#include <QFile>

/**
* @class ProfileLocker
Expand Down
2 changes: 1 addition & 1 deletion src/platform/posixsignalnotifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

#include "posixsignalnotifier.h"

#ifndef Q_OS_WIN
#if !defined(Q_OS_WIN) && !defined(Q_OS_WASM)
#include "src/platform/stacktrace.h"

#include <QDebug>
Expand Down

0 comments on commit 2065db2

Please sign in to comment.