├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build.rs ├── examples ├── factorial.rs ├── factorial_string.rs ├── factorial_ui.qml ├── math │ └── mod.rs ├── multiply.rs └── multiply_ui.qml ├── ext └── libqmlrswrapper │ ├── CMakeLists.txt │ ├── libqmlrswrapper.cpp │ ├── libqmlrswrapper.h │ ├── qrsdynamicobject.cpp │ ├── qrsdynamicobject.h │ └── qrsdynamicobject_capi.cpp ├── src ├── ffi.rs ├── lib.rs ├── macros.rs └── variant.rs └── tests ├── simple_test.qml └── simple_test.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | ext/libqmlrswrapper/build 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # qmlrs needs the Ubuntu 14.04 trusty beta image to get a recent Qt5 (trusty ships with Qt 5.2) 2 | dist: trusty 3 | # the trusty beta is only available as sudo 4 | sudo: required 5 | 6 | language: rust 7 | rust: 8 | - stable 9 | - beta 10 | - nightly 11 | matrix: 12 | allow_failures: 13 | - rust: nightly 14 | 15 | install: 16 | - sudo apt-get update -qq 17 | # requirements of qmlrs 18 | - sudo apt-get install -qq qtbase5-dev libqt5gui5 libqt5quick5 libqt5qml5 qtdeclarative5-dev qtdeclarative5-qtquick2-plugin cmake 19 | # requirements of travis-cargo 20 | - sudo apt-get install -qq libcurl4-openssl-dev libelf-dev libdw-dev binutils-dev 21 | 22 | 23 | # travis-cargo is a wrapper around cargo we use for code coverage generation 24 | # https://github.com/huonw/travis-cargo 25 | before_script: 26 | - | 27 | pip install 'travis-cargo<0.2' --user && 28 | export PATH=$HOME/.local/bin:$PATH 29 | 30 | script: 31 | - cargo build --verbose 32 | - cargo build --verbose --example factorial 33 | - cargo build --verbose --example factorial_string 34 | - cargo build --verbose --example multiply 35 | # this is to stop cargo from hiding stdout of tests, useful when strange things happen and tests run forever. 36 | # cargo test -- --nocapture 37 | - cargo test --verbose 38 | - cargo bench --verbose 39 | 40 | after_success: 41 | # measure code coverage and upload to coveralls.io 42 | - travis-cargo coveralls --no-sudo --verify 43 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log # 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## 0.1.1 - 2016-03-09 ## 6 | This release was brought to you thanks to @Kroisse and the excellent internet connection in the Tōkaidō Shinkansen line. 7 | ### Fixed ### 8 | - fixed #39: increased required `pkg-config` version to `0.3.8` to include a fix for a MacOSX Compile Error 9 | 10 | ## 0.1.0 - 2016-02-07 ## 11 | ### Added ### 12 | - Added a C++ wrapper around common QML-related features of Qt5, but not everything is already exposed to Rust code. 13 | - Rust code can: 14 | - load QML code from files and strings. 15 | - execute QML code via QQmlApplicationEngine. 16 | - expose methods of Rust structs to QML code as context properties. 17 | - Supported QVariant types that get converted automatically to Rust equivalents: Int64, Bool, String 18 | - Now compiles cleanly on Linux, MacOS X, Windows & FreeBSD. 19 | 20 | 21 | This uses [Keep a CHANGELOG](http://keepachangelog.com/) as a template. 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "qmlrs" 3 | version = "0.1.1" 4 | authors = ["Mikko Perttunen "] 5 | 6 | description = "qmlrs - QtQuick bindings for Rust" 7 | keywords = ["QML", "QtQuick", "Qt", "GUI"] 8 | license = "MIT/Apache-2.0" 9 | repository = "https://github.com/cyndis/qmlrs" 10 | readme = "README.md" 11 | 12 | build = "build.rs" 13 | 14 | [dependencies] 15 | libc = "^0.2.6" 16 | 17 | [build-dependencies] 18 | pkg-config = "^0.3.8" 19 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Mikko Perttunen 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qmlrs - [QtQuick](http://doc.qt.io/qt-5/qtquick-index.html) bindings for Rust 2 | 3 | [![Travis Build Status](https://travis-ci.org/cyndis/qmlrs.svg?branch=master)](https://travis-ci.org/cyndis/qmlrs) 4 | [![Coverage Status](https://coveralls.io/repos/github/cyndis/qmlrs/badge.svg?branch=master)](https://coveralls.io/github/cyndis/qmlrs?branch=master) 5 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE-MIT) 6 | [![Apache licensed](https://img.shields.io/badge/license-Apache-blue.svg)](./LICENSE-APACHE) 7 | [![crates.io](https://img.shields.io/crates/v/qmlrs.svg)](https://crates.io/crates/qmlrs) 8 | 9 | ![Image of example](https://raw.githubusercontent.com/cyndis/qmlrs/ghstatic/screenshot.png) 10 | 11 | qmlrs allows the use of QML/QtQuick code from Rust, specifically 12 | 13 | - Rust code can create a QtQuick engine (QQmlApplicationEngine) with a loaded QML script 14 | - QML code can invoke Rust functions 15 | 16 | …with certain limitations. The library should be safe (as in not `unsafe`) to use, but no promises 17 | at this time. Reviews of the code would be welcome. 18 | 19 | ## News 20 | 21 | See the [Changelog](./CHANGELOG.md) for the version history and what's waiting in master to be released. 22 | 23 | ## Requirements 24 | 25 | The library consists of a Rust part and a C++ part. The C++ part will be compiled automatically 26 | when building with Cargo. You will need `cmake`, Qt5 and a C++ compiler that can compile Qt5 code. 27 | Your Qt5 installation should have at least the following modules: Core, Gui, Qml, Quick and Quick Controls. 28 | 29 | If you are installing Qt5 from source, please note that passing "-noaccessibility" to the configure 30 | script disables the qtquickcontrols module. 31 | 32 | ## Usage 33 | 34 | Add the latest version of qmlrs from [crates.io](https://crates.io/crates/qmlrs/) in your project's `Cargo.toml`. 35 | 36 | ## Example 37 | 38 | This is the Rust code for an application allowing the calculation of factorials. 39 | You can find the corresponding QML code in the `examples` directory. 40 | 41 | ```rust 42 | #[macro_use] 43 | extern crate qmlrs; 44 | 45 | struct Factorial; 46 | impl Factorial { 47 | fn calculate(&self, x: i64) -> i64 { 48 | (1..x+1).fold(1, |t,c| t * c) 49 | } 50 | } 51 | 52 | Q_OBJECT! { Factorial: 53 | slot fn calculate(i64); 54 | } 55 | 56 | fn main() { 57 | let mut engine = qmlrs::Engine::new(); 58 | 59 | engine.set_property("factorial", Factorial); 60 | engine.load_local_file("examples/factorial_ui.qml"); 61 | 62 | engine.exec(); 63 | } 64 | 65 | ``` 66 | To run the above example, execute `cargo run --example factorial` in the project's root directory. 67 | 68 | ## Note regarding the Qt event loop and threads 69 | 70 | Creating an `Engine` automatically initializes the Qt main event loop if one doesn't already exist. 71 | At least on some operating systems, the event loop must run on the main thread. Qt will tell you 72 | if you mess up. The `.exec()` method on views starts the event loop. This will block the thread 73 | until the window is closed. 74 | 75 | ## Licensing 76 | 77 | The code in this library is dual-licensed under the MIT license and the Apache License (version 2.0). 78 | See [LICENSE-APACHE](./LICENSE-APACHE) and [LICENSE-MIT](./LICENSE-MIT) for details. 79 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | extern crate pkg_config; 2 | 3 | use std::process::Command; 4 | use std::fs; 5 | use std::path::Path; 6 | use std::env; 7 | use std::env::consts; 8 | use std::path::PathBuf; 9 | 10 | fn main() { 11 | let wcd = env::current_dir().unwrap(); 12 | let build = PathBuf::from(&wcd.join("ext/libqmlrswrapper/build")); 13 | 14 | let _ = fs::create_dir_all(&build); 15 | 16 | let mut myargs = vec![".."] ; 17 | 18 | /* 19 | * Support Qt installed via the Ports system on BSD-like systems. 20 | * 21 | * The native libs are in `/usr/local/lib`, which is not linked against by default. 22 | * This means that either the user or every package has to add this if they want to link 23 | * against something that is not part of the core distribution in `/usr/lib`. 24 | * 25 | * See https://wiki.freebsd.org/WarnerLosh/UsrLocal for the line of reasoning & how this will 26 | * change in the future. 27 | */ 28 | if cfg!(any(target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", 29 | target_os = "dragonfly", target_os = "bitrig")) { 30 | println!("cargo:rustc-link-search=native=/usr/local/lib"); 31 | } 32 | 33 | 34 | /* 35 | * Parameters for supporting QT on OS X 36 | * 37 | * Because QT5 conflicts with QT4 the homebrew package manager won't link 38 | * the QT5 package into the default search paths for libraries, to deal 39 | * with this we need to give pkg-config and cmake a nudge in the right 40 | * direction. 41 | */ 42 | if cfg!(target_os = "macos") { 43 | // We use the QTDIR or QTDIR64 env variables to find the location of 44 | // Qt5. If these are not set, we use the default homebrew install 45 | // location. 46 | let qtdir_variable = match consts::ARCH { 47 | "x86_64" => "QTDIR64", 48 | _ => "QTDIR", 49 | }; 50 | let mut qt5_lib_path = PathBuf::new(); 51 | qt5_lib_path.push(env::var(qtdir_variable).unwrap_or(String::from("/usr/local/opt/qt5"))); 52 | qt5_lib_path.push(Path::new("lib")); 53 | 54 | if qt5_lib_path.exists() { 55 | // First nudge cmake in the direction of the .cmake files added by 56 | // homebrew. This clobbers the existing value if present, it's 57 | // unlikely to be present though. 58 | env::set_var("CMAKE_PREFIX_PATH", qt5_lib_path.join("cmake")); 59 | 60 | // Nudge pkg-config in the direction of the brewed QT to ensure the 61 | // correct compiler flags get found for the project. 62 | env::set_var("PKG_CONFIG_PATH", qt5_lib_path.join("pkgconfig")); 63 | } else { 64 | panic!("QT5 was not found at the expected location ({}) please install it via homebrew, or set the {} env variable.", 65 | qt5_lib_path.display(), qtdir_variable); 66 | } 67 | } 68 | 69 | let is_msys = env::var("MSYSTEM").is_ok() ; 70 | if cfg!(windows) && is_msys { 71 | myargs.push("-GMSYS Makefiles") ; 72 | } 73 | 74 | let cmake_output = Command::new("cmake").args(&myargs).current_dir(&build).output().unwrap_or_else(|e| { 75 | panic!("Failed to run cmake: {}", e); 76 | }); 77 | let cmake_stderr = String::from_utf8(cmake_output.stderr).unwrap(); 78 | if !cmake_stderr.is_empty() { 79 | panic!("cmake produced stderr: {}", cmake_stderr); 80 | } 81 | 82 | Command::new("make").current_dir(&build).output().unwrap_or_else(|e| { 83 | panic!("Failed to run make: {}", e); 84 | }); 85 | 86 | if cfg!(windows) && is_msys { 87 | println!("cargo:rustc-link-search=native={}\\system32",env::var("WINDIR").unwrap()); 88 | } 89 | println!("cargo:rustc-link-lib=static=qmlrswrapper"); 90 | println!("cargo:rustc-link-lib=dylib=stdc++"); 91 | println!("cargo:rustc-link-search=native={}",build.display()); 92 | pkg_config::find_library("Qt5Core Qt5Gui Qt5Qml Qt5Quick").unwrap(); 93 | } 94 | -------------------------------------------------------------------------------- /examples/factorial.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate qmlrs; 3 | mod math; 4 | 5 | fn main() { 6 | let mut engine = qmlrs::Engine::new(); 7 | 8 | engine.set_property("factorial", math::Factorial); 9 | engine.load_local_file("examples/factorial_ui.qml"); 10 | 11 | engine.exec(); 12 | } 13 | -------------------------------------------------------------------------------- /examples/factorial_string.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate qmlrs; 3 | mod math; 4 | 5 | use std::fs::File; 6 | use std::io::prelude::*; 7 | 8 | fn main() { 9 | let mut engine = qmlrs::Engine::new(); 10 | engine.set_property("factorial", math::Factorial); 11 | let mut qml_file = File::open("examples/factorial_ui.qml").unwrap(); 12 | let mut qml_string = String::new(); 13 | qml_file.read_to_string(&mut qml_string).unwrap(); 14 | qml_string = qml_string.replace("Factorial", "Factorial (from string)"); 15 | engine.load_data(&qml_string); 16 | 17 | engine.exec(); 18 | } 19 | -------------------------------------------------------------------------------- /examples/factorial_ui.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.2 2 | import QtQuick.Controls 1.2 3 | import QtQuick.Layouts 1.0 4 | 5 | ApplicationWindow { 6 | visible: true 7 | title: "Factorial" 8 | 9 | property int margin: 11 10 | width: mainLayout.implicitWidth + 2 * margin 11 | height: mainLayout.implicitHeight + 2 * margin 12 | minimumWidth: mainLayout.Layout.minimumWidth + 2 * margin 13 | minimumHeight: mainLayout.Layout.minimumHeight + 2 * margin 14 | 15 | ColumnLayout { 16 | id: mainLayout 17 | anchors.fill: parent 18 | anchors.margins: margin 19 | 20 | RowLayout { 21 | TextField { 22 | id: numberField 23 | Layout.fillWidth: true 24 | 25 | placeholderText: "Enter number" 26 | focus: true 27 | 28 | onAccepted: doCalculate() 29 | } 30 | 31 | Button { 32 | text: "Calculate" 33 | 34 | onClicked: doCalculate() 35 | } 36 | } 37 | 38 | TextArea { 39 | id: resultArea 40 | Layout.fillWidth: true 41 | Layout.fillHeight: true 42 | } 43 | } 44 | 45 | function doCalculate() { 46 | var num = parseInt(numberField.text); 47 | resultArea.text = factorial.calculate(num); 48 | } 49 | 50 | /* 51 | Connections { 52 | target: factorial 53 | onTest: console.log("Got test signal!") 54 | } 55 | */ 56 | } 57 | -------------------------------------------------------------------------------- /examples/math/mod.rs: -------------------------------------------------------------------------------- 1 | extern crate qmlrs; 2 | 3 | pub struct Factorial; 4 | impl Factorial { 5 | pub fn calculate(&self, x: i64) -> i64 { 6 | (1..x+1).fold(1, |t,c| t * c) 7 | } 8 | } 9 | 10 | Q_OBJECT! { Factorial: 11 | slot fn calculate(i64); 12 | // signal fn test(); 13 | } 14 | -------------------------------------------------------------------------------- /examples/multiply.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate qmlrs; 3 | 4 | 5 | struct Multiply; 6 | impl Multiply { 7 | fn calculate(&self, x: i64, y: i64) -> i64 { 8 | x * y 9 | } 10 | } 11 | 12 | Q_OBJECT! { Multiply: 13 | slot fn calculate(i64, i64); 14 | } 15 | 16 | fn main() { 17 | let mut engine = qmlrs::Engine::new(); 18 | 19 | engine.set_property("multiply", Multiply); 20 | engine.load_local_file("examples/multiply_ui.qml"); 21 | 22 | engine.exec(); 23 | } 24 | -------------------------------------------------------------------------------- /examples/multiply_ui.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.2 2 | import QtQuick.Controls 1.2 3 | import QtQuick.Layouts 1.0 4 | 5 | ApplicationWindow { 6 | visible: true 7 | title: "Multiply" 8 | 9 | property int margin: 11 10 | width: mainLayout.implicitWidth + 2 * margin 11 | height: mainLayout.implicitHeight + 2 * margin 12 | minimumWidth: mainLayout.Layout.minimumWidth + 2 * margin 13 | minimumHeight: mainLayout.Layout.minimumHeight + 2 * margin 14 | 15 | ColumnLayout { 16 | id: mainLayout 17 | anchors.fill: parent 18 | anchors.margins: margin 19 | 20 | RowLayout { 21 | TextField { 22 | id: number1Field 23 | Layout.fillWidth: true 24 | 25 | placeholderText: "Enter number" 26 | focus: true 27 | 28 | onAccepted: doCalculate() 29 | } 30 | 31 | Label { 32 | text: "*" 33 | } 34 | 35 | TextField { 36 | id: number2Field 37 | Layout.fillWidth: true 38 | placeholderText: "Enter number" 39 | 40 | onAccepted: doCalculate() 41 | } 42 | 43 | Button { 44 | text: "Calculate" 45 | 46 | onClicked: doCalculate() 47 | } 48 | } 49 | 50 | TextArea { 51 | id: resultArea 52 | Layout.fillWidth: true 53 | Layout.fillHeight: true 54 | } 55 | } 56 | 57 | function doCalculate() { 58 | var num1 = parseInt(number1Field.text); 59 | var num2 = parseInt(number2Field.text); 60 | resultArea.text = multiply.calculate(num1, num2); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /ext/libqmlrswrapper/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.11) 2 | project(qmlrswrapper) 3 | 4 | set(CMAKE_BUILD_TYPE RELEASE) 5 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 6 | set(CMAKE_AUTOMOC ON) 7 | 8 | include(CheckCXXCompilerFlag) 9 | CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) 10 | CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) 11 | if(COMPILER_SUPPORTS_CXX11) 12 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 13 | elseif(COMPILER_SUPPORTS_CXX0X) 14 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") 15 | else() 16 | message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.") 17 | endif() 18 | 19 | find_package(Qt5Core) 20 | find_package(Qt5Quick) 21 | 22 | set(qmlrswrapper_SRCS libqmlrswrapper.cpp qrsdynamicobject.cpp qrsdynamicobject_capi.cpp) 23 | 24 | add_library(qmlrswrapper STATIC ${qmlrswrapper_SRCS}) 25 | target_link_libraries(qmlrswrapper Qt5::Core Qt5::Quick) 26 | 27 | install(TARGETS qmlrswrapper ARCHIVE DESTINATION lib) 28 | -------------------------------------------------------------------------------- /ext/libqmlrswrapper/libqmlrswrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "libqmlrswrapper.h" 2 | 3 | #include "qrsdynamicobject.h" 4 | 5 | #include 6 | #include 7 | 8 | #define rust_fun extern "C" 9 | 10 | rust_fun QrsApplicationEngine *qmlrs_create_engine_headless() { 11 | if (!QCoreApplication::instance()) { 12 | char *arg = (char *)malloc(13); 13 | strcpy(arg, "qmlrswrapper"); 14 | char **argp = (char **)malloc(sizeof(char *)); 15 | *argp = arg; 16 | 17 | int *argc = (int *)malloc(sizeof(int)); 18 | *argc = 1; 19 | 20 | new QCoreApplication(*argc, argp); 21 | } 22 | 23 | return new QrsApplicationEngine(); 24 | } 25 | 26 | rust_fun QrsApplicationEngine *qmlrs_create_engine() { 27 | if (!QGuiApplication::instance()) { 28 | char *arg = (char *)malloc(13); 29 | strcpy(arg, "qmlrswrapper"); 30 | char **argp = (char **)malloc(sizeof(char *)); 31 | *argp = arg; 32 | 33 | int *argc = (int *)malloc(sizeof(int)); 34 | *argc = 1; 35 | 36 | new QGuiApplication(*argc, argp); 37 | } 38 | 39 | return new QrsApplicationEngine(); 40 | } 41 | 42 | rust_fun void qmlrs_destroy_engine(QrsApplicationEngine *engine) { 43 | delete engine; 44 | } 45 | 46 | rust_fun void qmlrs_engine_load_url(QrsApplicationEngine *engine, const char *path, unsigned int len) { 47 | engine->load(QUrl(QString::fromUtf8(path, len))); 48 | } 49 | 50 | rust_fun void qmlrs_engine_load_from_data(QrsApplicationEngine *engine, const char *data, unsigned int len) { 51 | engine->loadData(QByteArray::fromRawData(data, len), QUrl()); 52 | } 53 | 54 | rust_fun void qmlrs_engine_invoke(QrsApplicationEngine *engine, const char *method, 55 | QVariant *result, const QVariantList *args) 56 | { 57 | if (args->size() > 10) { 58 | qFatal("Cannot invoke method with more than 10 arguments"); 59 | } 60 | 61 | QVariant returned; 62 | QMetaObject::invokeMethod(engine, "invokeQmlSlot", Q_RETURN_ARG(QVariant, returned), 63 | Q_ARG(QString, QString::fromUtf8(method)), 64 | Q_ARG(QVariantList, *args)); 65 | *result = returned; 66 | } 67 | 68 | rust_fun void qmlrs_engine_set_property(QrsApplicationEngine *engine, const char *name, uint len, 69 | QObject *object) { 70 | engine->rootContext()->setContextProperty(QString::fromUtf8(name, len), object); 71 | } 72 | 73 | rust_fun QVariantList *qmlrs_varlist_create() { 74 | return new QVariantList(); 75 | } 76 | 77 | rust_fun void qmlrs_varlist_destroy(QVariantList *list) { 78 | delete list; 79 | } 80 | 81 | rust_fun QVariant *qmlrs_varlist_push(QVariantList *list) { 82 | list->append(QVariant()); 83 | return (QVariant *)&list->last(); 84 | } 85 | 86 | rust_fun unsigned int qmlrs_varlist_length(const QVariantList *list) { 87 | return list->size(); 88 | } 89 | 90 | rust_fun QVariant *qmlrs_varlist_get(const QVariantList *list, unsigned int i) { 91 | return (QVariant *)&(*list)[i]; 92 | } 93 | 94 | rust_fun void qmlrs_app_exec() { 95 | QGuiApplication::exec(); 96 | } 97 | 98 | rust_fun void qmlrs_variant_set_int64(QVariant *v, int64_t x) { 99 | *v = QVariant((qlonglong)x); 100 | } 101 | 102 | rust_fun void qmlrs_variant_set_bool(QVariant *v, bool x) { 103 | *v = QVariant(x); 104 | } 105 | 106 | rust_fun void qmlrs_variant_set_invalid(QVariant *v) { 107 | *v = QVariant(); 108 | } 109 | 110 | rust_fun void qmlrs_variant_set_string(QVariant *v, unsigned int len, const char *data) { 111 | *v = QVariant(QString::fromUtf8(data, len)); 112 | } 113 | 114 | rust_fun QVariant *qmlrs_variant_create() { 115 | return new QVariant(); 116 | } 117 | 118 | rust_fun void qmlrs_variant_destroy(QVariant *v) { 119 | delete v; 120 | } 121 | 122 | enum QrsVariantType { 123 | Invalid = 0, Int64, Bool, String 124 | }; 125 | 126 | rust_fun QrsVariantType qmlrs_variant_get_type(const QVariant *v) { 127 | if (!v->isValid()) 128 | return Invalid; 129 | 130 | if (v->type() == (QVariant::Type)QMetaType::QString) 131 | return String; 132 | 133 | if (v->canConvert(QMetaType::LongLong)) 134 | return Int64; 135 | 136 | if (v->canConvert(QMetaType::Bool)) 137 | return Bool; 138 | 139 | /* Unknown type, not supported on Rust side */ 140 | return Invalid; 141 | } 142 | 143 | rust_fun void qmlrs_variant_get_int64(const QVariant *v, int64_t *x) { 144 | *x = v->toLongLong(); 145 | } 146 | 147 | rust_fun void qmlrs_variant_get_bool(const QVariant *v, bool *x) { 148 | *x = v->toBool(); 149 | } 150 | 151 | rust_fun void qmlrs_variant_get_string_length(const QVariant *v, unsigned int *len) { 152 | *len = v->toString().toUtf8().size(); 153 | } 154 | 155 | rust_fun void qmlrs_variant_get_string_data(const QVariant *v, char *data) { 156 | QByteArray ba = v->toString().toUtf8(); 157 | memcpy(data, ba.data(), ba.size()); 158 | } 159 | 160 | QrsApplicationEngine::QrsApplicationEngine() 161 | { 162 | } 163 | 164 | QVariant QrsApplicationEngine::invokeQmlSlot(QString name, QVariantList args) { 165 | QObject *root = rootObjects().first(); 166 | 167 | QVariant returned; 168 | 169 | QGenericArgument a0, a1, a2, a3, a4, a5, a6, a7, a8, a9; 170 | if (args.size() > 9) a9 = Q_ARG(QVariant, args[9]); 171 | if (args.size() > 8) a8 = Q_ARG(QVariant, args[8]); 172 | if (args.size() > 7) a7 = Q_ARG(QVariant, args[7]); 173 | if (args.size() > 6) a6 = Q_ARG(QVariant, args[6]); 174 | if (args.size() > 5) a5 = Q_ARG(QVariant, args[5]); 175 | if (args.size() > 4) a4 = Q_ARG(QVariant, args[4]); 176 | if (args.size() > 3) a3 = Q_ARG(QVariant, args[3]); 177 | if (args.size() > 2) a2 = Q_ARG(QVariant, args[2]); 178 | if (args.size() > 1) a1 = Q_ARG(QVariant, args[1]); 179 | if (args.size() > 0) a0 = Q_ARG(QVariant, args[0]); 180 | 181 | QMetaObject::invokeMethod(root, name.toUtf8(), Q_RETURN_ARG(QVariant, returned), 182 | a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); 183 | 184 | return returned; 185 | } 186 | -------------------------------------------------------------------------------- /ext/libqmlrswrapper/libqmlrswrapper.h: -------------------------------------------------------------------------------- 1 | #ifndef libqmlrswrapper_H 2 | #define libqmlrswrapper_H 3 | 4 | #include 5 | 6 | typedef void *(QrsSlotFun)(const char *name, void *data, QVariant *result, QVariantList *args); 7 | 8 | class QrsInterface; 9 | 10 | class QrsApplicationEngine : public QQmlApplicationEngine { 11 | Q_OBJECT 12 | 13 | public: 14 | QrsApplicationEngine(); 15 | 16 | public slots: 17 | QVariant invokeQmlSlot(QString name, QVariantList args); 18 | }; 19 | 20 | #endif // libqmlrswrapper_H 21 | -------------------------------------------------------------------------------- /ext/libqmlrswrapper/qrsdynamicobject.cpp: -------------------------------------------------------------------------------- 1 | #include "qrsdynamicobject.h" 2 | 3 | QrsDynamicMetaObject::QrsDynamicMetaObject() 4 | : _mo(NULL) 5 | { 6 | } 7 | 8 | QrsDynamicMetaObject::~QrsDynamicMetaObject() 9 | { 10 | if (_mo) { 11 | delete _mo->d.stringdata; 12 | delete _mo->d.data; 13 | delete _mo; 14 | } 15 | } 16 | 17 | void qrsStaticDynamicMetacall(QObject *qobj, QMetaObject::Call c, int id, void **a) { 18 | qobj->qt_metacall(c, id, a); 19 | } 20 | 21 | void QrsDynamicMetaObject::finalize() 22 | { 23 | _mo = new QMetaObject; 24 | _mo->d.superdata = &QObject::staticMetaObject; 25 | _mo->d.extradata = NULL; 26 | _mo->d.relatedMetaObjects = NULL; 27 | _mo->d.static_metacall = qrsStaticDynamicMetacall; 28 | 29 | /* Build string data */ 30 | 31 | struct ArrayData { 32 | int ref; 33 | int size; 34 | uint _1 : 31; 35 | uint _2 : 1; 36 | qptrdiff offset; 37 | }; 38 | 39 | QVector stringhdr; 40 | QByteArray stringdata; 41 | 42 | #define ADD_STRING_HDR(len) { stringhdr.append(ArrayData { -1, len, 0, 0, \ 43 | qptrdiff(stringdata.size() - stringhdr.size() * sizeof(ArrayData)) }); } 44 | 45 | QString classname = "DynamicObject"; 46 | 47 | ADD_STRING_HDR(classname.size()); 48 | stringdata.append(classname); 49 | stringdata.append('\0'); 50 | for (int i = 0; i < _methods.size(); ++i) { 51 | ADD_STRING_HDR(_methods[i].name.size()); 52 | stringdata.append(_methods[i].name); 53 | stringdata.append('\0'); 54 | ADD_STRING_HDR(0); 55 | stringdata.append('\0'); // tag 56 | for (int j = 0; j < _methods[i].args; ++j) { 57 | QString arg = QString::fromUtf8("arg%1").arg(j); 58 | ADD_STRING_HDR(arg.size()); 59 | stringdata.append(arg); 60 | stringdata.append('\0'); 61 | } 62 | } 63 | 64 | #undef ADD_STRING_HDR 65 | 66 | for (int i = 0; i < stringhdr.size(); ++i) { 67 | stringhdr[i].offset += stringhdr.size() * sizeof(ArrayData); 68 | } 69 | 70 | char *stringdata_buf = new char[stringhdr.size() * sizeof(ArrayData) + stringdata.size()]; 71 | memcpy(stringdata_buf, stringhdr.constData(), stringhdr.size() * sizeof(ArrayData)); 72 | memcpy(stringdata_buf + stringhdr.size() * sizeof(ArrayData), stringdata.data(), 73 | stringdata.size()); 74 | _mo->d.stringdata = (const QByteArrayData *)stringdata_buf; 75 | 76 | /* Build metadata */ 77 | 78 | QVector metadata; 79 | metadata.append(7); /* revision */ 80 | metadata.append(0); /* classname string id */ 81 | metadata.append(0); metadata.append(0); /* class info */ 82 | metadata.append(_methods.size()); metadata.append(0xbd); /* method number, list offset */ 83 | metadata.append(0); metadata.append(0); /* properties */ 84 | metadata.append(0); metadata.append(0); /* enums */ 85 | metadata.append(0); metadata.append(0); /* constructors */ 86 | metadata.append(0); /* flags */ 87 | 88 | int n_signals = 0; 89 | for (int i = 0; i < _methods.size(); ++i) 90 | if (_methods[i].flags == 0x06) ++n_signals; 91 | metadata.append(n_signals); /* signals */ 92 | 93 | metadata[5] = metadata.size(); /* fixup method list offset */ 94 | int str_ptr = 1; 95 | QList fixup_offsets; 96 | for (int i = 0; i < _methods.size(); ++i) { 97 | metadata.append(str_ptr); 98 | metadata.append(_methods[i].args); 99 | fixup_offsets.append(metadata.size()); 100 | metadata.append(0xbd); /* argument list offset */ 101 | metadata.append(str_ptr+1); /* tag. unused */ 102 | metadata.append(_methods[i].flags); /* public */ 103 | str_ptr += 2 + _methods[i].args; 104 | } 105 | 106 | str_ptr = 3; 107 | for (int i = 0; i < _methods.size(); ++i) { 108 | metadata[fixup_offsets.takeFirst()] = metadata.size(); 109 | metadata.append(QMetaType::QVariant); 110 | for (int j = 0; j < _methods[i].args; ++j) { 111 | metadata.append(QMetaType::QVariant); 112 | } 113 | for (int j = 0; j < _methods[i].args; ++j) { 114 | metadata.append(str_ptr+j); 115 | } 116 | str_ptr += 2 + _methods[i].args; 117 | } 118 | 119 | metadata.append(0); 120 | 121 | uint *metadata_buf = new uint[metadata.size()]; 122 | memcpy(metadata_buf, metadata.constData(), metadata.size() * sizeof(uint)); 123 | _mo->d.data = metadata_buf; 124 | } 125 | 126 | QObject* QrsDynamicMetaObject::create(QrsSlotFunction fun, void* data) 127 | { 128 | if (!_mo) 129 | finalize(); 130 | 131 | return new QrsDynamicObject(fun, data, _mo, _methods.size()); 132 | } 133 | 134 | QrsDynamicObject::QrsDynamicObject(QrsSlotFunction* fun, void* data, QMetaObject* mo, int n_slots) 135 | : QObject(), _fun(fun), _data(data), _mo(mo), _n_slots(n_slots) 136 | { 137 | } 138 | 139 | const QMetaObject* QrsDynamicObject::metaObject() const 140 | { 141 | return _mo; 142 | } 143 | 144 | void* QrsDynamicObject::qt_metacast(const char* ) 145 | { 146 | return Q_NULLPTR; 147 | } 148 | 149 | int QrsDynamicObject::qt_metacall(QMetaObject::Call c, int id, void** a) 150 | { 151 | id = QObject::qt_metacall(c, id, a); 152 | 153 | if (c == QMetaObject::InvokeMetaMethod) { 154 | if (id < _n_slots) { 155 | invokeMetacall(id, a); 156 | } 157 | id -= 1; 158 | } else if (c == QMetaObject::RegisterMethodArgumentMetaType) { 159 | if (id < _n_slots) { 160 | *reinterpret_cast(a[0]) = -1; 161 | } 162 | id -= 1; 163 | } 164 | return id; 165 | } 166 | 167 | void QrsDynamicObject::invokeMetacall(int id, void **args) 168 | { 169 | if (_fun) 170 | _fun(_data, id, (QVariant **)args); 171 | else 172 | qWarning("QrsDynamicMetaObject: tried to invoke metacall but handler not set"); 173 | } 174 | 175 | void QrsDynamicObject::emitSignal(int id) 176 | { 177 | QMetaObject::activate(this, _mo, id, Q_NULLPTR); 178 | } 179 | -------------------------------------------------------------------------------- /ext/libqmlrswrapper/qrsdynamicobject.h: -------------------------------------------------------------------------------- 1 | #ifndef QRSDYNAMICOBJECT_H 2 | #define QRSDYNAMICOBJECT_H 3 | 4 | #include 5 | 6 | #if Q_MOC_OUTPUT_REVISION != 67 7 | #error "Unsupport Qt version. Qt with Q_MOC_OUTPUT_REVISION == 67 is required." 8 | #endif 9 | 10 | extern "C" typedef void *(QrsSlotFunction)(void *data, int slot, QVariant **args); 11 | 12 | class QrsDynamicMetaObject 13 | { 14 | public: 15 | QrsDynamicMetaObject(); 16 | virtual ~QrsDynamicMetaObject(); 17 | 18 | struct Method { 19 | QString name; 20 | uint args; 21 | uint flags; 22 | }; 23 | 24 | void addSlot(QString name, uint args) { 25 | if (_mo) 26 | qFatal("Cannot add slot after object created"); 27 | 28 | Method m = { name, args, 0x0a }; 29 | 30 | _methods.append(m); 31 | } 32 | 33 | void addSignal(QString name, uint args) { 34 | if (_mo) 35 | qFatal("Cannot add signal after object created"); 36 | 37 | Method m = { name, args, 0x06 }; 38 | 39 | _methods.append(m); 40 | } 41 | 42 | QObject *create(QrsSlotFunction fun, void *data); 43 | 44 | private: 45 | QList _methods; 46 | QMetaObject *_mo; 47 | 48 | void finalize(); 49 | }; 50 | 51 | class QrsDynamicObject : public QObject 52 | { 53 | public: 54 | QrsDynamicObject(QrsSlotFunction *fun, void *data, QMetaObject *mo, int n_slots); 55 | virtual const QMetaObject* metaObject() const; 56 | virtual void* qt_metacast(const char* ); 57 | virtual int qt_metacall(QMetaObject::Call , int , void** ); 58 | 59 | void emitSignal(int id); 60 | 61 | private: 62 | QrsSlotFunction *_fun; 63 | void *_data; 64 | int _n_slots; 65 | 66 | QMetaObject *_mo; 67 | 68 | void invokeMetacall(int id, void** args); 69 | }; 70 | 71 | #endif // QRSDYNAMICOBJECT_H 72 | -------------------------------------------------------------------------------- /ext/libqmlrswrapper/qrsdynamicobject_capi.cpp: -------------------------------------------------------------------------------- 1 | #include "qrsdynamicobject.h" 2 | 3 | extern "C" QrsDynamicMetaObject *qmlrs_metaobject_create() { 4 | return new QrsDynamicMetaObject(); 5 | } 6 | 7 | extern "C" void qmlrs_metaobject_destroy(QrsDynamicMetaObject *mo) { 8 | delete mo; 9 | } 10 | 11 | extern "C" void qmlrs_metaobject_add_slot(QrsDynamicMetaObject *mo, const char *name, uint name_len, 12 | uint argc) 13 | { 14 | mo->addSlot(QString::fromUtf8(name, name_len), argc); 15 | } 16 | 17 | extern "C" void qmlrs_metaobject_add_signal(QrsDynamicMetaObject *mo, const char *name, uint name_len, 18 | uint argc) 19 | { 20 | mo->addSignal(QString::fromUtf8(name, name_len), argc); 21 | } 22 | 23 | extern "C" QObject *qmlrs_metaobject_instantiate(QrsDynamicMetaObject *mo, QrsSlotFunction fun, 24 | void *data) 25 | { 26 | return mo->create(fun, data); 27 | } 28 | 29 | extern "C" void qmlrs_object_emit_signal(QrsDynamicObject *obj, uint id) { 30 | obj->emitSignal(id); 31 | } 32 | 33 | extern "C" void qmlrs_object_destroy(QrsDynamicObject *obj) { 34 | delete obj; 35 | } 36 | -------------------------------------------------------------------------------- /src/ffi.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_copy_implementations)] 2 | 3 | use libc::{c_char, c_int, c_uint, c_void}; 4 | 5 | pub enum QrsEngine {} 6 | pub enum QrsMetaObject {} 7 | pub enum QObject {} 8 | pub enum QVariant {} 9 | pub enum QVariantList {} 10 | 11 | #[repr(C)] 12 | #[derive(Eq, PartialEq, Debug, Copy, Clone)] 13 | pub enum QrsVariantType { 14 | Invalid = 0, 15 | Int64, 16 | Bool, 17 | String 18 | } 19 | 20 | pub type SlotFunction = extern "C" fn(data: *mut c_void, id: c_int, args: *const *const QVariant); 21 | 22 | extern "C" { 23 | pub fn qmlrs_create_engine() -> *mut QrsEngine; 24 | pub fn qmlrs_create_engine_headless() -> *mut QrsEngine; 25 | pub fn qmlrs_destroy_engine(engine: *mut QrsEngine); 26 | pub fn qmlrs_engine_load_url(engine: *mut QrsEngine, path: *const c_char, len: c_uint); 27 | pub fn qmlrs_engine_load_from_data(engine: *mut QrsEngine, data: *const c_char, len: c_uint); 28 | pub fn qmlrs_engine_invoke(engine: *mut QrsEngine, method: *const c_char, result: *mut QVariant, 29 | args: *const QVariantList); 30 | pub fn qmlrs_engine_set_property(engine: *mut QrsEngine, name: *const c_char, len: c_uint, 31 | object: *mut QObject); 32 | 33 | pub fn qmlrs_variant_create() -> *mut QVariant; 34 | pub fn qmlrs_variant_destroy(v: *mut QVariant); 35 | pub fn qmlrs_variant_set_int64(var: *mut QVariant, x: i64); 36 | pub fn qmlrs_variant_set_bool(var: *mut QVariant, x: bool); 37 | pub fn qmlrs_variant_set_invalid(var: *mut QVariant); 38 | pub fn qmlrs_variant_set_string(var: *mut QVariant, len: c_uint, data: *const c_char); 39 | pub fn qmlrs_variant_get_type(var: *const QVariant) -> QrsVariantType; 40 | pub fn qmlrs_variant_get_int64(var: *const QVariant, x: *mut i64); 41 | pub fn qmlrs_variant_get_bool(var: *const QVariant, x: *mut bool); 42 | pub fn qmlrs_variant_get_string_length(var: *const QVariant, out: *mut c_uint); 43 | pub fn qmlrs_variant_get_string_data(var: *const QVariant, out: *mut c_char); 44 | 45 | pub fn qmlrs_varlist_create() -> *mut QVariantList; 46 | pub fn qmlrs_varlist_destroy(list: *mut QVariantList); 47 | pub fn qmlrs_varlist_push(list: *mut QVariantList) -> *mut QVariant; 48 | pub fn qmlrs_varlist_length(list: *const QVariantList) -> c_uint; 49 | pub fn qmlrs_varlist_get(list: *const QVariantList, i: c_uint) -> *mut QVariant; 50 | 51 | pub fn qmlrs_app_exec(); 52 | 53 | pub fn qmlrs_metaobject_create() -> *mut QrsMetaObject; 54 | pub fn qmlrs_metaobject_destroy(mo: *mut QrsMetaObject); 55 | pub fn qmlrs_metaobject_add_slot(mo: *mut QrsMetaObject, name: *const c_char, name_len: c_uint, 56 | argc: c_uint); 57 | pub fn qmlrs_metaobject_add_signal(mo: *mut QrsMetaObject, name: *const c_char, 58 | name_len: c_uint, argc: c_uint); 59 | pub fn qmlrs_metaobject_instantiate(mo: *mut QrsMetaObject, fun: SlotFunction, 60 | data: *mut c_void) -> *mut QObject; 61 | 62 | pub fn qmlrs_object_emit_signal(obj: *mut QObject, id: c_uint); 63 | pub fn qmlrs_object_destroy(obj: *mut QObject); 64 | } 65 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | 3 | use libc::{c_char, c_int, c_uint, c_void}; 4 | use std::sync::Arc; 5 | use ffi::{QrsEngine, QObject}; 6 | use std::path::Path; 7 | use std::convert::AsRef; 8 | 9 | /* Re-exports */ 10 | 11 | pub use variant::{Variant, FromQVariant, ToQVariant}; 12 | pub use ffi::QVariant as OpaqueQVariant; 13 | 14 | /* Submodules */ 15 | 16 | #[allow(dead_code)] 17 | mod ffi; 18 | mod macros; 19 | mod variant; 20 | 21 | pub trait Object { 22 | fn qt_metaobject(&self) -> MetaObject; 23 | fn qt_metacall(&mut self, slot: i32, args: *const *const OpaqueQVariant); 24 | } 25 | 26 | pub fn __qobject_emit(obj: &T, id: u32) { 27 | unsafe { 28 | ffi::qmlrs_object_emit_signal(get_qobject(obj), id as c_uint); 29 | } 30 | } 31 | 32 | /* This is unsafe in case the user copies the data. Might need to figure out something different. */ 33 | fn get_qobject(ptr: &T) -> *mut QObject { 34 | unsafe { 35 | let t_addr: usize = std::mem::transmute(ptr); 36 | let hdr: &PropHdr = std::mem::transmute(t_addr - std::mem::size_of::<*mut QObject>()); 37 | hdr.qobj 38 | } 39 | } 40 | 41 | struct EngineInternal { 42 | p: *mut QrsEngine, 43 | } 44 | 45 | /* Hack to get invoke working. Need to figure out better way for invokes anyway.. */ 46 | unsafe impl Send for EngineInternal { } 47 | unsafe impl Sync for EngineInternal { } 48 | 49 | impl Drop for EngineInternal { 50 | fn drop(&mut self) { 51 | unsafe { ffi::qmlrs_destroy_engine(self.p); } 52 | } 53 | } 54 | 55 | pub struct Engine { 56 | i: Arc, 57 | } 58 | 59 | #[repr(packed)] 60 | struct PropHdr { 61 | qobj: *mut QObject, 62 | obj: T 63 | } 64 | 65 | extern "C" fn slot_handler(data: *mut c_void, slot: c_int, 66 | args: *const *const ffi::QVariant) 67 | { 68 | unsafe { 69 | let hdr: &mut PropHdr = std::mem::transmute(data); 70 | hdr.obj.qt_metacall(slot as i32, args); 71 | } 72 | } 73 | 74 | impl Engine { 75 | pub fn new() -> Engine { 76 | let p = unsafe { ffi::qmlrs_create_engine() }; 77 | assert!(!p.is_null()); 78 | 79 | let i = Arc::new(EngineInternal { 80 | p: p, 81 | }); 82 | 83 | Engine { 84 | i: i 85 | } 86 | } 87 | 88 | pub fn new_headless() -> Engine { 89 | let p = unsafe { ffi::qmlrs_create_engine_headless() }; 90 | assert!(!p.is_null()); 91 | 92 | let i = Arc::new(EngineInternal { 93 | p: p, 94 | }); 95 | 96 | Engine { 97 | i: i 98 | } 99 | } 100 | 101 | pub fn load_url(&mut self, path: &str) { 102 | unsafe { 103 | ffi::qmlrs_engine_load_url(self.i.p, path.as_ptr() as *const c_char, 104 | path.len() as c_uint); 105 | } 106 | } 107 | 108 | pub fn load_data(&mut self, data: &str) { 109 | unsafe { 110 | ffi::qmlrs_engine_load_from_data(self.i.p, data.as_ptr() as *const c_char, 111 | data.len() as c_uint); 112 | } 113 | } 114 | 115 | 116 | 117 | pub fn load_local_file>(&mut self, name: P) { 118 | let path_raw = std::env::current_dir().unwrap().join(name); 119 | let path 120 | = if cfg!(windows) { 121 | format!("file:///{}",path_raw.display()) 122 | } else { 123 | format!("file://{}",path_raw.display()) 124 | } ; 125 | self.load_url(&path); 126 | } 127 | 128 | pub fn exec(self) { 129 | unsafe { ffi::qmlrs_app_exec(); } 130 | } 131 | 132 | /* 133 | pub fn handle(&self) -> Handle { 134 | Handle { i: self.i.downgrade() } 135 | } 136 | */ 137 | 138 | pub fn set_property(&mut self, name: &str, obj: T) { 139 | unsafe { 140 | let mo = obj.qt_metaobject().p; 141 | let mut boxed = Box::new(PropHdr { qobj: std::ptr::null_mut(), obj: obj }); 142 | let qobj = ffi::qmlrs_metaobject_instantiate( 143 | mo, slot_handler::, &mut *boxed as *mut PropHdr as *mut c_void); 144 | 145 | boxed.qobj = qobj; 146 | 147 | ffi::qmlrs_engine_set_property(self.i.p, name.as_ptr() as *const c_char, 148 | name.len() as c_uint, qobj); 149 | 150 | std::mem::forget(boxed); 151 | } 152 | } 153 | } 154 | 155 | /* MetaObjects currently leak. Once a cache system is implemented, this should be fine. */ 156 | 157 | #[allow(missing_copy_implementations)] 158 | pub struct MetaObject { 159 | p: *mut ffi::QrsMetaObject 160 | } 161 | 162 | impl MetaObject { 163 | pub fn new() -> MetaObject { 164 | let p = unsafe { ffi::qmlrs_metaobject_create() }; 165 | assert!(!p.is_null()); 166 | 167 | MetaObject { p: p } 168 | } 169 | 170 | pub fn slot(self, name: &str, argc: u8) -> MetaObject { 171 | unsafe { 172 | ffi::qmlrs_metaobject_add_slot(self.p, name.as_ptr() as *const c_char, 173 | name.len() as c_uint, argc as c_uint); 174 | } 175 | self 176 | } 177 | 178 | pub fn signal(self, name: &str, argc: u8) -> MetaObject { 179 | unsafe { 180 | ffi::qmlrs_metaobject_add_signal(self.p, name.as_ptr() as *const c_char, 181 | name.len() as c_uint, argc as c_uint); 182 | } 183 | self 184 | } 185 | } 186 | 187 | /* 188 | pub struct Handle { 189 | i: Weak 190 | } 191 | 192 | impl Handle { 193 | pub fn invoke(&self, method: &str, args: &[Variant]) -> Result, &'static str> { 194 | unsafe { 195 | let cstr = method.to_c_str(); 196 | 197 | let c_args = ffi::qmlrs_varlist_create(); 198 | assert!(!c_args.is_null()); 199 | for arg in args.iter() { 200 | let c_arg = ffi::qmlrs_varlist_push(c_args); 201 | assert!(!c_arg.is_null()); 202 | arg.to_qvariant(c_arg); 203 | } 204 | 205 | let result = ffi::qmlrs_variant_create(); 206 | assert!(!result.is_null()); 207 | 208 | match self.i.upgrade() { 209 | Some(i) => ffi::qmlrs_engine_invoke(i.p, cstr.as_ptr(), result, 210 | c_args as *const QVariantList), 211 | None => { 212 | ffi::qmlrs_variant_destroy(result); 213 | ffi::qmlrs_varlist_destroy(c_args); 214 | return Err("View has been freed") 215 | } 216 | } 217 | 218 | ffi::qmlrs_varlist_destroy(c_args); 219 | 220 | let ret = FromQVariant::from_qvariant(result as *const QVariant); 221 | ffi::qmlrs_variant_destroy(result); 222 | 223 | Ok(ret) 224 | } 225 | } 226 | } 227 | */ 228 | 229 | #[cfg(test)] 230 | mod test { 231 | use super::*; 232 | 233 | #[test] 234 | fn test_create_engine() { 235 | Engine::new_headless(); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! Q_OBJECT( 3 | ( 4 | $t:ty : 5 | $( 6 | slot fn $name:ident ( $($at:ty),* ); 7 | )* 8 | /* 9 | $( 10 | signal fn $sname:ident ( ); 11 | )* 12 | */ 13 | ) => ( 14 | /* 15 | impl $t { 16 | #[allow(dead_code, unused_mut, unused_variables, unused_assignments)] 17 | fn __qmlrs_signal_id(&self, name: &str) -> u32 { 18 | let mut id = 0; 19 | $( 20 | if stringify!($sname) == name { 21 | return id; 22 | } 23 | id += 1; 24 | )+ 25 | panic!("__qmlrs_signal_id called with invalid signal name!"); 26 | } 27 | 28 | $( 29 | #[allow(dead_code)] 30 | fn $sname (&self) { 31 | qmlrs::__qobject_emit(self, self.__qmlrs_signal_id(stringify!($sname))); 32 | } 33 | )+ 34 | } 35 | */ 36 | impl qmlrs::Object for $t { 37 | #[allow(unused_mut, unused_variables)] 38 | fn qt_metaobject(&self) -> qmlrs::MetaObject { 39 | let x = qmlrs::MetaObject::new(); 40 | /* 41 | $( 42 | let x = x.signal(stringify!($sname), 0); 43 | )+ 44 | */ 45 | $( 46 | let mut argc = 0; 47 | $( 48 | let _: $at; 49 | argc += 1; 50 | )* 51 | let x = x.slot(stringify!($name), argc); 52 | )+ 53 | x 54 | } 55 | 56 | #[allow(unused_assignments, unused_mut, unused_variables)] 57 | fn qt_metacall(&mut self, slot: i32, args: *const *const qmlrs::OpaqueQVariant) { 58 | use qmlrs::ToQVariant; 59 | let mut i = 0; 60 | /* 61 | $( 62 | let _ = stringify!($sname); 63 | i += 1; 64 | )+ 65 | */ 66 | $( 67 | if i == slot { 68 | let mut argi = 1u8; /* 0 is for return value */ 69 | let ret = self.$name( 70 | $( 71 | { 72 | let _: $at; 73 | match qmlrs::FromQVariant::from_qvariant(unsafe { *args.offset(argi as isize) }) { 74 | Some(arg) => { argi += 1; arg } 75 | None => { 76 | println!("qmlrs: Invalid argument {} type for slot {}", argi, stringify!($name)); 77 | return; 78 | } 79 | } 80 | } 81 | ),* 82 | ); 83 | ret.to_qvariant(unsafe { *args.offset(0) as *mut qmlrs::OpaqueQVariant }); 84 | return 85 | } 86 | i += 1; 87 | )+ 88 | } 89 | } 90 | ) 91 | ); 92 | -------------------------------------------------------------------------------- /src/variant.rs: -------------------------------------------------------------------------------- 1 | use libc::{c_char, c_uint}; 2 | use ffi; 3 | use ffi::{QVariant, QrsVariantType}; 4 | 5 | pub enum Variant { 6 | I64(i64), 7 | Bool(bool), 8 | String(String), 9 | } 10 | 11 | pub trait FromQVariant { 12 | fn from_qvariant(arg: *const QVariant) -> Option where Self: Sized; 13 | } 14 | 15 | impl FromQVariant for i64 { 16 | fn from_qvariant(var: *const QVariant) -> Option { 17 | unsafe { 18 | if ffi::qmlrs_variant_get_type(var) == QrsVariantType::Int64 { 19 | let mut x: i64 = 0; 20 | ffi::qmlrs_variant_get_int64(var, &mut x); 21 | Some(x) 22 | } else { 23 | None 24 | } 25 | } 26 | } 27 | } 28 | 29 | impl FromQVariant for bool { 30 | fn from_qvariant(var: *const QVariant) -> Option { 31 | unsafe { 32 | if ffi::qmlrs_variant_get_type(var) == QrsVariantType::Bool { 33 | let mut x: bool = false; 34 | ffi::qmlrs_variant_get_bool(var, &mut x); 35 | Some(x) 36 | } else { 37 | None 38 | } 39 | } 40 | } 41 | } 42 | 43 | impl FromQVariant for String { 44 | fn from_qvariant(var: *const QVariant) -> Option { 45 | unsafe { 46 | if ffi::qmlrs_variant_get_type(var) == QrsVariantType::String { 47 | let mut len: c_uint = 0; 48 | ffi::qmlrs_variant_get_string_length(var, &mut len); 49 | 50 | let mut data: Vec = Vec::with_capacity(len as usize); 51 | ffi::qmlrs_variant_get_string_data(var, data.as_mut_ptr() as *mut c_char); 52 | data.set_len(len as usize); 53 | 54 | Some(String::from_utf8_unchecked(data)) 55 | } else { 56 | None 57 | } 58 | } 59 | } 60 | } 61 | 62 | impl FromQVariant for Variant { 63 | fn from_qvariant(var: *const QVariant) -> Option { 64 | use ffi::QrsVariantType::*; 65 | unsafe { 66 | match ffi::qmlrs_variant_get_type(var) { 67 | Int64 => 68 | Some(Variant::I64(FromQVariant::from_qvariant(var).unwrap())), 69 | Bool => 70 | Some(Variant::Bool(FromQVariant::from_qvariant(var).unwrap())), 71 | String => 72 | Some(Variant::String(FromQVariant::from_qvariant(var).unwrap())), 73 | _ => None 74 | } 75 | } 76 | } 77 | } 78 | 79 | pub trait ToQVariant { 80 | fn to_qvariant(&self, var: *mut QVariant); 81 | } 82 | 83 | impl ToQVariant for () { 84 | fn to_qvariant(&self, var: *mut QVariant) { 85 | unsafe { 86 | ffi::qmlrs_variant_set_invalid(var); 87 | } 88 | } 89 | } 90 | 91 | impl ToQVariant for i64 { 92 | fn to_qvariant(&self, var: *mut QVariant) { 93 | unsafe { 94 | ffi::qmlrs_variant_set_int64(var, *self); 95 | } 96 | } 97 | } 98 | 99 | impl ToQVariant for bool { 100 | fn to_qvariant(&self, var: *mut QVariant) { 101 | unsafe { 102 | ffi::qmlrs_variant_set_bool(var, *self); 103 | } 104 | } 105 | } 106 | 107 | macro_rules! int_toqvar { 108 | ($($t:ty)*) => ( 109 | $( 110 | impl ToQVariant for $t { 111 | fn to_qvariant(&self, var: *mut QVariant) { 112 | (*self as i64).to_qvariant(var); 113 | } 114 | } 115 | )* 116 | ) 117 | } 118 | 119 | int_toqvar!(u8 u16 u32 i8 i16 i32 isize); 120 | 121 | impl ToQVariant for str { 122 | fn to_qvariant(&self, var: *mut QVariant) { 123 | unsafe { 124 | ffi::qmlrs_variant_set_string(var, self.len() as c_uint, 125 | self.as_ptr() as *const c_char); 126 | } 127 | } 128 | } 129 | 130 | impl ToQVariant for Variant { 131 | fn to_qvariant(&self, var: *mut QVariant) { 132 | match *self { 133 | Variant::I64(ref x) => x.to_qvariant(var), 134 | Variant::Bool(ref x) => x.to_qvariant(var), 135 | Variant::String(ref s) => s.to_qvariant(var), 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /tests/simple_test.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.2 2 | 3 | Item { 4 | Timer { 5 | // 0ms (firing instantly) confuses travis CI, therefore 50ms 6 | interval: 50 7 | running: true 8 | onTriggered: { 9 | console.debug("Test timer triggered!"); 10 | Qt.quit(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/simple_test.rs: -------------------------------------------------------------------------------- 1 | extern crate qmlrs; 2 | 3 | #[test] 4 | fn test_quit() { 5 | let mut engine = qmlrs::Engine::new_headless(); 6 | 7 | engine.load_local_file("tests/simple_test.qml"); 8 | 9 | engine.exec(); 10 | } 11 | --------------------------------------------------------------------------------