< 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.
 2132        fn qt_init(argc: i32) -> i32;
 33
 34        /// Runs the Qt event loop; returns the process exit code.
 2135        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.
 2144        fn qt_scheme_install_handler(webview_id: u64);
 45        /// Completes a request that `qt_scheme_request` reported as pending
 46        /// (`handled == 2`).
 2147        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`.
 126115pub fn qt_on_webview_event(id: u64, kind: i32, payload: &str) {
 126116    crate::qtrace!("[tauri-runtime-qt] webview {id} event {kind}: {payload}");
 126117}
 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.
 1323129pub fn qt_scheme_request(
 1323130    webview_id: u64,
 1323131    request_id: u64,
 1323132    scheme_kind: i32,
 1323133    method: &str,
 1323134    url: &str,
 1323135    headers: &str,
 1323136    body: &str,
 1323137) -> ffi::QtSchemeResponse {
 1323138    crate::qtrace!(
 139        "[tauri-runtime-qt] scheme_request webview={webview_id} kind={scheme_kind} {method} {url}"
 140    );
 141
 1323142    let scheme = match scheme_kind {
 1302143        0 => "ipc",
 21144        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.
 1323152    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    }
 1323164    let gate = std::sync::Arc::new(Mutex::new(Gate {
 1323165        returned: false,
 1323166        immediate: None,
 1323167    }));
 168    // The responder may outlive this call, so it must own its diagnostic text.
 1323169    let method_owned = method.to_owned();
 1323170    let url_owned = url.to_owned();
 171
 172    {
 1323173        let guard = crate::SCHEME_PROTOCOLS.lock();
 1323174        let Some(entry) = guard.iter().find(|(id, _, _)| *id == webview_id) else {
 0175            return unhandled();
 176        };
 1323177        let Some(handler) = entry.2.get(scheme) else {
 0178            return unhandled();
 179        };
 1323180        let label = entry.1.clone();
 181
 1323182        let gate = std::sync::Arc::clone(&gate);
 1323183        let method_owned = method_owned.clone();
 1323184        let url_owned = url_owned.clone();
 1323185        handler(
 1323186            &label,
 1323187            request,
 1323188            Box::new(
 1323189                move |response: http::Response<std::borrow::Cow<'static, [u8]>>| {
 1323190                    let (status, headers, body) = qtcore::scheme::encode_response(response);
 1323191                    crate::qtrace!(
 192                    "[tauri-runtime-qt] {scheme} {method_owned} {url_owned} -> {status} ({} body bytes)",
 1323193                    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.
 1323198                    let deferred = {
 1323199                        let mut gate = gate.lock();
 1323200                        if gate.returned {
 21201                            true
 202                        } else {
 1302203                            gate.immediate = Some(ffi::QtSchemeResponse {
 1302204                                handled: 1,
 1302205                                status,
 1302206                                headers: headers.clone(),
 1302207                                body: body.clone(),
 1302208                            });
 1302209                            false
 210                        }
 211                    };
 1323212                    if deferred {
 21213                        ffi::qt_scheme_respond(request_id, status, &headers, &body);
 1302214                    }
 1323215                },
 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.
 1323223    let inline = {
 1323224        let mut gate = gate.lock();
 1323225        gate.returned = true;
 1323226        gate.immediate.take()
 227    };
 1323228    if let Some(response) = inline {
 1302229        return response;
 21230    }
 231
 232    // Not answered yet: the C++ side keeps the job alive and waits for
 233    // `qt_scheme_respond`.
 21234    ffi::QtSchemeResponse {
 21235        handled: 2,
 21236        status: 0,
 21237        headers: String::new(),
 21238        body: String::new(),
 21239    }
 1323240}