< Summary

Information
Class: /home/runner/work/TauriQt/TauriQt/tauri-runtime-qt/src/ops/runtime.rs
Assembly: Default
File(s): /home/runner/work/TauriQt/TauriQt/tauri-runtime-qt/src/ops/runtime.rs
Line coverage
26%
Covered lines: 39
Uncovered lines: 109
Coverable lines: 148
Total lines: 375
Line coverage: 26.3%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

File(s)

/home/runner/work/TauriQt/TauriQt/tauri-runtime-qt/src/ops/runtime.rs

#LineLine coverage
 1// SPDX-FileCopyrightText: 2026 Alper Çelik
 2// SPDX-License-Identifier: MIT OR Apache-2.0
 3
 4//! Runtime-level operations (owned by the runtime worker): raw window/display
 5//! handles, the cross-thread event proxy and event-loop control.
 6//!
 7//! # Event flow
 8//!
 9//! `EventLoopProxy::send_event` boxes the event into a process-wide queue and
 10//! wakes the Qt event loop. On the GUI thread the pump tick (`qt_on_pump_tick`
 11//! in `crate::bridge::runtime`, driven by the `QTimer` installed by
 12//! `start_pump()` and by the queued wake-ups) drains the queue into the sink
 13//! installed with `set_event_sink()`. `request_exit()` stores the exit code; the
 14//! tick delivers the events queued before it and then leaves the loop through
 15//! `QCoreApplication::exit(code)`.
 16//!
 17//! # Raw handles
 18//!
 19//! What Qt can answer truthfully (verified against Qt 6.11 sources):
 20//!
 21//! * X11 (xcb plugin): `QWidget::winId()` is the window's XID and
 22//!   `QX11Application` hands out the `xcb_connection_t*` (`Display*` fallback).
 23//! * Wayland: `QWidget::winId()` is the window's `wl_surface*`
 24//!   (`QWaylandWindow::winId()` returns `wlSurface()`, qtbase
 25//!   `src/plugins/platforms/wayland/qwaylandwindow.cpp`), and
 26//!   `QWaylandApplication` hands out the `wl_display*`.
 27//! * Every other platform plugin (offscreen, minimal, eglfs, …) and a missing
 28//!   application: no window handle exists — the offscreen plugin even answers
 29//!   `QX11Application` with a GLX-only `Display*` and returns counter values
 30//!   from `winId()` — so `HandleError::Unavailable` is returned instead of a
 31//!   fabricated value. The plugin name gates the decision (`platformConnection`
 32//!   in `qt_runtime.cpp`).
 33
 34use std::any::Any;
 35use std::collections::VecDeque;
 36use std::ffi::c_void;
 37use std::num::NonZeroU32;
 38use std::os::raw::{c_int, c_ulong};
 39use std::ptr::NonNull;
 40use std::sync::atomic::{AtomicI32, Ordering};
 41
 42use parking_lot::Mutex;
 43use raw_window_handle::{
 44    DisplayHandle, HandleError, RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle,
 45    WaylandWindowHandle, WindowHandle, XcbDisplayHandle, XcbWindowHandle, XlibDisplayHandle,
 46    XlibWindowHandle,
 47};
 48use tauri_runtime::{Error, Result, UserEvent};
 49
 50use crate::bridge::runtime::ffi;
 51
 52/// Platform kinds reported by `qt_runtime_platform_kind()` (see the bridge);
 53/// anything else — including 0, "no application / unsupported platform" — maps
 54/// to `NativePlatform::None`.
 55const PLATFORM_XCB: i32 = 1;
 56const PLATFORM_XLIB: i32 = 2;
 57const PLATFORM_WAYLAND: i32 = 3;
 58
 59/// `EXIT_CODE` value meaning "no exit requested".
 60const NO_EXIT: i32 = i32::MIN;
 61
 62/// X11 screen index reported in display handles. Qt's `QX11Application` exposes
 63/// `display()` and `connection()` only — no screen number — and Qt connects to
 64/// the connection's default screen, which is index 0.
 65const X11_DEFAULT_SCREEN: c_int = 0;
 66
 67/// Events queued by `proxy_send_event`, drained on the Qt GUI thread.
 68static EVENT_QUEUE: Mutex<VecDeque<Box<dyn Any + Send>>> = Mutex::new(VecDeque::new());
 69
 70/// Where drained events go; installed by the integrator from `run()`.
 71static EVENT_SINK: Mutex<Option<Box<dyn FnMut(Box<dyn Any + Send>) + Send>>> = Mutex::new(None);
 72
 73/// Pending exit code (see `request_exit`).
 74static EXIT_CODE: AtomicI32 = AtomicI32::new(NO_EXIT);
 75
 76/// Queues `event` for delivery on the Qt GUI thread.
 77///
 78/// Safe and non-blocking from any thread, and usable before the event loop
 79/// runs (the queue is drained by the first pump tick).
 080pub(crate) fn proxy_send_event<T: UserEvent>(event: T) -> Result<()> {
 081    enqueue(Box::new(event));
 082    Ok(())
 083}
 84
 85/// Pushes a boxed event into the queue and schedules a drain.
 86///
 87/// One wake-up is posted per event: the GUI thread processes the posted
 88/// invocation after the event was pushed by this thread, so the drain it
 89/// triggers always sees the event. (Coalescing the wake-ups would risk losing
 90/// the last one; an extra queued invocation is cheap.)
 091fn enqueue(event: Box<dyn Any + Send>) {
 092    EVENT_QUEUE.lock().push_back(event);
 093    ffi::qt_runtime_wake();
 094}
 95
 96/// Installs the sink that receives every drained event.
 97///
 98/// The integrator calls this once from `run()`/`run_return()`; calling it again
 99/// replaces the previous sink. The sink is invoked exclusively on the Qt GUI
 100/// thread (from the pump tick) and must not call `set_event_sink` itself — use
 101/// `main_thread_sink` when the closure is not `Send`.
 20102pub fn set_event_sink(sink: Box<dyn FnMut(Box<dyn Any + Send>) + Send>) {
 20103    let sink = debug::wrap_sink(sink);
 20104    *EVENT_SINK.lock() = Some(sink);
 20105}
 106
 107/// Closure wrapper that is only ever invoked on the Qt GUI thread but may be
 108/// stored in a `Send` slot (see `main_thread_sink`).
 109struct MainThreadOnly<F>(F);
 110
 111// SAFETY: the pump tick — see `main_thread_sink` — is the only caller and it
 112// runs on the Qt GUI thread.
 113unsafe impl<F> Send for MainThreadOnly<F> {}
 114
 115impl<F: FnMut(Box<dyn Any + Send>)> MainThreadOnly<F> {
 0116    fn call(&mut self, event: Box<dyn Any + Send>) {
 0117        (self.0)(event)
 0118    }
 119}
 120
 121/// Wraps a closure that is only ever invoked on the Qt GUI thread so it can be
 122/// handed to `set_event_sink`, which requires `Send` for its process-wide slot.
 123///
 124/// The trait's `run`/`run_return` take `FnMut(RunEvent<T>)` without a `Send`
 125/// bound, so the callback may capture `Rc`/`RefCell` state; the wrapper asserts
 126/// that this never matters because the pump drains on the GUI thread only.
 20127pub fn main_thread_sink<F>(sink: F) -> Box<dyn FnMut(Box<dyn Any + Send>) + Send>
 20128where
 20129    F: FnMut(Box<dyn Any + Send>) + 'static,
 130{
 131    // Calling through the wrapper (instead of `sink.0`) keeps the closure from
 132    // capturing the inner `F` by itself, which would require `F: Send`.
 20133    let mut sink = MainThreadOnly(sink);
 20134    Box::new(move |event| sink.call(event))
 20135}
 136
 137/// Installs the Qt-side drain timer; called once from `run()`/`run_return()`
 138/// after `set_event_sink`.
 20139pub fn start_pump() {
 20140    if !ffi::qt_runtime_start_pump() {
 0141        crate::qtrace!("[tauri-runtime-qt] start_pump: no QApplication yet, events stay queued");
 20142    }
 20143    debug::fire_probe();
 20144}
 145
 146/// Requests an exit of the Qt event loop with `code`.
 147///
 148/// Safe from any thread and non-blocking: the code is stored and the GUI thread
 149/// exits on the next pump tick, after delivering the events that were queued
 150/// before this call. Returns `Err(Error::EventLoopClosed)` when no
 151/// `QCoreApplication` exists (nothing to exit); events queued before `run()`
 152/// start are still delivered, because the first tick exits.
 0153pub(crate) fn request_exit(code: i32) -> Result<()> {
 0154    if !ffi::qt_runtime_has_application() {
 0155        return Err(Error::EventLoopClosed);
 0156    }
 0157    EXIT_CODE.store(code, Ordering::SeqCst);
 0158    ffi::qt_runtime_wake();
 0159    Ok(())
 0160}
 161
 162/// Runs one turn of the Qt event loop without blocking.
 0163pub(crate) fn run_iteration() {
 0164    ffi::qt_runtime_process_events();
 0165}
 166
 167/// Pump tick; runs on the Qt GUI thread (see `bridge::runtime::qt_on_pump_tick`).
 13351168pub(crate) fn pump_tick() {
 169    // Take the queue under the lock but feed the sink outside of it: the sink
 170    // may send further events through the proxy, and the queue mutex is not
 171    // reentrant.
 13351172    let drained: Vec<Box<dyn Any + Send>> = EVENT_QUEUE.lock().drain(..).collect();
 173
 13351174    if !drained.is_empty() {
 0175        let mut slot = EVENT_SINK.lock();
 0176        match slot.as_mut() {
 0177            Some(sink) => {
 0178                for event in drained {
 0179                    sink(event);
 0180                }
 181            }
 182            // No sink yet (`run()` has not started): hold the events for the
 183            // next tick instead of dropping them.
 184            None => {
 0185                let mut queue = EVENT_QUEUE.lock();
 0186                for event in drained.into_iter().rev() {
 0187                    queue.push_front(event);
 0188                }
 189            }
 190        }
 13351191    }
 192
 193    // Exit last: everything queued before the exit request was delivered above.
 13351194    let code = EXIT_CODE.swap(NO_EXIT, Ordering::SeqCst);
 13351195    if code != NO_EXIT {
 0196        ffi::qt_runtime_exit(code);
 13351197    }
 13351198}
 199
 200/// Native platform Qt is running on.
 201#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 202enum NativePlatform {
 203    /// No QGuiApplication yet, or a plugin without native handles.
 204    None,
 205    /// X11 through the xcb plugin: XID + `xcb_connection_t*`.
 206    Xcb,
 207    /// X11 with only the Xlib `Display*` available.
 208    Xlib,
 209    /// Wayland: `wl_surface*` + `wl_display*`.
 210    Wayland,
 211}
 212
 213/// Name of the platform plugin Qt is running on ("wayland", "xcb", …).
 20214pub fn platform_name() -> String {
 20215    ffi::qt_runtime_platform_name()
 20216}
 217
 0218fn native_platform() -> NativePlatform {
 0219    match ffi::qt_runtime_platform_kind() {
 0220        PLATFORM_XCB => NativePlatform::Xcb,
 0221        PLATFORM_XLIB => NativePlatform::Xlib,
 0222        PLATFORM_WAYLAND => NativePlatform::Wayland,
 0223        _ => NativePlatform::None,
 224    }
 0225}
 226
 227/// Raw handle of the Qt window behind the runtime's `native` handle.
 228///
 229/// X11: `QWidget::winId()` is the XID; the xcb variant is used when Qt has an
 230/// xcb connection, the Xlib variant otherwise. Wayland: `QWidget::winId()` is
 231/// the window's `wl_surface*`. Unknown handles, missing windows and platforms
 232/// without native handles report `HandleError::Unavailable`.
 0233pub(crate) fn window_handle(
 0234    native: u64,
 0235) -> std::result::Result<WindowHandle<'static>, HandleError> {
 0236    let platform = native_platform();
 0237    let winid = ffi::qt_runtime_window_winid(native);
 238
 0239    let raw = match platform {
 240        NativePlatform::Xcb => {
 0241            let window = u32::try_from(winid)
 0242                .ok()
 0243                .and_then(NonZeroU32::new)
 0244                .ok_or(HandleError::Unavailable)?;
 0245            RawWindowHandle::Xcb(XcbWindowHandle::new(window))
 246        }
 247        NativePlatform::Xlib => {
 0248            if winid == 0 {
 0249                return Err(HandleError::Unavailable);
 0250            }
 0251            RawWindowHandle::Xlib(XlibWindowHandle::new(winid as c_ulong))
 252        }
 253        NativePlatform::Wayland => {
 0254            let surface = NonNull::new(winid as *mut c_void).ok_or(HandleError::Unavailable)?;
 0255            RawWindowHandle::Wayland(WaylandWindowHandle::new(surface))
 256        }
 0257        NativePlatform::None => return Err(HandleError::Unavailable),
 258    };
 259
 260    // SAFETY: the handle describes the live Qt platform resource. Qt keeps it
 261    // alive for at least as long as the window exists, and the returned borrow
 262    // never outlives the process-lifetime QGuiApplication that owns it.
 0263    Ok(unsafe { WindowHandle::borrow_raw(raw) })
 0264}
 265
 266/// Raw display handle of the Qt platform connection.
 267///
 268/// The pointers come from the `QNativeInterface` the platform plugin
 269/// implements (`QX11Application` / `QWaylandApplication`); platforms without
 270/// one report `HandleError::Unavailable`.
 0271pub(crate) fn display_handle() -> std::result::Result<DisplayHandle<'static>, HandleError> {
 0272    let platform = native_platform();
 0273    let pointer = ffi::qt_runtime_display_pointer();
 274
 0275    let raw = match platform {
 276        NativePlatform::Xcb => {
 0277            let connection =
 0278                NonNull::new(pointer as *mut c_void).ok_or(HandleError::Unavailable)?;
 0279            RawDisplayHandle::Xcb(XcbDisplayHandle::new(Some(connection), X11_DEFAULT_SCREEN))
 280        }
 281        NativePlatform::Xlib => {
 0282            let display = NonNull::new(pointer as *mut c_void).ok_or(HandleError::Unavailable)?;
 0283            RawDisplayHandle::Xlib(XlibDisplayHandle::new(Some(display), X11_DEFAULT_SCREEN))
 284        }
 285        NativePlatform::Wayland => {
 0286            let display = NonNull::new(pointer as *mut c_void).ok_or(HandleError::Unavailable)?;
 0287            RawDisplayHandle::Wayland(WaylandDisplayHandle::new(display))
 288        }
 0289        NativePlatform::None => return Err(HandleError::Unavailable),
 290    };
 291
 292    // SAFETY: the connection is owned by the Qt platform plugin and lives until
 293    // the process exits; the borrow does not outlive it.
 0294    Ok(unsafe { DisplayHandle::borrow_raw(raw) })
 0295}
 296
 297/// `TAURIQT_PROXY_DEBUG=1` end-to-end probe of the proxy path. It logs every
 298/// event the sink receives, queues three probe events from a worker thread at
 299/// pump start and then asks the loop to `exit(0)`. The whole module is dormant
 300/// unless the environment variable is set.
 301mod debug {
 302    use super::*;
 303    use std::sync::Once;
 304    use std::time::Duration;
 305
 306    static PROBE: Once = Once::new();
 307
 40308    pub fn enabled() -> bool {
 40309        std::env::var_os("TAURIQT_PROXY_DEBUG").is_some_and(|value| value != "0")
 40310    }
 311
 312    /// Logs each event on its way into the real sink.
 20313    pub fn wrap_sink(
 20314        mut sink: Box<dyn FnMut(Box<dyn Any + Send>) + Send>,
 20315    ) -> Box<dyn FnMut(Box<dyn Any + Send>) + Send> {
 20316        if !enabled() {
 20317            return sink;
 0318        }
 0319        let mut received = 0usize;
 0320        Box::new(move |event| {
 0321            received += 1;
 0322            match event.downcast_ref::<String>() {
 0323                Some(payload) => eprintln!(
 324                    "[tauri-runtime-qt] proxy-debug: sink received event #{received}: {payload}"
 325                ),
 0326                None => eprintln!(
 327                    "[tauri-runtime-qt] proxy-debug: sink received event #{received} (non-probe payload)"
 328                ),
 329            }
 0330            sink(event);
 0331        })
 20332    }
 333
 334    /// Queues three probe events from a worker thread, then requests exit(0).
 20335    pub fn fire_probe() {
 20336        if !enabled() {
 20337            return;
 0338        }
 0339        PROBE.call_once(|| {
 0340            eprintln!(
 341                "[tauri-runtime-qt] proxy-debug: native state: {}",
 0342                ffi::qt_runtime_debug_native_state()
 343            );
 0344            eprintln!(
 345                "[tauri-runtime-qt] proxy-debug: display_handle() = {:?}",
 0346                display_handle()
 347            );
 0348            eprintln!(
 349                "[tauri-runtime-qt] proxy-debug: window_handle(u64::MAX) = {:?}",
 0350                window_handle(u64::MAX)
 351            );
 352            // The registry hands out 1-based handles; report the first real
 353            // window as well (unknown ids answer with `Unavailable`).
 0354            for id in 1..=3u64 {
 0355                if let Ok(handle) = window_handle(id) {
 0356                    eprintln!("[tauri-runtime-qt] proxy-debug: window_handle({id}) = {handle:?}");
 0357                    break;
 0358                }
 359            }
 0360            eprintln!(
 361                "[tauri-runtime-qt] proxy-debug: queueing 3 probe events from a worker thread"
 362            );
 0363            std::thread::spawn(|| {
 0364                for index in 1..=3 {
 0365                    std::thread::sleep(Duration::from_millis(200));
 0366                    eprintln!("[tauri-runtime-qt] proxy-debug: worker queues probe #{index}");
 0367                    enqueue(Box::new(format!("proxy-probe-{index}")));
 0368                }
 0369                std::thread::sleep(Duration::from_millis(400));
 0370                eprintln!("[tauri-runtime-qt] proxy-debug: worker requests exit(0)");
 0371                let _ = request_exit(0);
 0372            });
 0373        });
 20374    }
 375}

