Coder Social home page Coder Social logo

cxx-qt's Introduction

CXX-Qt

Github Book status GitHub Workflow Status License (MIT/Apache2.0) REUSE status Contributor Covenant

Crate Status
cxx-qt docs.rs Crates.io
cxx-qt-build docs.rs Crates.io
cxx-qt-lib docs.rs Crates.io
qt-build-utils docs.rs Crates.io

If you want to get in touch with us, feel free to join our Zulip Chat at: https://cxx-qt.zulipchat.com. There we openly discuss all things about CXX-Qt development.

CXX-Qt is a set of Rust crates for creating bidirectional Rust ⇄ C++ bindings with Qt. It can be used to integrate Rust into C++ applications using CMake or used to build Rust applications with Cargo. CXX-Qt provides tools for implementing QObject subclasses in Rust which can be used from C++, QML, and JavaScript. It consists of two parts:

  • cxx-qt-lib, a library of Rust bindings to common QtCore and QtGui classes made with CXX

  • cxx-qt & cxx-qt-build, a pair of Rust & C++ code generators which are a superset of CXX plus additional attributes to interface with Qt's signals & slots and property system. The cxx-qt crate implements a macro for Rust code generation. cxx-qt-build is used in Cargo build scripts to generate and compile the corresponding C++ code.

The CXX-Qt Book walks through a minimal example step-by-step and documents CXX-Qt's features for the latest release. The examples folder contains demonstrations of using threading, QQmlExtensionPlugin, and various other features.

CXX-Qt is tested on CI on Linux, Windows, and macOS (all on x86_64). It should work on other platforms that Qt and Rust both support, however, these are not tested regularly.

CXX-Qt is in early development and the API changes frequently. For the latest documentation between releases, install mdBook and run mdbook serve --open in the book folder. It will open your own browser. If you need to open it in another browser goto url http://localhost:3000.

Comparison to other Rust Qt bindings

Project Integrate into C++ codebase Safe Rust QML QWidgets Maintained1 Binding mechanism
CXX-Qt limited2 cxx plus additional code generation to implement QObject subclasses in Rust and bind them to C++
qmetaobject cpp macro to write C++ inline in Rust, plus Rust macros to create QObject subclasses from Rust structs
Rust Qt Binding Generator limited2 generates Rust traits and C++ bindings from JSON description of QObject subclass
rust-qt ritual to generate unsafe Rust bindings from C++ headers
qml-rust DOtherSide C wrapper for QML C++ classes
qmlrs own C++ library to bind QQmlApplicationEngine
qmlrsng libqmlbind with bindgen
rust-qml libqmlbind

1: maintained: supports Qt6 and repository has had nontrivial commits within last year as of August 2022

2: CXX-Qt and Rust Qt Binding Generator can be used to implement custom QObjects subclasses in Rust. C++ bindings for these QObject subclasses can be used in QWidgets applications, but these projects do not provide Rust bindings for QWidgets APIs.

Contributing to CXX-Qt

Clone the Git repository

This repository contains symbolic links, which requires some setup on Windows 10 before cloning the repository. First, enable Windows Developer Mode to avoid needing administrator privileges to create symlinks. Then, enable symlinks in Git:

git config --global core.symlinks true

Now clone the Git repository:

git clone https://github.com/KDAB/cxx-qt.git

Building

Ensure that you have the following installed

This repository's build system uses CMake, which calls Cargo under the hood to build all the examples and tests. One example can be built and run with Cargo directly without using CMake: cargo run -p qml-minimal-no-cmake (this example is also built in the CMake build). This example does not link with GNU ld.bfd which is the default linker on most Linux distributions; installing mold, lld, or GNU ld.gold (from GNU binutils but may be separate package) is required on Linux.

On Windows and macOS vcpkg can be used by adding -D VCPKG=ON to the CMake configure step to automatically download release mode packages from GitHub Packages (this will take several minutes the first time you run CMake). Note that debug symbols are not built in these packages.

CXX-Qt defaults to building with Qt6. If you want to build with Qt5 when both are installed, or you want to tell vcpkg to use Qt5, add -D USE_QT5=ON to the CMake configure step.

cmake -S . -B build
cmake --build build

Run the basic QML example

./build/examples/qml_minimal/example_qml_minimal

Testing

Testing assumes that cargo clippy and cargo fmt are available, you may need to install these with rustup component add clippy rustfmt.

For testing the book, it assumes that mdbook and mdbook-linkcheck are installed.

For license and memory testing, it assumes that you have reuse installed (eg via pip3 install reuse) and valgrind.

