cxx_qt_lib_extras/core/qeventloop.rs
1// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
2// SPDX-FileContributor: Joshua Booth <joshua.n.booth@gmail.com>
3//
4// SPDX-License-Identifier: MIT OR Apache-2.0
5
6use std::pin::Pin;
7use std::time::Duration;
8
9use cxx::{type_id, UniquePtr};
10use cxx_qt_lib::{QFlag, QFlags};
11
12#[cxx_qt::bridge]
13mod ffi {
14 /// This enum controls the types of events processed by [`QEventLoop::process_events`].
15 #[repr(i32)]
16 enum QEventLoopProcessEventsFlag {
17 /// All events. Note that [QEvent::DeferredDelete](https://doc.qt.io/qt/qevent.html#Type-enum) events are processed specially. See [QObject::deleteLater](https://doc.qt.io/qt/qobject.html#deleteLater)() for more details.
18 AllEvents = 0x00,
19 /// Do not process user input events, such as [QEvent::MouseButtonPress](https://doc.qt.io/qt/qevent.html#Type-enum) and [QEvent::KeyPress](https://doc.qt.io/qt/qevent.html#Type-enum). Note that the events are not discarded; they will be delivered the next time [`QEventLoop::process_events`] is called without this flag.
20 ExcludeUserInputEvents = 0x01,
21 /// Do not process socket notifier events. Note that the events are not discarded; they will be delivered the next time [`QEventLoop::process_events`] is called without this flag.
22 ExcludeSocketNotifiers = 0x02,
23 /// Wait for events if no pending events are available.
24 WaitForMoreEvents = 0x04,
25 }
26
27 extern "C++" {
28 include!("cxx-qt-lib-extras/core/qeventloop.h");
29 type QEventLoopProcessEventsFlag;
30 type QEventLoopProcessEventsFlags = super::QEventLoopProcessEventsFlags;
31 }
32
33 extern "Rust" {
34 type EventLoopClosure<'a>;
35 }
36
37 unsafe extern "C++Qt" {
38 /// The `QEventLoop` class provides a means of entering and leaving an event loop.
39 ///
40 /// Qt Documentation: [QEventLoop](https://doc.qt.io/qt/qeventloop.html#details)
41 #[qobject]
42 type QEventLoop;
43
44 /// Enters the main event loop and waits until [`exit`](Self::exit) is called. Returns the value that was passed to [`exit`](Self::exit).
45 ///
46 /// Only events of the types allowed by `flags` will be processed.
47 ///
48 /// It is necessary to call this function to start event handling. The main event loop receives events from the window system and dispatches these to the application widgets.
49 ///
50 /// Generally speaking, no user interaction can take place before calling this function. As a special case, modal widgets like [QMessageBox](https://doc.qt.io/qt/qmessagebox.html) can be used before calling this function, because modal widgets use their own local event loop.
51 ///
52 /// To make your application perform idle processing (i.e. executing a special function whenever there are no pending events), use a [QChronoTimer](https://doc.qt.io/qt/qchronotimer.html) with 0ns timeout. More sophisticated idle processing schemes can be achieved using [`process_events`](QEventLoop::process_events).
53 fn exec(self: Pin<&mut QEventLoop>, flags: QEventLoopProcessEventsFlags) -> i32;
54
55 /// Tells the event loop to exit with a return code.
56 ///
57 /// After this function has been called, the event loop returns from the call to [`exec`](Self::exec) or [`exec_all`](Self::exec_all). The call returns `return_code`.
58 ///
59 /// By convention, a `return_code` of 0 means success, and any non-zero value indicates an error.
60 ///
61 /// Note that unlike the C library function of the same name, this function does return to the caller – it is event processing that stops.
62 fn exit(self: Pin<&mut QEventLoop>, return_code: i32);
63
64 /// Processes some pending events that match `flags`. Returns `true` if pending events were handled; otherwise returns `false`.
65 ///
66 /// This function is especially useful if you have a long running operation and want to show its progress without allowing user input; i.e. by using the [`QEventLoopProcessEventsFlag::ExcludeUserInputEvents`] flag.
67 ///
68 /// This function is simply a wrapper for [QAbstractEventDispatcher::processEvents](https://doc.qt.io/qt/qabstracteventdispatcher.html#processEvents)(). See the documentation for that function for details.
69 #[rust_name = "process_events"]
70 fn processEvents(self: Pin<&mut QEventLoop>, flags: QEventLoopProcessEventsFlags) -> bool;
71
72 #[doc(hidden)]
73 #[rust_name = "process_events_until_msecs"]
74 fn processEvents(
75 self: Pin<&mut QEventLoop>,
76 flags: QEventLoopProcessEventsFlags,
77 max_time: i32,
78 );
79
80 /// Tells the event loop to exit normally.
81 ///
82 /// Same as [`self.exit(0)`](Self::exit).
83 fn quit(self: Pin<&mut QEventLoop>);
84
85 /// Wakes up the event loop.
86 #[rust_name = "wake_up"]
87 fn wakeUp(self: Pin<&mut QEventLoop>);
88 }
89
90 #[namespace = "rust::cxxqtlib1"]
91 unsafe extern "C++" {
92 #[allow(clippy::needless_lifetimes)]
93 #[doc(hidden)]
94 #[rust_name = "qeventloop_exec_with"]
95 fn qeventloopExecWith<'a>(
96 event_loop: Pin<&mut QEventLoop>,
97 context: &mut EventLoopClosure<'a>,
98 functor: fn(&mut EventLoopClosure<'a>),
99 ) -> i32;
100 }
101
102 #[namespace = "rust::cxxqtlib1"]
103 unsafe extern "C++" {
104 include!("cxx-qt-lib/common.h");
105
106 #[doc(hidden)]
107 #[rust_name = "qeventloop_init_default"]
108 fn make_unique() -> UniquePtr<QEventLoop>;
109 }
110}
111
112pub use ffi::{QEventLoop, QEventLoopProcessEventsFlag};
113
114/// [`QFlags`] of [`QEventLoopProcessEventsFlag`].
115pub type QEventLoopProcessEventsFlags = QFlags<QEventLoopProcessEventsFlag>;
116
117unsafe impl QFlag for QEventLoopProcessEventsFlag {
118 type TypeId = type_id!("QEventLoopProcessEventsFlags");
119
120 type Repr = i32;
121
122 fn to_repr(self) -> Self::Repr {
123 self.repr
124 }
125}
126
127impl QEventLoop {
128 /// Constructs an event loop object.
129 pub fn new() -> UniquePtr<Self> {
130 ffi::qeventloop_init_default()
131 }
132
133 /// Enters the main event loop and waits until [`exit`](Self::exit) is called. Returns the value that was passed to [`exit`](Self::exit).
134 ///
135 /// It is necessary to call this function to start event handling. The main event loop receives events from the window system and dispatches these to the application widgets.
136 ///
137 /// Generally speaking, no user interaction can take place before calling this function. As a special case, modal widgets like [QMessageBox](https://doc.qt.io/qt/qmessagebox.html) can be used before calling this function, because modal widgets use their own local event loop.
138 ///
139 /// To make your application perform idle processing (i.e. executing a special function whenever there are no pending events), use a [QChronoTimer](https://doc.qt.io/qt/qchronotimer.html) with 0ns timeout. More sophisticated idle processing schemes can be achieved using [`process_all_events`](Self::process_all_events).
140 pub fn exec_all(self: Pin<&mut Self>) -> i32 {
141 self.exec(QEventLoopProcessEventsFlag::AllEvents.into())
142 }
143
144 /// Enters an event loop, runs a `closure`, and exits the event loop when the closure completes.
145 ///
146 /// As with `QEventLoop`'s other methods, a [`QApplication`](crate::QApplication), [`QGuiApplication`](cxx_qt_lib::QGuiApplication), or [`QCoreApplication`](cxx_qt_lib::QCoreApplication) must be running.
147 pub fn exec_with<F>(self: Pin<&mut QEventLoop>, closure: F)
148 where
149 F: FnOnce(),
150 {
151 let mut closure = EventLoopClosure {
152 closure: Some(Box::new(closure)),
153 };
154 ffi::qeventloop_exec_with(self, &mut closure, EventLoopClosure::run);
155 }
156
157 /// Processes some pending events. Returns `true` if pending events were handled; otherwise returns `false`.
158 ///
159 /// This function is simply a wrapper for [QAbstractEventDispatcher::processEvents](https://doc.qt.io/qt/qabstracteventdispatcher.html#processEvents)(). See the documentation for that function for details.
160 pub fn process_all_events(self: Pin<&mut QEventLoop>) -> bool {
161 self.process_events(QEventLoopProcessEventsFlag::AllEvents.into())
162 }
163
164 /// Process pending events that match `flags` until `deadline` has expired, or until there are no more events to process, whichever happens first. This function is especially useful if you have a long running operation and want to show its progress without allowing user input, i.e. by using the [`QEventLoopProcessEventsFlag::ExcludeUserInputEvents`] flag.
165 ///
166 /// **Notes:**
167 ///
168 /// * This function does not process events continuously; it returns after all available events are processed.
169 /// * Specifying the [`QEventLoopProcessEventsFlag::WaitForMoreEvents`] flag makes no sense and will be ignored.
170 pub fn process_events_until(
171 self: Pin<&mut QEventLoop>,
172 flags: QEventLoopProcessEventsFlags,
173 deadline: Duration,
174 ) {
175 self.process_events_until_msecs(
176 flags,
177 i32::try_from(deadline.as_millis()).unwrap_or(i32::MAX),
178 );
179 }
180
181 /// Process pending events until `deadline` has expired, or until there are no more events to process, whichever happens first.
182 ///
183 /// **Note:** This function does not process events continuously; it returns after all available events are processed.
184 pub fn process_all_events_until<T>(self: Pin<&mut QEventLoop>, deadline: Duration) {
185 self.process_events_until(QEventLoopProcessEventsFlag::AllEvents.into(), deadline);
186 }
187}
188
189struct EventLoopClosure<'a> {
190 closure: Option<Box<dyn FnOnce() + 'a>>,
191}
192
193impl<'a> EventLoopClosure<'a> {
194 pub fn run(&mut self) {
195 self.closure.take().unwrap()();
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use cxx_qt_lib::QCoreApplication;
202
203 use super::QEventLoop;
204
205 #[test]
206 fn qeventloop_exec_with() {
207 std::mem::forget(QCoreApplication::new()); // cargo test may randomly segfault if app is dropped
208 let mut increment_count = 0;
209 QEventLoop::new().pin_mut().exec_with(|| {
210 increment_count += 1;
211 });
212 assert_eq!(increment_count, 1);
213 }
214}