Skip to main content

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}