ctest --test-dir build

Licensing

CXX-Qt is Copyright (C) Klarälvdalens Datakonsult AB, and is available under the terms of the MIT or the Apache-2.0 licenses.

Contact KDAB at [email protected] to inquire about additional features or services related to this project.

The following CMake source files are available under the BSD-3-Clause

About KDAB

CXX-Qt is supported and maintained by Klarälvdalens Datakonsult AB (KDAB).

The KDAB Group is the global No.1 software consultancy for Qt, C++ and OpenGL applications across desktop, embedded and mobile platforms.

The KDAB Group provides consulting and mentoring for developing Qt applications from scratch and in porting from all popular and legacy frameworks to Qt. We continue to help develop parts of Qt and are one of the major contributors to the Qt Project. We can give advanced or standard trainings anywhere around the globe on Qt as well as C++, OpenGL, 3D and more.

Please visit https://www.kdab.com to meet the people who write code like this.

cxx-qt's People

Contributors

ahayzen-kdab avatar alois31 avatar be-ing avatar benfordtytherington avatar dependabot[bot] avatar gerharddc avatar haata avatar jacquetc avatar jimmyvanhest avatar knoxfighter avatar leonmattheskdab avatar mattkdab avatar mmmike avatar montel avatar olivierldff avatar przempore avatar redstrate avatar vimpostor avatar winterz avatar yuja avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cxx-qt's Issues

Can we (de)serialise subobjects somehow

Figure out how sub objects in Data can be (de)serialised ? At the moment subobjects are removed silently from the internal Data, so then the from impl of Data doesn't consider it.

We might need to manually implement the (de)serialise traits to have custom code.

Add support for super as the prefix for CppObj types

Currently we only allow crate::module::CppObj or CppObj as the type. Once we are able to track where a module is relative to the root, consider adding support for super::module::CppObj.

Currently this won't work as we need to build the namespace and other items from the module path, so we require a full module path.

We need the following to be true

  • Starts with crate/super (if more than one part) (TODO: check imports from other crates or modules that are already in use)
  • Ends with CppObj
  • Has one part or three+ parts

Consider if we can allow sub object to be returned

Currently nested objects can be used as properties and parameters. But they cannot be used as return types as we need return a pointer on the C++ side, but also ensure that the ownership is in a valid place (?). Eg we can't construct a sub object inside an invokable and then return it, as no one owns it.

Ability to declare and emit signals

  • Have the ability to write enums for signals
  • Have a handleSignal trait for listening to signals
  • CppObj has emit_queued(Signal) if there is a signal enum, and then uses runOnGUIThread
  • CppObj has unsafe emit_now(Signal) that is called now
  • If there is a handleSignal then in constructor of C++ obj we need to connect signals to a method which proxies to RustObj
  • Extract phase needs
    • signals enum
    • if there is a trait

Rust API would use enums, then have an emit method in the wrapper. We use named struct like enums so that the names can be used for the C++ signal arg.

enum Signal {
  Ready,
  DataChanged { first: i32, second: i32 }
}

impl RustObj {
  #[invokable]
  fn invokable(&self, cpp: &mut CppObj) {
    cpp.emit_queued(Signal::Ready { 1, 2 });
    cpp.emit_queued(Signal::DataChanged { 1, 2 });
  }
}

Internally the emit method destructures the enum and call a relevant C++ method.

impl CppObj {
  fn emit_queued(&mut self, signal: Signal) {
    match signal {
      Signal::Ready => self.cpp.as_mut().emit_ready(),
      Signal::DataChanged(first, second) => self.cpp.as_mut().emit_data_changed(first, second),
    }
  }
}

The public C++ side would then appear as

Q_SIGNALS:
  void ready();
  void dataChanged(qint32 first, qint32 second);

Then the C++ side would have methods for each of these signals to emit them on the GUI thread (these are called from Rust).

