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::{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}