Skip to main content

qtbridge_runtime/
qml_method_invoker.rs

1// Copyright (C) 2026 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only
3
4use std::sync::Arc;
5use std::sync::atomic::{AtomicBool, Ordering};
6use qtbridge_type_lib::{QObject, QVariantList};
7
8use crate::QObjectHolder;
9
10#[cxx::bridge]
11pub mod ffi {
12    #[allow(clippy::missing_safety_doc)]
13    unsafe extern "C++" {
14        include!("qtbridge-type-lib/src/core/qobject/cpp/qobject.h");
15        type QObject = qtbridge_type_lib::QObject;
16        include!("qtbridge-type-lib/src/core/qlist/cpp/qlist_qvariant.h");
17        type QList_QVariant = qtbridge_type_lib::QList_QVariant;
18
19        include!("cpp/qml_method_invoker.h");
20
21        unsafe fn connect_destroyed_callback(obj: *mut QObject, flag_ptr: usize);
22
23        unsafe fn invoke_method(obj: *mut QObject, name: &str, args: &QList_QVariant) -> bool;
24    }
25
26    extern "Rust" {
27        fn on_qobject_destroyed(flag_ptr: usize);
28    }
29}
30
31fn on_qobject_destroyed(flag_ptr: usize) {
32    let arc = unsafe { Arc::from_raw(flag_ptr as *const AtomicBool) };
33    arc.store(false, Ordering::Release);
34}
35
36/// Invokes a slot or signal by name on a [`QmlMethodInvoker`].
37///
38/// Without extra arguments, delegates to [`QmlMethodInvoker::invoke_method`].
39/// With extra arguments, constructs the argument list and delegates to
40/// [`QmlMethodInvoker::invoke_method_with_args`].
41///
42/// ```
43/// use qtbridge::invoke_method;
44/// use qtbridge::{qobject, QmlObject};
45///
46/// #[derive(Default)]
47/// pub struct MyClass { }
48///
49/// #[qobject]
50/// impl MyClass {
51///     #[qslot]
52///     fn set_value(&mut self, value: i32) { }
53///
54///     #[qslot]
55///     fn reset(&self) { }
56/// }
57///
58/// let obj = MyClass::default_with_attached_qobject();
59/// let invoker = obj.borrow().get_qml_method_invoker();
60/// invoke_method!(invoker, "reset");
61/// invoke_method!(invoker, "setValue", 42);
62/// ```
63///
64#[macro_export]
65macro_rules! invoke_method {
66    ($invoker:expr, $name:expr $(,)?) => {{
67        $invoker.invoke_method($name)
68    }};
69
70    ($invoker:expr, $name:expr, $($arg:expr),+ $(,)?) => {{
71        let args: qtbridge::qtbridge_type_lib::QVariantList = [
72            $(qtbridge::qtbridge_runtime::QVariantConvertible::to_qvariant(&$arg)),+
73        ]
74        .iter()
75        .collect();
76        $invoker.invoke_method_with_args($name, &args)
77    }};
78}
79
80/// A thread-safe handle for invoking slots and signals on a `#[qobject]` instance.
81///
82/// Calls are scheduled on the Qt event loop and execute on the Qt thread.
83/// If the target object has been dropped, calls are silently discarded.
84///
85/// Arguments follow the same coercion rules QML applies when it calls a
86/// method: an argument matches a parameter as long as it can be converted to
87/// the parameter's type. These rules apply uniformly, even when a Rust method
88/// is invoked from Rust; this path does *not* enforce Rust's exact-type
89/// semantics, so conversions QML permits (for example between numeric types,
90/// or between a number and its textual form) are permitted here too.
91///
92/// When a call executes, Qt borrows the `Rc<RefCell<_>>` held by the QML
93/// engine. If the object is already mutably borrowed on the Qt thread at
94/// that moment, the call will panic.
95///
96/// Obtain an instance via [`QmlObject::get_qml_method_invoker`](crate::QmlObject::get_qml_method_invoker).
97///
98/// # Example
99///
100/// ```
101/// # use qtbridge::{qobject, QmlObject};
102/// # #[qobject]
103/// # pub mod example {
104/// #     #[derive(Default)]
105/// #     pub struct Backend {}
106/// #     impl Backend {
107/// #         #[qsignal]
108/// #         pub fn data_ready(&mut self);
109/// #     }
110/// # }
111/// # use example::Backend;
112/// let backend = Backend::default_with_attached_qobject();
113/// let invoker = backend.borrow().get_qml_method_invoker();
114/// std::thread::spawn(move || {
115///     invoker.invoke_method("dataReady");
116/// }).join().unwrap();
117/// ```
118pub struct QmlMethodInvoker {
119    obj: *mut QObject,
120    alive: Arc<AtomicBool>,
121}
122
123unsafe impl Send for QmlMethodInvoker {}
124
125impl QmlMethodInvoker {
126
127    /// Creates a `QmlMethodInvoker` for `target` and tracks its lifetime.
128    ///
129    /// Prefer [`QmlObject::get_qml_method_invoker`](crate::QmlObject::get_qml_method_invoker) over calling this directly.
130    pub fn new<T: QObjectHolder>(target: &T) -> Self {
131        let obj = target.get_qobject_ptr();
132        let alive = Arc::new(AtomicBool::new(true));
133        let flag_ptr = Arc::into_raw(alive.clone()) as usize;
134        unsafe { ffi::connect_destroyed_callback(obj, flag_ptr) };
135        Self { obj, alive }
136    }
137
138    fn is_alive(&self) -> bool {
139        self.alive.load(Ordering::Acquire)
140    }
141
142    /// Schedules `name` to run on the Qt thread via the Qt event loop.
143    ///
144    /// `name` is resolved over the object's meta-object most-derived-first
145    /// (QML-added members, then Rust, then the C++ base). Returns `false` if
146    /// the target has been dropped or no method named `name` takes zero
147    /// arguments; returns `true` otherwise.
148    pub fn invoke_method(&self, name: &str) -> bool {
149        if !self.is_alive() {
150            return false;
151        }
152        unsafe { ffi::invoke_method(self.obj, name, &QVariantList::default()) }
153    }
154
155    /// Schedules `name` to run on the Qt thread via the Qt event loop,
156    /// passing `args` to the method.
157    ///
158    /// `name` is resolved over the object's meta-object most-derived-first
159    /// (QML-added members, then Rust, then the C++ base), choosing the first
160    /// candidate whose parameter count matches `args` and whose arguments are
161    /// all convertible to the parameter types. Returns `false` if the target
162    /// has been dropped, no candidate matches, or a conversion fails; returns
163    /// `true` otherwise.
164    pub fn invoke_method_with_args(&self, name: &str, args: &QVariantList) -> bool {
165        if !self.is_alive() {
166            return false;
167        }
168        unsafe { ffi::invoke_method(self.obj, name, args) }
169    }
170}