void MyObject::emitReady() {
  runOnGUIThread([&]() { Q_EMIT ready(); };
}

void MyObject::emitDataChanged(qint32 first, qint32 second) {
  runOnGUIThread([&]() { Q_EMIT dataChanged(); };
}

The flow is the following for a generic "MySignal" with "number".

# A signal being emitted from Rust
CppObj::emit_queued(Signal::MySignal { number }) -> MyObject::emit_my_signal(number) -> runOnGUIThread -> Q_EMIT MyObject::mySignal(number)

Use full module path in generated rust code to avoid collisions

Use full module path in generated source paths, so src/moda/lib.rs with the contents mod modb { mod modc { our macro around mod my_object } } would become src/moda/modb/modc/my_object.h. This helps later if we allow for generating objects into different QML namespaces.

At the moment it's possible to cause two modules to collide in generation.

There will be fun here around sub objects, might even simplfy code as sub objects use fully qualified names?

Figure out API for supporting list models

We need support for the following items

  • Row struct , which will declare roles (unless we use generic enums from #34 )
  • Have a Model or ItemModel trait which implements the model methods
  • Have support in the macro for defining different base object (eg cxx_qt(QAbstractItemModel))
  • Signal support for emitting signals #31
  • A way to trigger beginInsertRows / remove / reset ?
  • When a signal arrives and we handle it, we need a way to perform model tasks. Eg handle_signal needs to then trigger set_data or insert_row, but this needs to go back to the C++ side and not just be on the rust side like an invokable?
// what happens for qabstractitemmodel, qabstracttablemodel, qstandardmodel etc ?
#[make_qabstractlistmodel]
mod my_model {
    #[derive(Default)]
    pub struct Row {
        first_name: String,
        last_name: String,
    }
 
    #[derive(Default)]
    struct RustObj {
        items: Vec<Row>,
    }
 
    // TODO: need a way to trigger dataChanged() from another thread
 
    // TODO: need a way to trigger beginInsertRows / remove / reset / move etc
 
    impl unsafe Model<CppObj, Role> for RustObj {
        // TODO: what do we return here ? some kind of variant or an enum?
        fn data(&self, index: i32, role: Role) -> Option<Value> {
            if let Some(row) = self.items.get(index) {
                let value = match role {
                    Role::FirstName => row.first_name.into(),
                    Role::LastName => row.last_name.into(),
                }
                Some(value)
            } else {
                None
            }
        }
 
        // TODO: QAbstractItemModel takes a parent: QModelIndex
        fn row_count(&self) -> i32 {
            self.items.len()
        }
 
        fn set_data(&mut self, index: i32, value: Value, role: Role) -> bool {
            if let Some(row) = self.items.get_mut(index) {
                match role {
                    // TODO: how do we do the conversion here?
                    Role::FirstName => row.first_name = value.into(),
                    Role::LastName => row.last_name = value.into(),
                }
 
                // TODO: needs to emit dataChanged()
                // could we have an existing channel to send signals into?
                // or should developers handle this themselves
                self.model.send(Model::DataChanged{ first, last });
 
                true
            } else {
                false
            }
        }
    }

    impl UpdateRequestHandler<CppObj> for RustObj {
        fn handle_update_request(&mut self, cpp: &mut CppObj) {
           // From a custom channel we could perform signal updates here
            cpp.model_data_changed(first, last);
            cpp.model_rows_inserted(first, last);
            cpp.model_rows_moved(first, last);
            cpp.model_rows_removed(first, last);
            cpp.model_reset();
        }
    }
}

Rename C++ members and methods to avoid collisions

Rename C++ members so that more Q_PROPERTY names are available eg use m_internalRustObj, m_propertyObj, m_mutexInternalRustObj, m_mutexPropertyObj etc

So there could be the following prefixes

  • internal
  • property
  • mutex

Also consider if there are any names we cannot avoid collisions and we should error if they are used in Rust (and if this should move to another issue).

Transparently use the CppObjWrapper in invokables and other methods

Currently we have cpp: Pin<&mut CppObj> as the type to signify the Cpp instance. This works until you try to perform multiple mutations, and doesn't allow us to perform the Rust <-> C++ conversions transparently. This also means users can perform tasks that might be considered "unsafe" - eg triggering an invokable via the C++ side rather than Rust (could cause deadlock).

Currently users need to write let wrapper = CppObjWrapper::new(cpp); and then user this wrapper.

Instead use something like cpp: CppObj or cpp: CppObjWrapper as the type the user uses, then transparently generate another rust method which is called first and takes the Pin<&mut CppObj>, then creates the wrapper and calls the users method.

Consider how (de)serialise can work with custom types

For types such as QPoint consider how the user can use these with serde on the Data struct.

Options appear to be

  • Implement derive of serde always
  • Behind a feature implement serde derive helpers
  • Don't implement and force the user to impl the (de)seralise trait for the struct

Have the ability to mark an object as singleton

Add option to mark an option as a singleton in the macro attribute, so make_object(singleton = true) or something.

Once #22 is done this might be cxx_qt(QObject, singleton = true) or cxx_qt(QObject(singleton = true)). Consider if we can hint the QML versions and name here as well.

Have a handle_init / handle_destroy function and trait

Currently there is no way to perform any tasks at creation of the object that require access to the CppObj. (eg starting a background thread that takes the update_requester).

There also isn't a way to tidy up when the object is deconstructed, eg closing a thread / disconnect from db / network.

Add a HandleInit / HandleDestroy trait and method similar to handle_property_change, which is triggered by in the constructor / deconstructor of the object.

  • HandleInit / HandleConstructor trait
  • HandleDestroy / HandleDeconstructor trait

Have a helper to register all types to the QML engine

Currently if you use the QQmlExtensionPlugin all generated types are registered to the QML engine.

But if you don't use the plugin then you currently have to register them manually.

Provide a method somewhere that does this automatically, eg CxxQtTypes::registerTypes(uri).

Method named a_b2 confuses something

If you have a method named a_b2 something gets confused and it looks for a_b_2. This is likely due to the convert_case going wrong somewhere.

Automatically perform the to/from Rust type conversion inside the CppObjWrapper

Once we have #8 the user will always be using the CppObjWrapper. So then in the CppObjWrapper getter/setters etc change the types so that the parameters and return types are the "Rust" types and any conversion is performed inside the CppObjWrapper.

Eg instead of the user needing to change QVariant -> Variant. The CppObjWrapper can do the to_rust() conversion and return Variant.

Remove Reliance on the sizeof(int), etc.

For the conversion of some types, we currently rely on the fact that sizeof(int) is 32 bit wide on most platforms.
Unfortunately, according to the C and C++ standards, this doesn't have to be the case. There might already exist platforms where an "int" is 64 bits wide.

Therefore instead of checking that an int is 32-bit wide, maybe the appropriate action is to change the data type we're using for the Rust side depending on the size of int.

We should probably also look into how CXX deals with this 🤔

Consider which attributes should be on a Q_PROPERTY

Have the ability to declare a #[property(getter = "...", setter = "...", notify = "...")]

For example we can't currently say that a property is readonly to C++/QML, but note that this would require the setter to still be accessible from Rust. Does this simply mean which Q_PROPERTY attributes as set?

Investigate behavior of object references in extern "C" code

Currently we sometimes declare extern "C" functions in C++ that take references as arguments.

As references aren't part of the C language, it's unclear what exactly this code compiles down to. Currently, this doesn't seem to cause issues under GCC, Clang or MSVC, but it is likely compiler dependent.

This is a tracking issue to reference, should this issue come up in the future.
We will not change our code for now, as CXX also uses references in this way, so CXX would need to be changed anyway.

Remove Copy from cxx-qt-lib types and implement Clone properly

  • Remove Copy from structs eg QPointF
  • Don't implement impl From<&QPointF> for QPointF for trivial types
  • Implement Clone for trivial types that uses new constructor on C++ side, eg QPointF::new(x, y)
  • Remove impl From<&QVariant> for Variant for opaque types and just use the to_rust() ?
  • Change the generation in the impl<'a> From<&CppObj<'a>> for Data so that it uses .clone() or .to_rust() for the right values
    • Note that if #9 is implemented the CppObj getter will return a cloned/to_rust object already (?)

Support u64 and i64

Add u64 and i64 support, qint64 and std::int64_t disagree on long long int vs long int so cxx becomes confused. And using just std means QML is confused (we need to register the types?).

Improve and investigate error messages

  • Investigate why errors from cxx-qt-gen don't point to the span - #455
  • Investigate if CXX errors can be exposed better (eg from cxx-qt-build) - #461
  • Investigate why macros don't error in IDEs from cxx-qt-gen errors (this was because i had "rust-analyzer.checkOnSave": false, set)
  • Can we get CXX errors to error in IDEs ? - #461
  • Check if we need to use the dummy env var to improve IDE experience

Original Description

  • Add error messages for invalid invokable input and return types
  • Add error messages for invalid property and invokable names
  • Consider how other messages we emitted when the macros code is wrong etc

Add member functions to basic Qt types in Rust

  • Add missing member functions to types #416
  • Consider if we should adapt the Rust API to be "safe" / less Qt-like (eg QRectF set_x)

Original Description
Types like QPoint have a lot of member functions, as well as operators (i.e. +,-,/,*) that make them a lot more useful. We should give Rust access to these as well.

Change macros to use cxx_qt

Consider changing our macros to always have cxx_qt so we don't collide. This is similar to other crates such as serde. As currently we use macros like make_qobject and invokable these could collide with other crates.

Eg have cxx_qt(QObject), cxx_qt(QAbstractListModel), cxx_qt(invokable), cxx_qt(enum)

Create a Rust book

  • Write existing sections, eg QObject, Data, RustObj, invokable, properties, handle property change, threading update etc
  • Write future sections, eg QAbstractListModel, Roles, enum etc
  • When on Github integration with CI for Github pages? https://rust-lang.github.io/mdBook/continuous-integration.html#deploying-your-book-to-github-pages
  • Ensure we clearly describe which types are trivial and not in a table or something so that users know when to use Variant vs QVariant etc
  • Safety lines in qcolor etc have TODOs which could be linked to an advanced section in the book about internals

https://rust-lang.github.io/mdBook/

Try to enable deny(missing_docs) and fix any ignore code blocks in docs

Try to enable #![deny(missing_docs)] in all crates and ensure any ```ignore are removed from code blocks so we have real code blocks

  • book - this is harder and might need mdbook-keeper rust-lang/mdBook#706 so maybe not worth it as these are mostly included from real examples
  • cxx-qt - #456
  • cxx-qt-build - #456
  • cxx-qt-gen - #880
  • cxx-qt-lib
  • cxx-qt-lib-headers - #456
  • qt-build-utils - #880

https://doc.rust-lang.org/rustdoc/lints.html#missing_docs

Document enabling LTO

Potentially document in the Rust Book how enabling lto in the cargo.toml of the project can reduce the plugin size (eg we went from 5MB → 2MB)

Improve the acceptance test examples

Make the examples more logical so they have a property, parameter, return type for each type. And then have (de)serialise for each type. And then have a sub object test.

Use visibility for invokables rather than attribute

Instead of using #[invokable] to mark which methods should be exposed to Qt, use the visibility.

// This method is exposed to Qt as a Q_INVOKABLE
pub fn qt_invokable() {}

// This method is not exposed to Qt but other Rust objects can use it
pub(crate) fn rust_only_public_method() {}

// This method is private to only this Rust struct
fn rust_only_private_method() {}

Later we might also want to have #[cxx_qt(skip)] to skip pub invokables which could be related to #22 and #25

Ensure all C++ code is pre clang-format

Not all of the gen_cpp code is formatted with clang-format's mozilla style, ensure that we are following that to reduce work by clang-format on our generated C++ code. (eg in the cpp files the return type is on the same line as the method definition not the previous).

Trying to build a object with no Data properties fails

We fail to generate a C++ from Rust File with the error explicit #[repr(...)] is required for enum without any variants, is this due to the Property enum being empty in the CXX bridge?

Solutions are one of the following

  • Add something like #[repr(i32)] to the enum (lose CXX automatic type choosing)
  • Remove enum Property when there aren't any
  • Inject fake value such as "LastProperty" or "NoProperties"

Move CxxQObject to be a member rather than inherited

We should change CxxQObject to be a member of the generated class rather than inherited.

This will then allow us to reuse the same object for other classes we generate (eg non-QObject ones).

This could look something like ThreadHelper m_threadHelper and then m_threadHelper->runOnGUIThread(...).

Use qualified names for C++ includes

Currently our includes are just #include <QColor>, consider using #include <QtGui/QColor> so that it is obvious which Qt modules we need to link to in the cmake.

As this caused compile failures when developing due to missing non-obvious modules.

Support more types in the module

If you try to put a const VAR: T = V inside our macro module then it fails with unsupported type. We should pass these through like we do with use and enum etc.

Implement borrowRustObj() on the CppObjWrapper

Have the ability to perform borrowRustObj on a sub objects CppObjWrapper (not our own)

  • Return Borrow and when Drop occurs it should trigger releaseRustObj
  • Use try_lock in borrowRustObj, panic / return Result<Borrow>, if user wants multithreading they should have their own locking
  • Borrow should take a ref of the CppWrapper and a closure for the impl Drop

This would allow the QML code below, where the tablemodel needs to clone() the database pool on the project RustObj to access the database connection.

Project { id: project; path: "/my/db" }
TableModel { project: project }

Add support for types to QVariant

Primitives - #54

  • bool
  • f32
  • f64
  • i8
  • i16
  • i32
  • String
  • u8
  • u16
  • u32

Qt types - #65

  • QColor
  • QDate
  • QDateTime
  • QTime (?)
  • QPoint
  • QPointF
  • QRect
  • QRectF
  • QSize
  • QSizeF
  • QString (will this be the same / required as String?)
  • QUrl

Consider how to support isValid() === false vs an Unsupported type.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.