< Summary

Information
Class: /home/runner/work/TauriQt/TauriQt/tauri-runtime-qt/src/qt.rs
Assembly: Default
File(s): /home/runner/work/TauriQt/TauriQt/tauri-runtime-qt/src/qt.rs
Line coverage
69%
Covered lines: 71
Uncovered lines: 31
Coverable lines: 102
Total lines: 240
Line coverage: 69.6%
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/qt.rs

#LineLine coverage
 1// SPDX-FileCopyrightText: 2026 Alper Çelik
 2// SPDX-License-Identifier: MIT OR Apache-2.0
 3
 4//! Rust <-> Qt bridge for the runtime.
 5//!
 6//! The C++ side owns the `QApplication` singleton, the window/webview registries
 7//! and the custom URL scheme handlers; Rust drives it through this bridge.
 8//!
 9//! C++ translation units must include the cxx-generated header for this bridge
 10//! (it declares the `extern "Rust"` callbacks below with the correct linkage):
 11//! `#include "tauri-runtime-qt/src/qt.rs.h"`.
 12//!
 13//! The payload encoding the bridge speaks (header block, base64 body marker,
 14//! response framing) lives in `tauri-runtime-qt-core::scheme`; the callbacks and
 15//! the bridge declaration stay here.
 16
 17use parking_lot::Mutex;
 18use tauri_runtime as rt;
 19use tauri_runtime::window::WindowEvent;
 20use tauri_runtime_qt_core as qtcore;
 21
 22#[cxx::bridge]
 23pub mod ffi {
 24    unsafe extern "C++" {
 25        include!("qt_app.hpp");
 26        include!("qt_scheme.hpp");
 27
 28        // ---- application bootstrap (qt_app.cpp) ----
 29        /// Creates the `QApplication` and registers Tauri's URL schemes.
 30        /// Must be called on the main thread before any GUI object exists.
 31        /// Returns 0 on success, non-zero if an application already exists.
 2032        fn qt_init(argc: i32) -> i32;
 33
 34        /// Runs the Qt event loop; returns the process exit code.
 2035        fn qt_run() -> i32;
 36
 37        /// Version string of the Qt runtime actually in use (e.g. "6.11.2").
 038        fn qt_version() -> String;
 39
 40        // ---- custom schemes (qt_scheme.cpp) ----
 41        /// Registers ipc/tauri/asset schemes; must run before the QApplication.
 042        fn qt_scheme_register_schemes();
 43        /// Installs the scheme handlers into the webview's profile.
 2044        fn qt_scheme_install_handler(webview_id: u64);
 45        /// Completes a request that `qt_scheme_request` reported as pending
 46        /// (`handled == 2`).
 2047        fn qt_scheme_respond(request_id: u64, status: i32, headers: &str, body: &str);
 48    }
 49
 50    extern "Rust" {
 51        /// Window events emitted from Qt: 0 = close requested, 1 = focused,
 52        /// 2 = unfocused, 3 = resized ("WxH"), 4 = moved ("x,y").
 53        fn qt_on_window_event(id: u64, kind: i32, payload: &str);
 54
 55        /// Webview events: 1 = load started (url), 2 = load finished (url),
 56        /// 3 = url changed, 4 = title changed, 5 = load failed.
 57        fn qt_on_webview_event(id: u64, kind: i32, payload: &str);
 58
 59        /// Custom-scheme request routed to the handler tauri registered for the
 60        /// webview. `scheme_kind`: 0 = ipc, 1 = tauri, 2 = asset. `headers` is a
 61        /// newline-separated `k: v` list, and the response carries the same
 62        /// encoding back.
 63        fn qt_scheme_request(
 64            webview_id: u64,
 65            request_id: u64,
 66            scheme_kind: i32,
 67            method: &str,
 68            url: &str,
 69            headers: &str,
 70            body: &str,
 71        ) -> QtSchemeResponse;
 72    }
 73
 74    /// Response produced for a custom-scheme request.
 75    struct QtSchemeResponse {
 76        /// 1 = handled (the fields below are valid), 0 = unhandled.
 77        handled: i32,
 78        status: i32,
 79        headers: String,
 80        body: String,
 81    }
 82}
 83
 84/// Window events emitted from Qt (see the bridge declaration for the kinds).
 085pub fn qt_on_window_event(id: u64, kind: i32, payload: &str) {
 086    let event = match kind {
 87        0 => {
 088            let (tx, _rx) = std::sync::mpsc::channel();
 089            WindowEvent::CloseRequested { signal_tx: tx }
 90        }
 091        1 => WindowEvent::Focused(true),
 092        2 => WindowEvent::Focused(false),
 93        3 => {
 094            let (w, h) = qtcore::window::parse_resize_payload(payload);
 095            WindowEvent::Resized(rt::dpi::PhysicalSize::new(w, h))
 96        }
 97        4 => {
 098            let (x, y) = qtcore::window::parse_move_payload(payload);
 099            WindowEvent::Moved(rt::dpi::PhysicalPosition::new(x, y))
 100        }
 0101        _ => return,
 102    };
 103
 0104    for (_, native, handler) in crate::WINDOW_HANDLERS.lock().iter() {
 0105        if *native == id {
 0106            handler(&event);
 0107        }
 108    }
 0109}
 110
 111/// Webview events emitted from Qt (see the bridge declaration for the kinds).
 112///
 113/// `tauri_runtime::window::WebviewEvent` only has `DragDrop` in 2.12; page-load
 114/// and title events must go through the handlers tauri puts on `PendingWebview`.
 120115pub fn qt_on_webview_event(id: u64, kind: i32, payload: &str) {
 120116    crate::qtrace!("[tauri-runtime-qt] webview {id} event {kind}: {payload}");
 120117}
 118
 0119fn unhandled() -> ffi::QtSchemeResponse {
 0120    ffi::QtSchemeResponse {
 0121        handled: 0,
 0122        status: 404,
 0123        headers: String::new(),
 0124        body: String::new(),
 0125    }
 0126}
 127
 128/// Custom-scheme request routed to the handler tauri registered for this webview.
 1260129pub fn qt_scheme_request(
 1260130    webview_id: u64,
 1260131    request_id: u64,
 1260132    scheme_kind: i32,
 1260133    method: &str,
 1260134    url: &str,
 1260135    headers: &str,
 1260136    body: &str,
 1260137) -> ffi::QtSchemeResponse {
 1260138    crate::qtrace!(
 139        "[tauri-runtime-qt] scheme_request webview={webview_id} kind={scheme_kind} {method} {url}"
 140    );
 141
 1260142    let scheme = match scheme_kind {
 1240143        0 => "ipc",
 20144        1 => "tauri",
 0145        2 => "asset",
 0146        _ => return unhandled(),
 147    };
 148
 149    // QtWebEngine never hands us the POST body, so our invoke system carries it
 150    // in `X-Tauriqt-Payload` (base64); the core crate turns the bridge's header
 151    // block back into a normal request for tauri's IPC handler.
 1260152    let Some(request) = qtcore::scheme::decode_request(method, url, headers, body) else {
 0153        return unhandled();
 154    };
 155
 156    // tauri answers most protocols asynchronously; the responder may fire after
 157    // this function returned. In that case we hand the response to C++ through
 158    // `qt_scheme_respond` and report `handled == 2` (pending).
 159    /// Decides, without a race, whether the responder fired inline or later.
 160    struct Gate {
 161        returned: bool,
 162        immediate: Option<ffi::QtSchemeResponse>,
 163    }
 1260164    let gate = std::sync::Arc::new(Mutex::new(Gate {
 1260165        returned: false,
 1260166        immediate: None,
 1260167    }));
 168    // The responder may outlive this call, so it must own its diagnostic text.
 1260169    let method_owned = method.to_owned();
 1260170    let url_owned = url.to_owned();
 171
 172    {
 1260173        let guard = crate::SCHEME_PROTOCOLS.lock();
 1260174        let Some(entry) = guard.iter().find(|(id, _, _)| *id == webview_id) else {
 0175            return unhandled();
 176        };
 1260177        let Some(handler) = entry.2.get(scheme) else {
 0178            return unhandled();
 179        };
 1260180        let label = entry.1.clone();
 181
 1260182        let gate = std::sync::Arc::clone(&gate);
 1260183        let method_owned = method_owned.clone();
 1260184        let url_owned = url_owned.clone();
 1260185        handler(
 1260186            &label,
 1260187            request,
 1260188            Box::new(
 1260189                move |response: http::Response<std::borrow::Cow<'static, [u8]>>| {
 1260190                    let (status, headers, body) = qtcore::scheme::encode_response(response);
 1260191                    crate::qtrace!(
 192                    "[tauri-runtime-qt] {scheme} {method_owned} {url_owned} -> {status} ({} body bytes)",
 1260193                    body.len()
 194                );
 195                    // If `qt_scheme_request` has not returned yet the response can
 196                    // travel back inline; otherwise it must be delivered through
 197                    // `qt_scheme_respond`, because this call outlived the bridge call.
 1260198                    let deferred = {
 1260199                        let mut gate = gate.lock();
 1260200                        if gate.returned {
 20201                            true
 202                        } else {
 1240203                            gate.immediate = Some(ffi::QtSchemeResponse {
 1240204                                handled: 1,
 1240205                                status,
 1240206                                headers: headers.clone(),
 1240207                                body: body.clone(),
 1240208                            });
 1240209                            false
 210                        }
 211                    };
 1260212                    if deferred {
 20213                        ffi::qt_scheme_respond(request_id, status, &headers, &body);
 1240214                    }
 1260215                },
 216            ),
 217        );
 218    }
 219
 220    // Mark the bridge call as finished while holding the lock, so a responder
 221    // that fires concurrently either lands in `immediate` or takes the
 222    // `qt_scheme_respond` path — never neither.
 1260223    let inline = {
 1260224        let mut gate = gate.lock();
 1260225        gate.returned = true;
 1260226        gate.immediate.take()
 227    };
 1260228    if let Some(response) = inline {
 1240229        return response;
 20230    }
 231
 232    // Not answered yet: the C++ side keeps the job alive and waits for
 233    // `qt_scheme_respond`.
 20234    ffi::QtSchemeResponse {
 20235        handled: 2,
 20236        status: 0,
 20237        headers: String::new(),
 20238        body: String::new(),
 20239    }
 1260240}