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::{QMetaObject, QObject, QVariantList};
7
8use crate::QObjectHolder;
9
10#[cxx::bridge]
11pub mod ffi {
12 unsafe extern "C++" {
13 include!("qtbridge-type-lib/src/generated/core/qobject/cpp/qobject.h");
14 type QObject = qtbridge_type_lib::QObject;
15
16 include!("cpp/qml_method_invoker.h");
17
18 unsafe fn connect_destroyed_callback(obj: *mut QObject, flag_ptr: usize);
19 }
20
21 extern "Rust" {
22 fn on_qobject_destroyed(flag_ptr: usize);
23 }
24}
25
26fn on_qobject_destroyed(flag_ptr: usize) {
27 let arc = unsafe { Arc::from_raw(flag_ptr as *const AtomicBool) };
28 arc.store(false, Ordering::Release);
29}
30
31/// Invokes a slot or signal by name on a [`QmlMethodInvoker`].
32///
33/// Without extra arguments, delegates to [`QmlMethodInvoker::invoke_method`].
34/// With extra arguments, constructs the argument list and delegates to
35/// [`QmlMethodInvoker::invoke_method_with_args`].
36///
37/// ```
38/// use qtbridge::invoke_method;
39/// use qtbridge::{qobject, QObjectHolder};
40///
41/// #[derive(Default)]
42/// pub struct MyClass { }
43///
44/// #[qobject]
45/// impl MyClass {
46/// #[qslot]
47/// fn set_value(&mut self, value: i32) { }
48///
49/// #[qslot]
50/// fn reset(&self) { }
51/// }
52///
53/// let obj = MyClass::default_with_attached_qobject();
54/// let invoker = obj.borrow().get_qml_method_invoker();
55/// invoke_method!(invoker, "reset");
56/// invoke_method!(invoker, "setValue", 42);
57/// ```
58///
59#[macro_export]
60macro_rules! invoke_method {
61 ($invoker:expr, $name:expr $(,)?) => {{
62 $invoker.invoke_method($name)
63 }};
64
65 ($invoker:expr, $name:expr, $($arg:expr),+ $(,)?) => {{
66 let args = qtbridge::qtbridge_type_lib::QVariantList::from([
67 $(qtbridge::qtbridge_type_lib::QVariant::from($arg)),+
68 ]);
69 $invoker.invoke_method_with_args($name, &args)
70 }};
71}
72
73/// A thread-safe handle for invoking slots and signals on a [`QObjectHolder`].
74///
75/// Calls are scheduled on the Qt event loop and execute on the Qt thread.
76/// If the target object has been dropped, calls are silently discarded.
77///
78/// When a call executes, Qt borrows the `Rc<RefCell<_>>` held by the QML
79/// engine. If the object is already mutably borrowed on the Qt thread at
80/// that moment, the call will panic.
81///
82/// Obtain an instance via [`QObjectHolder::get_qml_method_invoker`].
83///
84/// # Example
85///
86/// ```
87/// # use qtbridge::{qobject, QObjectHolder};
88/// # #[qobject]
89/// # pub mod example {
90/// # #[derive(Default)]
91/// # pub struct Backend {}
92/// # impl Backend {
93/// # #[qsignal]
94/// # pub fn data_ready(&mut self);
95/// # }
96/// # }
97/// # use example::Backend;
98/// let backend = Backend::default_with_attached_qobject();
99/// let invoker = backend.borrow().get_qml_method_invoker();
100/// std::thread::spawn(move || {
101/// invoker.invoke_method("dataReady");
102/// }).join().unwrap();
103/// ```
104pub struct QmlMethodInvoker {
105 obj: *mut QObject,
106 alive: Arc<AtomicBool>,
107}
108
109unsafe impl Send for QmlMethodInvoker {}
110
111impl QmlMethodInvoker {
112
113 /// Creates a `QmlMethodInvoker` for `target` and tracks its lifetime.
114 ///
115 /// Prefer [`QObjectHolder::get_qml_method_invoker`] over calling this directly.
116 pub fn new<T: QObjectHolder>(target: &T) -> Self {
117 let obj = target.get_qobject_ptr();
118 let alive = Arc::new(AtomicBool::new(true));
119 let flag_ptr = Arc::into_raw(alive.clone()) as usize;
120 unsafe { ffi::connect_destroyed_callback(obj, flag_ptr) };
121 Self { obj, alive }
122 }
123
124 fn is_alive(&self) -> bool {
125 self.alive.load(Ordering::Acquire)
126 }
127
128 /// Schedules `name` to run on the Qt thread via the Qt event loop.
129 ///
130 /// `name` must be a `qslot` or `qsignal` on the target object.
131 /// Returns `false` if the target has been dropped or `name` is not found;
132 /// returns `true` otherwise.
133 pub fn invoke_method(&self, name: &str) -> bool {
134 if !self.is_alive() {
135 return false;
136 }
137 QMetaObject::invoke_method(self.obj, name)
138 }
139
140 /// Schedules `name` to run on the Qt thread via the Qt event loop,
141 /// passing `args` to the method.
142 ///
143 /// `name` must be a `qslot` or `qsignal` on the target object.
144 /// Returns `false` if the target has been dropped, `name` is not found,
145 /// or the argument types do not match the method signature;
146 /// returns `true` otherwise.
147 pub fn invoke_method_with_args(&self, name: &str, args: &QVariantList) -> bool {
148 if !self.is_alive() {
149 return false;
150 }
151 QMetaObject::invoke_method_with_args(self.obj, name, args)
152 }
153}