Skip to content

Commit d795722

Browse files
authored
Add QEventLoop to cxx-qt-lib-extras (#1264)
* cxx-qt-lib-extras: add QEventLoop * update changelog * cxx-qt-lib-extras: add missing #[doc(hidden)] pragmas * cxx-qt-lib-extras: include <climits> * cxx-qt-lib-extras: export QEventLoop enums * cxx-qt-lib-extras: postpone loop quit * cxx-qt-lib-extras: remove catch-unwind logic from QEventLoop * cxx-qt-lib-extras: also test that the QEventLoop closure only runs once
1 parent c7a92c5 commit d795722

File tree

5 files changed

+279
-0
lines changed

5 files changed

+279
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3434
- Casting is automatically implemented for qobjects or types which have `#[base = T]` in `"RustQt"` or `"C++Qt"` blocks
3535
- Support for `QMessageLogContext` and sending log messages to the Qt message handler.
3636
- Serde support for further types: `QByteArray`, `QSet`, `QStringList`, `QVector`, `QUrl`
37+
- Added `QEventLoop` to cxx-qt-lib-extras.
3738

3839
### Removed
3940

crates/cxx-qt-lib-extras/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ fn main() {
5454

5555
let rust_bridges = vec![
5656
"core/qelapsedtimer",
57+
"core/qeventloop",
5758
"core/qcommandlineoption",
5859
"core/qcommandlineparser",
5960
"gui/qapplication",
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// clang-format off
2+
// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
3+
// clang-format on
4+
// SPDX-FileContributor: Joshua Booth <[email protected]>
5+
//
6+
// SPDX-License-Identifier: MIT OR Apache-2.0
7+
#pragma once
8+
9+
#include <QtCore/QCoreApplication>
10+
#include <QtCore/QEvent>
11+
#include <QtCore/QEventLoop>
12+
#include <climits>
13+
14+
#include "rust/cxx.h"
15+
16+
using QEventLoopProcessEventsFlag = QEventLoop::ProcessEventsFlag;
17+
using QEventLoopProcessEventsFlags = QEventLoop::ProcessEventsFlags;
18+
19+
namespace rust {
20+
namespace cxxqtlib1 {
21+
22+
template<typename T>
23+
class QEventLoopClosureEvent : public QEvent
24+
{
25+
public:
26+
inline QEventLoopClosureEvent(QEventLoop& eventLoop,
27+
T& context,
28+
rust::Fn<void(T&)> closure)
29+
: QEvent(QEvent::User)
30+
, eventLoop(eventLoop)
31+
, context(context)
32+
, closure(closure) {};
33+
34+
~QEventLoopClosureEvent() override
35+
{
36+
(*closure)(context);
37+
QCoreApplication::postEvent(&eventLoop, new QEvent(QEvent::Quit), INT_MIN);
38+
}
39+
40+
private:
41+
QEventLoop& eventLoop;
42+
T& context;
43+
rust::Fn<void(T&)> closure;
44+
};
45+
46+
template<typename T>
47+
int
48+
qeventloopExecWith(QEventLoop& eventLoop,
49+
T& context,
50+
rust::Fn<void(T&)> closure)
51+
{
52+
QCoreApplication::postEvent(
53+
&eventLoop,
54+
new QEventLoopClosureEvent(eventLoop, context, closure),
55+
INT_MAX);
56+
return eventLoop.exec();
57+
}
58+
59+
}
60+
}

crates/cxx-qt-lib-extras/src/core/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
//
44
// SPDX-License-Identifier: MIT OR Apache-2.0
55

6+
mod qeventloop;
7+
pub use qeventloop::{QEventLoop, QEventLoopProcessEventsFlag, QEventLoopProcessEventsFlags};
8+
69
mod qelapsedtimer;
710
pub use qelapsedtimer::QElapsedTimer;
811

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
2+
// SPDX-FileContributor: Joshua Booth <[email protected]>
3+
//
4+
// SPDX-License-Identifier: MIT OR Apache-2.0
5+
6+
use std::pin::Pin;
7+
use std::time::Duration;
8+
9+
use cxx::{type_id, UniquePtr};
10+
use cxx_qt_lib::{QFlag, QFlags};
11+
12+
#[cxx_qt::bridge]
13+
mod ffi {
14+
/// This enum controls the types of events processed by [`QEventLoop::process_filtered_events`].
15+
#[repr(i32)]
16+
enum QEventLoopProcessEventsFlag {
17+
/// All events. Note that [`QEventType::DeferredDelete`](https://doc.qt.io/qt-6/qevent.html#Type-enum) events are processed specially. See [`QObject::delete_later`](https://doc.qt.io/qt-6/qobject.html#deleteLater) for more details.
18+
AllEvents = 0x00,
19+
/// Do not process user input events, such as [QEventType::MouseButtonPress](https://doc.qt.io/qt-6/qevent.html#Type-enum) and [QEventType::KeyPress](https://doc.qt.io/qt-6/qevent.html#Type-enum). Note that the events are not discarded; they will be delivered the next time [`QEventLoop::process_filtered_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_filtered_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/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-6/qeventloop.html#details)
41+
#[qobject]
42+
type QEventLoop;
43+
44+
/// Enters the main event loop and waits until [`exit`](QEventLoop::exit) is called. Returns the value that was passed to [`exit`](QEventLoop::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-6/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-6/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`](QeventLoop::exec) or [`exec_all`](QeventLoop::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::process_events`](https://doc.qt.io/qt-6/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)`](QEventLoop::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+
112+
pub use ffi::{QEventLoop, QEventLoopProcessEventsFlag};
113+
114+
/// [`QFlags`] of [`QEventLoopProcessEventsFlag`].
115+
pub type QEventLoopProcessEventsFlags = QFlags<QEventLoopProcessEventsFlag>;
116+
117+
unsafe 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+
127+
impl 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`](QEventLoop::exit) is called. Returns the value that was passed to [`exit`](QEventLoop::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-6/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-6/qchronotimer.html) with 0ns timeout. More sophisticated idle processing schemes can be achieved using [`process_all_events`](QEventLoop::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::process_events`](https://doc.qt.io/qt-6/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+
189+
struct EventLoopClosure<'a> {
190+
closure: Option<Box<dyn FnOnce() + 'a>>,
191+
}
192+
193+
impl<'a> EventLoopClosure<'a> {
194+
pub fn run(&mut self) {
195+
self.closure.take().unwrap()();
196+
}
197+
}
198+
199+
#[cfg(test)]
200+
mod 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+
}

0 commit comments

Comments
 (0)