Skip to main content

qtbridge_runtime/
qthreadhandle.rs

1// Copyright (C) 2026 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only
3
4use std::cell::RefCell;
5use std::marker::PhantomData;
6use std::rc::Rc;
7
8use qtbridge_type_lib::QObject;
9
10use crate::QObjectHolder;
11use crate::qproxies::QRustProxy;
12use crate::rustobjectgetter::get_rust_proxy;
13
14#[cxx::bridge]
15pub mod ffi {
16    #[allow(clippy::missing_safety_doc)]
17    unsafe extern "C++" {
18        include!("qtbridge-type-lib/src/core/qobject/cpp/qobject.h");
19        type QObject = qtbridge_type_lib::QObject;
20
21        include!("cpp/qthreadhandle.h");
22        type QThreadHandleCpp;
23
24        /// Creates the C++ guard holding a `QPointer<QObject>` with `target`.
25        ///
26        /// The returned guard may be moved to, used from and destroyed on
27        /// any thread.
28        ///
29        /// A null `target`, or one not living on the application thread,
30        /// yields a guard whose calls are all discarded.
31        ///
32        /// # Safety
33        ///
34        /// Must be called on `target`'s thread, with `target` either alive
35        /// or null.
36        unsafe fn make_qt_thread_handle(target: *mut QObject) -> UniquePtr<QThreadHandleCpp>;
37
38        /// Posts `closure` to the event loop running on the Qt thread.
39        /// Thread-safe; never touches the target object on the calling thread.
40        fn post_closure(handle: &QThreadHandleCpp, closure: Box<QObjectClosureBox>);
41
42        /// Posts `closure` to the event loop running on the Qt thread, without
43        /// a target. Thread-safe.
44        fn post_void_closure(closure: Box<VoidClosureBox>);
45    }
46
47    extern "Rust" {
48        type VoidClosureBox;
49        fn run(self: &mut VoidClosureBox);
50
51        type QObjectClosureBox;
52        fn run(self: &mut QObjectClosureBox, target: &QObject);
53    }
54}
55
56
57type VoidClosure = dyn FnOnce() + Send;
58#[doc(hidden)]
59pub struct VoidClosureBox {
60    f: Option<Box<VoidClosure>>,
61}
62
63impl VoidClosureBox {
64    fn run(&mut self) {
65        if let Some(f) = self.f.take() {
66            f();
67        }
68    }
69}
70
71type QObjectClosure = dyn FnOnce(&QObject) + Send;
72#[doc(hidden)]
73pub struct QObjectClosureBox {
74    f: Option<Box<QObjectClosure>>,
75}
76
77impl QObjectClosureBox {
78    fn run(&mut self, target: &QObject) {
79        if let Some(f) = self.f.take() {
80            f(target);
81        }
82    }
83}
84
85/// A thread-safe handle that schedules Rust closures to run on the Qt thread.
86///
87/// A `QThreadHandle<T>` is bound to a `#[qobject]` instance of type `T` and
88/// hands the closure access to it: shared via [`invoke`](Self::invoke),
89/// mutable via [`invoke_mut`](Self::invoke_mut). Obtain one via
90/// [`QmlObject::get_thread_handle`](crate::QmlObject::get_thread_handle).
91/// The calls are type-checked at compile time and need no `#[qslot]` annotation.
92/// A `QThreadHandle<T>` has to be created on the Qt thread and can only be bound
93/// to `#[qobject]` instances that live on that thread.
94///
95/// A `QThreadHandle<()>`, created via [`QThreadHandle::default`], is not
96/// bound to any `#[qobject]` instance and just runs closures on the Qt thread.
97/// It can be created in any thread.
98///
99/// Closures are posted to the Qt event loop through the application instance;
100/// On delivery the target's liveness is checked on the Qt thread, so a target
101/// deleted at any point is safe: the closure is then silently discarded.
102/// Without an application, closures are discarded as well, and so are the
103/// closures of a handle whose target was created on another thread.
104///
105/// While a closure runs, it holds a borrow of the `Rc<RefCell<_>>` shared
106/// with the QML engine. If the object is already mutably borrowed at that
107/// point (for example by a slot running a nested event loop), the application
108/// is aborted.
109///
110/// # Example
111///
112/// ```
113/// # use qtbridge::{qobject, QmlObject};
114/// # #[qobject]
115/// # pub mod example {
116/// #     #[derive(Default)]
117/// #     pub struct Backend { pub value: i32 }
118/// #     impl Backend {
119/// #         #[qsignal]
120/// #         pub fn data_ready(&mut self);
121/// #     }
122/// # }
123/// # use example::Backend;
124/// let backend = Backend::default_with_attached_qobject();
125/// let handle = backend.borrow().get_thread_handle();
126/// std::thread::spawn(move || {
127///     handle.invoke_mut(|b| {
128///         b.value = 42;
129///         b.data_ready();
130///     });
131/// }).join().unwrap();
132/// ```
133pub struct QThreadHandle<T> {
134    cpp: cxx::UniquePtr<ffi::QThreadHandleCpp>,
135    _target_type: PhantomData<fn(T) -> T>,
136}
137
138// SAFETY: the C++ guard is immutable after construction. `post_closure`
139// only copies the weak reference `QPointer` and posts to the application
140// instance. Dropping the guard releases the weak reference. No
141// dereferencing of the Qt thread.
142unsafe impl<T> Send for QThreadHandle<T> {}
143unsafe impl<T> Sync for QThreadHandle<T> {}
144
145impl Default for QThreadHandle<()> {
146    /// Creates a handle without a target. May be called on any thread.
147    fn default() -> Self {
148        Self { cpp: cxx::UniquePtr::null(), _target_type: PhantomData }
149    }
150}
151
152impl QThreadHandle<()> {
153    /// Schedules `callback` in the Qt event loop.
154    ///
155    /// If there is no application, the callback is silently discarded, along
156    /// with all captured objects.
157    ///
158    /// # Example
159    ///
160    /// ```
161    /// # use qtbridge::QThreadHandle;
162    /// let handle = QThreadHandle::default();
163    /// std::thread::spawn(move || {
164    ///     handle.invoke(|| println!("on the Qt thread"));
165    ///     QThreadHandle::default().invoke(|| println!("also on the Qt thread"));
166    /// }).join().unwrap();
167    /// ```
168    pub fn invoke(&self, callback: impl FnOnce() + Send + 'static) {
169        ffi::post_void_closure(Box::new(VoidClosureBox { f: Some(Box::new(callback)) }));
170    }
171}
172
173impl<T: QObjectHolder> QThreadHandle<T> {
174    /// Creates a `QThreadHandle` for `target`.
175    ///
176    /// Prefer
177    /// [`QmlObject::get_thread_handle`](crate::QmlObject::get_thread_handle)
178    /// over calling this directly.
179    pub fn new(target: &T) -> Self {
180        let obj = target.get_qobject_ptr();
181        let cpp = unsafe { ffi::make_qt_thread_handle(obj) };
182        Self { cpp, _target_type: PhantomData }
183    }
184
185    /// Schedules `callback` in the Qt event loop with shared access to the
186    /// bound target.
187    ///
188    /// If the target has been dropped by the time the event loop delivers
189    /// the call the callback is silently discarded, along with all captured
190    /// objects.
191    pub fn invoke(&self, callback: impl FnOnce(&T) + Send + 'static) {
192        self.post(move |rc| callback(&rc.borrow()));
193    }
194
195    /// Schedules `callback` in the Qt event loop with mutable access to the
196    /// target.
197    ///
198    /// If the target has been dropped by the time the event loop delivers
199    /// the call the callback is silently discarded, along with all captured
200    /// objects.
201    pub fn invoke_mut(&self, callback: impl FnOnce(&mut T) + Send + 'static) {
202        self.post(move |rc| callback(&mut rc.borrow_mut()));
203    }
204
205    #[doc(hidden)]
206    fn post(&self, callback: impl FnOnce(Rc<RefCell<T>>) + Send + 'static) {
207        let erased: Box::<dyn FnOnce(&QObject) + Send> = Box::new(move |qobj| callback(recover_rc::<T>(qobj)));
208        ffi::post_closure(&self.cpp, Box::new(QObjectClosureBox { f: Some(erased) }));
209    }
210}
211
212// This is `QObjectHolder::qobject_to_rc_ref_cell` minus two things it does
213// not need: the inherits check (the handle is typed at construction, so the
214// `QObject` guarded by the `QPointer` is statically a `T`), and the
215// ownership `repin`.
216fn recover_rc<T: QObjectHolder>(qobj: &QObject) -> Rc<RefCell<T>> {
217    let proxy_ptr = get_rust_proxy(qobj) as *const T::ProxyRust;
218    debug_assert!(!proxy_ptr.is_null());
219    let proxy = unsafe { &*proxy_ptr };
220    let rc_adapter = proxy.get_rust_object_rc();
221
222    // SAFETY: see `qobject_to_rc_ref_cell`
223    let raw_ref_cell = Rc::into_raw(rc_adapter).cast::<RefCell<T>>();
224    unsafe { Rc::from_raw(raw_ref_cell) }
225}