From def92a4b9c0b41e3bd08eb70e5c57794fa0c82b5 Mon Sep 17 00:00:00 2001 From: d2weber <29163905+d2weber@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:32:16 +0200 Subject: [PATCH 1/4] feat: Generate qt jsonrpc bindings --- CMakeLists.txt | 68 ++++++----- deltachat-jsonrpc-bindings/.gitignore | 1 + deltachat-jsonrpc-bindings/Cargo.toml | 2 - deltachat-jsonrpc-bindings/build.rs | 3 +- deltachat-jsonrpc-bindings/qt/.gitignore | 1 + .../qt/deltachat-jsonrpc/cffi_client.hpp | 111 ++++++++++++++++++ .../qt/generated/.gitkeep | 0 deltachat-jsonrpc-bindings/src/main.rs | 3 + 8 files changed, 159 insertions(+), 30 deletions(-) create mode 100644 deltachat-jsonrpc-bindings/.gitignore create mode 100644 deltachat-jsonrpc-bindings/qt/.gitignore create mode 100644 deltachat-jsonrpc-bindings/qt/deltachat-jsonrpc/cffi_client.hpp create mode 100644 deltachat-jsonrpc-bindings/qt/generated/.gitkeep create mode 100644 deltachat-jsonrpc-bindings/src/main.rs diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f0f2e4501..435f5e75a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,45 +2,59 @@ cmake_minimum_required(VERSION 3.16) project(deltachat LANGUAGES C) include(GNUInstallDirs) +option(WITH_JSONRPC_BINDINGS "Generate jsonrpc bindings" OFF) + find_program(CARGO cargo) if(APPLE) - set(DYNAMIC_EXT "dylib") + set(DYNAMIC_EXT "dylib") elseif(UNIX) - set(DYNAMIC_EXT "so") + set(DYNAMIC_EXT "so") else() - set(DYNAMIC_EXT "dll") + set(DYNAMIC_EXT "dll") endif() if(DEFINED ENV{CARGO_BUILD_TARGET}) - set(ARCH_DIR "$ENV{CARGO_BUILD_TARGET}") + set(CARGO_OUT_DIR "${CMAKE_BINARY_DIR}/target/$ENV{CARGO_BUILD_TARGET}/release") else() - set(ARCH_DIR "./") + set(CARGO_OUT_DIR "${CMAKE_BINARY_DIR}/target/release") endif() -add_custom_command( - OUTPUT - "${CMAKE_BINARY_DIR}/target/release/libdeltachat.a" - "${CMAKE_BINARY_DIR}/target/release/libdeltachat.${DYNAMIC_EXT}" - "${CMAKE_BINARY_DIR}/target/release/pkgconfig/deltachat.pc" - COMMAND - PREFIX=${CMAKE_INSTALL_PREFIX} - LIBDIR=${CMAKE_INSTALL_FULL_LIBDIR} - INCLUDEDIR=${CMAKE_INSTALL_FULL_INCLUDEDIR} - ${CARGO} build --target-dir=${CMAKE_BINARY_DIR}/target --release - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/deltachat-ffi -) +if(WITH_JSONRPC_BINDINGS) + set(JSONRPC_ARGS --package deltachat-jsonrpc-bindings) +endif() add_custom_target( - lib_deltachat - ALL - DEPENDS - "${CMAKE_BINARY_DIR}/target/release/libdeltachat.a" - "${CMAKE_BINARY_DIR}/target/release/libdeltachat.${DYNAMIC_EXT}" - "${CMAKE_BINARY_DIR}/target/release/pkgconfig/deltachat.pc" + lib_deltachat + ALL + COMMAND + ${CMAKE_COMMAND} -E env + CARGO_TARGET_DIR="${CMAKE_BINARY_DIR}/target" + PREFIX="${CMAKE_INSTALL_PREFIX}" + LIBDIR="${CMAKE_INSTALL_FULL_LIBDIR}" + INCLUDEDIR="${CMAKE_INSTALL_FULL_INCLUDEDIR}" + ${CARGO} build --release --package deltachat_ffi ${JSONRPC_ARGS} + WORKING_DIRECTORY + "${CMAKE_CURRENT_SOURCE_DIR}" ) -install(FILES "deltachat-ffi/deltachat.h" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -install(FILES "${CMAKE_BINARY_DIR}/target/${ARCH_DIR}/release/libdeltachat.a" DESTINATION ${CMAKE_INSTALL_LIBDIR}) -install(FILES "${CMAKE_BINARY_DIR}/target/${ARCH_DIR}/release/libdeltachat.${DYNAMIC_EXT}" DESTINATION ${CMAKE_INSTALL_LIBDIR}) -install(FILES "${CMAKE_BINARY_DIR}/target/${ARCH_DIR}/release/pkgconfig/deltachat.pc" DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +install(FILES "deltachat-ffi/deltachat.h" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") +install(FILES "${CARGO_OUT_DIR}/libdeltachat.a" DESTINATION "${CMAKE_INSTALL_LIBDIR}") +install(FILES "${CARGO_OUT_DIR}/libdeltachat.${DYNAMIC_EXT}" DESTINATION "${CMAKE_INSTALL_LIBDIR}") +install(FILES "${CARGO_OUT_DIR}/pkgconfig/deltachat.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + +if(WITH_JSONRPC_BINDINGS) + install( + FILES + "${CMAKE_CURRENT_SOURCE_DIR}/deltachat-jsonrpc-bindings/qt/generated/types.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/deltachat-jsonrpc-bindings/qt/generated/client.hpp" + DESTINATION + "${CMAKE_INSTALL_INCLUDEDIR}/deltachat-jsonrpc/generated" + ) + install( + FILES + "${CMAKE_CURRENT_SOURCE_DIR}/deltachat-jsonrpc-bindings/qt/deltachat-jsonrpc/cffi_client.hpp" + DESTINATION + "${CMAKE_INSTALL_INCLUDEDIR}/deltachat-jsonrpc" + ) +endif() diff --git a/deltachat-jsonrpc-bindings/.gitignore b/deltachat-jsonrpc-bindings/.gitignore new file mode 100644 index 0000000000..86d4c2dd38 --- /dev/null +++ b/deltachat-jsonrpc-bindings/.gitignore @@ -0,0 +1 @@ +generated diff --git a/deltachat-jsonrpc-bindings/Cargo.toml b/deltachat-jsonrpc-bindings/Cargo.toml index 47c2d9a958..ab837a1862 100644 --- a/deltachat-jsonrpc-bindings/Cargo.toml +++ b/deltachat-jsonrpc-bindings/Cargo.toml @@ -9,8 +9,6 @@ repository = "https://github.com/chatmail/core" [build-dependencies] deltachat-jsonrpc = { workspace = true } -[dependencies] - [features] default = ["vendored"] vendored = ["deltachat-jsonrpc/vendored"] diff --git a/deltachat-jsonrpc-bindings/build.rs b/deltachat-jsonrpc-bindings/build.rs index 073e3f4e16..744a97fec0 100644 --- a/deltachat-jsonrpc-bindings/build.rs +++ b/deltachat-jsonrpc-bindings/build.rs @@ -1,6 +1,7 @@ -use deltachat_jsonrpc::api::write_ts_bindings; +use deltachat_jsonrpc::api::{write_qt_bindings, write_ts_bindings}; use std::path::Path; fn main() { write_ts_bindings(Path::new("typescript/generated")); + write_qt_bindings(Path::new("qt/generated"), "deltachat"); } diff --git a/deltachat-jsonrpc-bindings/qt/.gitignore b/deltachat-jsonrpc-bindings/qt/.gitignore new file mode 100644 index 0000000000..86d4c2dd38 --- /dev/null +++ b/deltachat-jsonrpc-bindings/qt/.gitignore @@ -0,0 +1 @@ +generated diff --git a/deltachat-jsonrpc-bindings/qt/deltachat-jsonrpc/cffi_client.hpp b/deltachat-jsonrpc-bindings/qt/deltachat-jsonrpc/cffi_client.hpp new file mode 100644 index 0000000000..ebe254b351 --- /dev/null +++ b/deltachat-jsonrpc-bindings/qt/deltachat-jsonrpc/cffi_client.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include "deltachat.h" +#include "generated/client.hpp" +#include "generated/types.hpp" + +#include +#include +#include + +namespace deltachat { + +class CffiTransport : public Transport { + using CompletionHandler = Transport::CompletionHandler; + +public: + explicit CffiTransport(dc_accounts_t *accounts) + : jsonrpc_(dc_jsonrpc_init(accounts)) { + if (!jsonrpc_) + std::abort(); + thread_ = std::thread([this] { run(); }); + } + + ~CffiTransport() override { + done_ = true; + // Unblock dc_jsonrpc_next_response by sending a dummy request + if (jsonrpc_) + dc_jsonrpc_request( + jsonrpc_, + "{\"jsonrpc\":\"2.0\",\"id\":0,\"method\":\"get_system_info\"}"); + if (thread_.joinable()) + thread_.join(); + std::lock_guard lk(mu_); + for (auto &[id, cb] : pending_) { + cb(Result::error(-32060, "Transport destructed")); + } + pending_.clear(); + if (jsonrpc_) + dc_jsonrpc_unref(jsonrpc_); + } + + virtual void send(const QString method, const QJsonValue params, + CompletionHandler onCompleted) override { + uint32_t id = next_id_++; + QJsonObject envelope{ + {"jsonrpc", "2.0"}, + {"id", static_cast(id)}, + {"method", method}, + {"params", params}, + }; + + { + std::lock_guard lk(mu_); + pending_[id] = std::move(onCompleted); + } + + QByteArray json = QJsonDocument(envelope).toJson(QJsonDocument::Compact); + dc_jsonrpc_request(jsonrpc_, json.constData()); + } + +private: + void run() { + while (!done_) { + char *raw_json = dc_jsonrpc_next_response(jsonrpc_); + if (!raw_json) { + break; + } + QByteArray json{raw_json}; + dc_str_unref(raw_json); + if (done_) + break; + + QJsonObject obj = QJsonDocument::fromJson(json).object(); + + if (!obj["id"].isDouble()) { + qCritical() << "No valid rpc id in" << QString{json}; + continue; + } + uint32_t id = static_cast(obj["id"].toInt()); + + CompletionHandler cb; + { + std::lock_guard lk(mu_); + if (auto nh = pending_.extract(id)) { + cb = std::move(nh.mapped()); + } else { + qCritical() << "Could not map response" << QString{json}; + continue; + } + } + cb(parseResult(obj)); + } + } + +private: + dc_jsonrpc_instance_t *jsonrpc_; + std::thread thread_; + std::mutex mu_; + std::atomic next_id_{1}; + std::atomic done_{false}; + std::unordered_map pending_; +}; + +class CffiDeltaChat : public RawClient { +public: + explicit CffiDeltaChat(dc_accounts_t *accounts) + : RawClient(std::make_unique(accounts)) {} +}; + +} // namespace deltachat +Q_DECLARE_METATYPE(deltachat::CffiDeltaChat *) diff --git a/deltachat-jsonrpc-bindings/qt/generated/.gitkeep b/deltachat-jsonrpc-bindings/qt/generated/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/deltachat-jsonrpc-bindings/src/main.rs b/deltachat-jsonrpc-bindings/src/main.rs new file mode 100644 index 0000000000..e7a11a969c --- /dev/null +++ b/deltachat-jsonrpc-bindings/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} From 151b1d1339d2413cf61ce74247b19c7478ed0100 Mon Sep 17 00:00:00 2001 From: d2weber <29163905+d2weber@users.noreply.github.com> Date: Thu, 3 Sep 2026 13:44:22 +0200 Subject: [PATCH 2/4] fixup: undo empty [dependecies] removal --- deltachat-jsonrpc-bindings/Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deltachat-jsonrpc-bindings/Cargo.toml b/deltachat-jsonrpc-bindings/Cargo.toml index ab837a1862..47c2d9a958 100644 --- a/deltachat-jsonrpc-bindings/Cargo.toml +++ b/deltachat-jsonrpc-bindings/Cargo.toml @@ -9,6 +9,8 @@ repository = "https://github.com/chatmail/core" [build-dependencies] deltachat-jsonrpc = { workspace = true } +[dependencies] + [features] default = ["vendored"] vendored = ["deltachat-jsonrpc/vendored"] From fa46cae61bd12b016a3b664d90d246a1d6b89293 Mon Sep 17 00:00:00 2001 From: d2weber <29163905+d2weber@users.noreply.github.com> Date: Thu, 3 Sep 2026 14:11:40 +0200 Subject: [PATCH 3/4] fixup: remove unused main.rs --- deltachat-jsonrpc-bindings/src/main.rs | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 deltachat-jsonrpc-bindings/src/main.rs diff --git a/deltachat-jsonrpc-bindings/src/main.rs b/deltachat-jsonrpc-bindings/src/main.rs deleted file mode 100644 index e7a11a969c..0000000000 --- a/deltachat-jsonrpc-bindings/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} From 40199d606b031f5f2fb538f5fdab109eb6f57b11 Mon Sep 17 00:00:00 2001 From: d2weber <29163905+d2weber@users.noreply.github.com> Date: Tue, 8 Sep 2026 21:54:59 +0200 Subject: [PATCH 4/4] fixup: remove superfluous gitignore --- deltachat-jsonrpc-bindings/.gitignore | 1 - 1 file changed, 1 deletion(-) delete mode 100644 deltachat-jsonrpc-bindings/.gitignore diff --git a/deltachat-jsonrpc-bindings/.gitignore b/deltachat-jsonrpc-bindings/.gitignore deleted file mode 100644 index 86d4c2dd38..0000000000 --- a/deltachat-jsonrpc-bindings/.gitignore +++ /dev/null @@ -1 +0,0 @@ -generated