Skip to content

Add Qt bindings#76

Draft
d2weber wants to merge 17 commits into
chatmail:mainfrom
d2weber:qt_bindings
Draft

Add Qt bindings#76
d2weber wants to merge 17 commits into
chatmail:mainfrom
d2weber:qt_bindings

Conversation

@d2weber

@d2weber d2weber commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

This adds qt binding generation. It uses the json parsing from qt. A transport-implementation is needed to use it, similar to typescript. I have an implementation for deltachat-cffi but i think it would go in the chatmail-core repo.

Click to expand the CffiTransport implementation
class CffiTransport : public QThread, public Transport {
    Q_OBJECT

public:
    explicit CffiTransport(dc_accounts_t* accounts, QObject* parent = nullptr)
        : QThread(parent)
        , jsonrpc_(dc_jsonrpc_init(accounts))
    {
        if (!jsonrpc_) std::abort();
        start();
    }

    virtual ~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\"}");
        wait();
        QMutexLocker lk(&mu_);
        for (auto& [id, prom] : pending_) {
            prom.set_value({{}, "Transport destructed", -32060});
        }
        pending_.clear();
        if (jsonrpc_) dc_jsonrpc_unref(jsonrpc_);
    }

    virtual std::future<Result<QJsonValue>> send(const QString method, const QJsonValue params) override {
        uint32_t id = next_id_++;
        QJsonObject envelope{
            {"jsonrpc", "2.0"},
            {"id", static_cast<qint64>(id)},
            {"method", method},
            {"params", params},
        };

        std::promise<Result<QJsonValue>> prom;
        std::future<Result<QJsonValue>> fut = prom.get_future();

        {
            QMutexLocker lk(&mu_);
            pending_[id] = std::move(prom);
        }

        QByteArray json = QJsonDocument(envelope).toJson(QJsonDocument::Compact);
        dc_jsonrpc_request(jsonrpc_, json.constData());
        return fut;
    }
protected:
    void run() override {
        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<uint32_t>(obj["id"].toInt());

            std::promise<Result<QJsonValue>> prom;
            {
                QMutexLocker lk(&mu_);
                if (auto nh = pending_.extract(id)) {
                  prom = std::move(nh.mapped());
                } else {
                  qCritical() << "Could not map response" << QString{json};
                  continue;
                }
            }
            prom.set_value(parseResult(obj));
        }
    }

private:
    dc_jsonrpc_instance_t* jsonrpc_;
    QMutex mu_;
    std::atomic<uint32_t> next_id_{1};
    std::atomic<bool> done_{false};
    std::unordered_map<uint32_t, std::promise<Result<QJsonValue>>> pending_;
};

I created a hopefully generally useful TypeInfo type, which can be created from TypeDef::SHAPE. This should allow easier future expansions for other (C-like) languages.

Future improvements: Add docs for generated types. Currently only the rpc methods themself are documentd.


Sidenote: I discarded an ealier draft to create bindings which tried to first implement json parsing on the C-layer with a swap-able json-parser implementation (to support both cjson and qtjson). Then C++ wrappers were added ontop of the C-Layer. But writing safe C code and interop is hard and the generated code was quite involved and hard to understand, all in all it got quite complicated. This approach is much simpler, it just works for qt, but the generated code is straight forward.

Sidenote 2: I also took a look into https://facet.rs/ as a replacement for typescript-type-defs derive(TypeDef). It is an extensible reflection framework and an alternative to serde. https://docs.rs/facet-typescript could in theory replace our typescript generation. I did not investigate further because we'd have to either have to switch to use facet also for json de-/serialization or we'd have to duplicate all the #serde(..) annotations in deltachat-jsonrpc. (Facet is probably slower at runtime than serde.) The nice thing about typescript-type-def is that it reuses the serde annotations.
Another related advencement is reflection and comptime in rust which might make the derives superfluous all together one day.

@d2weber d2weber marked this pull request as draft June 13, 2026 07:48
@d2weber

d2weber commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator Author

I converted to draft because I want to first settle the corresponding usage in deltatouch, see https://codeberg.org/lk108/deltatouch/pulls/269 and chatmail/core#8330

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant