cxxqt-rust-gui
SKILL.md
CxxQt Rust GUI Development Instructions
Guidelines for building Qt-based GUI applications in Rust using CxxQt.
Architecture Overview
┌─────────────────────────────────────────┐
│ QML UI Layer │
│ (main.qml, components/*.qml) │
└──────────────────┬──────────────────────┘
│ Properties, Signals, Invokables
┌──────────────────▼──────────────────────┐
│ CxxQt Bridge Module │
│ (cxxqt_bridge.rs - #[cxx_qt::bridge]) │
└──────────────────┬──────────────────────┘
│
┌──────────────────▼──────────────────────┐
│ Rust Business Logic │
│ (lib.rs, modules/*.rs) │
└─────────────────────────────────────────┘
Interactive-Terminal App Profile (Rust+QML)
Use this profile when the CxxQt app is acting as a human-approval gateway for MCP command execution.
Approval Queue Model
- Incoming command requests are enqueued in Rust backend state before any execution request is routed.
- QML renders pending items from bridge properties (request id, command, cwd, context/metadata).
- User actions (
approve/decline) are emitted as bridge invokables/signals and persisted to request state.
Event Flow (Recommended)
- Transport layer receives request (TCP/bridge input) and emits
requestReceived. - QObject bridge updates queue properties (
pendingCount, active request details). - QML updates approval UI reactively from NOTIFY signals.
- User approves/declines via invokable methods.
- Rust backend emits response event and routing metadata.
- Request is routed to one terminal surface and completion status is signaled back to QML.
Bridge Responsibilities
- Keep bridge logic focused on state sync and signal/slot translation.
- Keep execution routing decisions in Rust service/backend layer, not in QML components.
- Expose explicit properties for queue depth, current request, decision state, and execution status.
- Emit stable NOTIFY signals so the UI remains deterministic under rapid queue updates.
Policy Cross-References
- For terminal-surface selection semantics, follow
instructions/mcp-usage.instructions.md(Canonical Terminal Surface Selection). - Treat Rust+QML as approval/orchestration; execution still resolves to
memory_terminalormemory_terminal_interactive. - If docs conflict on surface contracts, follow
instructions/mcp-usage.instructions.md(Contract-Collision Warnings) and escalate for cleanup.
Project Structure
my-app/
├── Cargo.toml # Dependencies
├── build.rs # CxxQt build config
├── .qmlls.ini # QML language server
├── qml/
│ ├── main.qml # Main window
│ └── components/ # Reusable QML components
├── resources/
│ ├── app.manifest # Windows manifest
│ ├── app.ico # Windows icon
│ └── icon.svg # App icon
└── src/
├── main.rs # Entry point
├── cxxqt_bridge.rs # Bridge definitions
└── *.rs # Business logic
Required Dependencies
Cargo.toml
[dependencies]
cxx = "1.0.95"
cxx-qt = "0.8"
cxx-qt-lib = { version = "0.8", features = ["qt_full"] }
[build-dependencies]
cxx-qt-build = { version = "0.8", features = ["link_qt_object_files"] }
[target.'cfg(windows)'.build-dependencies]
winresource = "0.1"
Environment
Set QT_DIR to your Qt installation (e.g., C:\Qt\6.7.0\msvc2019_64).
Build Configuration (build.rs)
use cxx_qt_build::{CxxQtBuilder, QmlModule};
fn main() {
#[cfg(windows)]
{
let mut res = winresource::WindowsResource::new();
res.set_icon("resources/app.ico");
res.set_manifest_file("resources/app.manifest");
res.compile().expect("Failed to compile Windows resources");
}
CxxQtBuilder::new_qml_module(
QmlModule::new("com.mycompany.myapp")
.qml_files(["qml/main.qml"]),
)
.file("src/cxxqt_bridge.rs")
.qrc_resources(["resources/icon.svg"])
.build();
}
CxxQt Bridge Pattern
Bridge Structure (cxxqt_bridge.rs)
use cxx_qt_lib::QString;
use std::pin::Pin;
#[cxx_qt::bridge]
pub mod ffi {
// Import Qt types
unsafe extern "C++" {
include!("cxx-qt-lib/qstring.h");
type QString = cxx_qt_lib::QString;
}
// Define QObject with properties
unsafe extern "RustQt" {
#[qobject]
#[qml_element]
#[qproperty(QString, status_message, cxx_name = "statusMessage")]
#[qproperty(bool, is_busy, cxx_name = "isBusy")]
#[qproperty(i32, progress)]
type MyApp = super::MyAppRust;
#[qinvokable]
#[cxx_name = "doWork"]
fn do_work(self: Pin<&mut MyApp>, input: QString);
}
// Define signals
unsafe extern "RustQt" {
#[qsignal]
#[cxx_name = "workComplete"]
fn work_complete(self: Pin<&mut MyApp>, success: bool);
}
// Enable threading
impl cxx_qt::Threading for MyApp {}
}
// Rust backing struct
#[derive(Default)]
pub struct MyAppRust {
status_message: QString,
is_busy: bool,
progress: i32,
}
// Method implementations
impl ffi::MyApp {
pub fn do_work(mut self: Pin<&mut Self>, input: QString) {
self.as_mut().set_is_busy(true);
// ... implementation
}
}
Naming Conventions
| Context | Convention | Example |
|---|---|---|
| Rust fields | snake_case | status_message |
| C++/QML names | camelCase via cxx_name |
statusMessage |
| Rust methods | snake_case | do_work |
| QML invokables | camelCase via cxx_name |
doWork |
Property Types
| Qt Type | Rust Type | Usage |
|---|---|---|
QString |
QString |
Text |
QStringList |
QStringList |
String arrays |
bool |
bool |
Flags |
int |
i32 |
Integers |
double |
f64 |
Decimals |
QUrl |
QUrl |
File paths, URLs |
Threading Pattern
Always use qt_thread() for background work:
pub fn heavy_work(mut self: Pin<&mut Self>) {
let qt_thread = self.qt_thread();
self.as_mut().set_is_busy(true);
std::thread::spawn(move || {
// Background work here...
// Send updates back to Qt thread
qt_thread.queue(move |mut qobject| {
qobject.as_mut().set_is_busy(false);
qobject.as_mut().work_complete(true);
}).expect("Failed to queue");
});
}
Cancellation Pattern
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
#[derive(Default)]
pub struct MyAppRust {
cancel_flag: Arc<AtomicBool>,
}
impl ffi::MyApp {
pub fn cancel(self: Pin<&mut Self>) {
self.rust().cancel_flag.store(true, Ordering::SeqCst);
}
}
QML Integration
Importing the Module
import QtQuick 2.15
import QtQuick.Controls 2.15
import com.mycompany.myapp 1.0
ApplicationWindow {
visible: true
width: 800
height: 600
MyApp {
id: myApp
onWorkComplete: console.log("Done:", success)
}
Button {
text: myApp.isBusy ? "Working..." : "Start"
enabled: !myApp.isBusy
onClicked: myApp.doWork("input")
}
}
Resource Access
Resources via qrc:/qt/qml/<uri-path>/resources/<file>:
Image {
source: "qrc:/qt/qml/com/mycompany/myapp/resources/icon.svg"
}
Main Entry Point
#![windows_subsystem = "windows"]
mod cxxqt_bridge;
use cxx_qt_lib::{QGuiApplication, QQmlApplicationEngine, QUrl};
fn main() {
#[cfg(windows)]
std::env::set_var("QT_QPA_PLATFORM", "windows:darkmode=2");
let mut app = QGuiApplication::new();
let mut engine = QQmlApplicationEngine::new();
if let Some(engine) = engine.as_mut() {
engine.load(&QUrl::from("qrc:/qt/qml/com/mycompany/myapp/qml/main.qml"));
}
if let Some(app) = app.as_mut() {
app.exec();
}
}
Error Handling
Store errors in properties and emit signals:
pub fn load_file(mut self: Pin<&mut Self>, path: QString) {
match std::fs::read_to_string(path.to_string()) {
Ok(content) => {
self.as_mut().set_content(QString::from(content.as_str()));
self.as_mut().set_error_message(QString::default());
}
Err(e) => {
let msg = QString::from(e.to_string().as_str());
self.as_mut().set_error_message(msg.clone());
self.as_mut().error_occurred(msg);
}
}
}
Best Practices
- Never panic in methods called from Qt - always handle errors gracefully
- Capture
qt_thread()on the Qt thread before spawning background threads - Use
queue()to send all updates back to the Qt thread - Keep QML files focused - extract reusable components
- Use
Arc<AtomicBool>for cancellation flags - Initialize properties via the
Initializetrait whenDefaultisn't sufficient
Windows Resources
app.manifest
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
</assembly>
.qmlls.ini
[General]
buildDir=target/debug
References
Weekly Installs
1
Repository
ds-codi/project…mory-mcpGitHub Stars
3
First Seen
6 days ago
Security Audits
Installed on
zencoder1
amp1
cline1
openclaw1
opencode1
cursor1