Methods/Properties

_RINvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtime16proxy_send_eventNtCsayqiEzhWKCy_5tauri16EventLoopMessageECsl9yYLK4ftHb_7tauriqt
_RINvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtime16proxy_send_eventpEB6_
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtime7enqueue
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtime14set_event_sink
_RNvMs_NtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtimeINtB4_14MainThreadOnlyNCINvXs3_B8_NtB8_9QtRuntimeINtCs5B3ewcnoLTC_13tauri_runtime7RuntimeNtCsayqiEzhWKCy_5tauri16EventLoopMessageE3runNCINvMsc_NtB2j_3appINtB39_3AppB1o_E28make_run_event_loop_callbackNCNvMsf_B39_INtB39_7BuilderB1o_E3run0E0E0E4callCsl9yYLK4ftHb_7tauriqt
_RNvMs_NtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtimeINtB4_14MainThreadOnlypE4callB8_
_RINvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtime16main_thread_sinkNCINvXs3_B6_NtB6_9QtRuntimeINtCs5B3ewcnoLTC_13tauri_runtime7RuntimeNtCsayqiEzhWKCy_5tauri16EventLoopMessageE3runNCINvMsc_NtB2d_3appINtB33_3AppB1i_E28make_run_event_loop_callbackNCNvMsf_B33_INtB33_7BuilderB1i_E3run0E0E0ECsl9yYLK4ftHb_7tauriqt
_RINvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtime16main_thread_sinkpEB6_
_RNCINvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtime16main_thread_sinkNCINvXs3_B8_NtB8_9QtRuntimeINtCs5B3ewcnoLTC_13tauri_runtime7RuntimeNtCsayqiEzhWKCy_5tauri16EventLoopMessageE3runNCINvMsc_NtB2f_3appINtB35_3AppB1k_E28make_run_event_loop_callbackNCNvMsf_B35_INtB35_7BuilderB1k_E3run0E0E0E0Csl9yYLK4ftHb_7tauriqt
_RNCINvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtime16main_thread_sinkpE0B8_
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtime10start_pump
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtime12request_exit
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtime13run_iteration
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtime9pump_tick
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtime13platform_name
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtime15native_platform
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtime13window_handle
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtime14display_handle
_RNvNtNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtime5debug7enabled
_RNCNvNtNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtime5debug7enabled0B9_
_RNvNtNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtime5debug9wrap_sink
_RNCNvNtNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtime5debug9wrap_sink0B9_
_RNvNtNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtime5debug10fire_probe
_RNCNvNtNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtime5debug10fire_probe0B9_
_RNCNCNvNtNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7runtime5debug10fire_probe00Bb_