From 0ba210fe26cf88050f70c3dccdd7062f387d6e7d Mon Sep 17 00:00:00 2001 From: Joshua Booth Date: Wed, 25 Jun 2025 13:03:42 -0700 Subject: [PATCH 1/3] cxx-qt-lib: implement From for QString --- crates/cxx-qt-lib/src/core/qstring.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/cxx-qt-lib/src/core/qstring.rs b/crates/cxx-qt-lib/src/core/qstring.rs index 69f5d0bd2..bd7ae903b 100644 --- a/crates/cxx-qt-lib/src/core/qstring.rs +++ b/crates/cxx-qt-lib/src/core/qstring.rs @@ -320,6 +320,15 @@ impl From for String { } } +impl From> for QString { + fn from(value: fmt::Arguments) -> Self { + match value.as_str() { + Some(s) => Self::from(s), + None => Self::from(&value.to_string()), + } + } +} + impl QString { /// Returns a copy of this string with the lowest numbered place marker replaced by string a, i.e., %1, %2, ..., %99. pub fn arg(&self, a: &QString) -> Self { From 2bdb76990c212b71f0ccbedc1e6b2b5210b175c0 Mon Sep 17 00:00:00 2001 From: Joshua Booth Date: Wed, 25 Jun 2025 18:46:30 -0700 Subject: [PATCH 2/3] cxx-qt-lib: use Qt's public logging API --- crates/cxx-qt-lib/include/core/qtlogging.h | 41 ++-- crates/cxx-qt-lib/src/core/mod.rs | 4 +- crates/cxx-qt-lib/src/core/qtlogging.cpp | 71 +++--- crates/cxx-qt-lib/src/core/qtlogging.rs | 271 ++++++++++++--------- crates/cxx-qt-lib/src/lib.rs | 2 - crates/cxx-qt-lib/src/util.rs | 18 -- 6 files changed, 211 insertions(+), 196 deletions(-) delete mode 100644 crates/cxx-qt-lib/src/util.rs diff --git a/crates/cxx-qt-lib/include/core/qtlogging.h b/crates/cxx-qt-lib/include/core/qtlogging.h index f0032861b..1d55e05ad 100644 --- a/crates/cxx-qt-lib/include/core/qtlogging.h +++ b/crates/cxx-qt-lib/include/core/qtlogging.h @@ -2,38 +2,31 @@ // SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company // clang-format on // SPDX-FileContributor: Joshua Goins +// SPDX-FileContributor: Joshua Booth // // SPDX-License-Identifier: MIT OR Apache-2.0 #pragma once -#include "rust/cxx.h" -#include -#include - -QMessageLogContext -construct_qmessagelogcontext(const char* fileName, - int lineNumber, - const char* functionName, - const char* categoryName); - -int -qmessagelogcontext_line(const QMessageLogContext& context); - -const char* -qmessagelogcontext_file(const QMessageLogContext& context); - -const char* -qmessagelogcontext_function(const QMessageLogContext& context); - -const char* -qmessagelogcontext_category(const QMessageLogContext& context); +#include // Define namespace otherwise we hit a GCC bug // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56480 namespace rust { +namespace cxxqtlib1 { +void +q_debug(const char* fileName, int lineNumber, const QString& message); + +void +q_info(const char* fileName, int lineNumber, const QString& message); + +void +q_warning(const char* fileName, int lineNumber, const QString& message); + +void +q_critical(const char* fileName, int lineNumber, const QString& message); -template<> -struct IsRelocatable : ::std::true_type -{}; +void +q_fatal(const char* fileName, int lineNumber, const QString& message); +} } // namespace rust diff --git a/crates/cxx-qt-lib/src/core/mod.rs b/crates/cxx-qt-lib/src/core/mod.rs index a6590c190..786be54eb 100644 --- a/crates/cxx-qt-lib/src/core/mod.rs +++ b/crates/cxx-qt-lib/src/core/mod.rs @@ -118,9 +118,7 @@ mod qvector; pub use qvector::{QVector, QVectorElement}; mod qtlogging; -pub use qtlogging::{ - q_format_log_message, q_set_message_pattern, qt_message_output, QMessageLogContext, QtMsgType, -}; +pub use qtlogging::{q_critical, q_debug, q_fatal, q_info, q_set_message_pattern, q_warning}; #[cxx::bridge] mod ffi { diff --git a/crates/cxx-qt-lib/src/core/qtlogging.cpp b/crates/cxx-qt-lib/src/core/qtlogging.cpp index cb298fa00..943305455 100644 --- a/crates/cxx-qt-lib/src/core/qtlogging.cpp +++ b/crates/cxx-qt-lib/src/core/qtlogging.cpp @@ -2,56 +2,57 @@ // SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company // clang-format on // SPDX-FileContributor: Joshua Goins +// SPDX-FileContributor: Joshua Booth // // SPDX-License-Identifier: MIT OR Apache-2.0 #include "cxx-qt-lib/qtlogging.h" -#include - -// QMessageLogContext has three "const char*" members for line, category, etc -// https://codebrowser.dev/qt5/qtbase/src/corelib/global/qlogging.h.html#QMessageLogContext -assert_alignment_and_size(QMessageLogContext, { - int version; - int line; - const char* file; - const char* function; - const char* category; -}); - -static_assert(!::std::is_trivially_copy_assignable::value); -static_assert( - !::std::is_trivially_copy_constructible::value); -static_assert(::std::is_trivially_destructible::value); - -QMessageLogContext -construct_qmessagelogcontext(const char* fileName, - int lineNumber, - const char* functionName, - const char* categoryName) +#include + +namespace rust { +namespace cxxqtlib1 { + +inline void +log(QtMsgType type, + const char* fileName, + int lineNumber, + const QString& message) +{ + qt_message_output( + type, + QMessageLogContext(fileName, lineNumber, nullptr, "default"), + message); +} + +void +q_debug(const char* fileName, int lineNumber, const QString& message) { - return QMessageLogContext(fileName, lineNumber, functionName, categoryName); + log(QtMsgType::QtDebugMsg, fileName, lineNumber, message); } -int -qmessagelogcontext_line(const QMessageLogContext& context) +void +q_info(const char* fileName, int lineNumber, const QString& message) { - return context.line; + log(QtMsgType::QtInfoMsg, fileName, lineNumber, message); } -const char* -qmessagelogcontext_file(const QMessageLogContext& context) +void +q_warning(const char* fileName, int lineNumber, const QString& message) { - return context.file; + log(QtMsgType::QtWarningMsg, fileName, lineNumber, message); } -const char* -qmessagelogcontext_function(const QMessageLogContext& context) +void +q_critical(const char* fileName, int lineNumber, const QString& message) { - return context.function; + log(QtMsgType::QtCriticalMsg, fileName, lineNumber, message); } -const char* -qmessagelogcontext_category(const QMessageLogContext& context) +void +q_fatal(const char* fileName, int lineNumber, const QString& message) { - return context.category; + log(QtMsgType::QtFatalMsg, fileName, lineNumber, message); +} + +} } diff --git a/crates/cxx-qt-lib/src/core/qtlogging.rs b/crates/cxx-qt-lib/src/core/qtlogging.rs index 0ca320e19..f50aabbd1 100644 --- a/crates/cxx-qt-lib/src/core/qtlogging.rs +++ b/crates/cxx-qt-lib/src/core/qtlogging.rs @@ -1,149 +1,192 @@ // SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company // SPDX-FileContributor: Joshua Goins +// SPDX-FileContributor: Joshua Booth // // SPDX-License-Identifier: MIT OR Apache-2.0 -use cxx::{type_id, ExternType}; -use std::ffi::c_char; -use std::ffi::CStr; -use std::marker::PhantomData; -use std::mem::size_of; + +use crate::QString; #[cxx::bridge] mod ffi { - /// The level the message is sent to the message handler at. - #[repr(i32)] - enum QtMsgType { - /// A debug message. - QtDebugMsg = 0, - /// An info message. - QtInfoMsg = 4, - /// A warning message. - QtWarningMsg = 1, - /// A fatal message. - QtFatalMsg = 3, - /// A critical message. - QtCriticalMsg = 2, - } - - unsafe extern "C++" { + extern "C++" { include!("cxx-qt-lib/qstring.h"); type QString = crate::QString; + } + unsafe extern "C++" { include!("cxx-qt-lib/qtlogging.h"); - type QMessageLogContext<'a> = crate::QMessageLogContext<'a>; - type QtMsgType; - - /// Outputs a message in the Qt message handler. - fn qt_message_output(msgType: QtMsgType, context: &QMessageLogContext, string: &QString); - - /// Generates a formatted string out of the type, context, str arguments. - #[cxx_name = "qFormatLogMessage"] - fn q_format_log_message( - msgType: QtMsgType, - context: &QMessageLogContext, - string: &QString, - ) -> QString; /// Changes the output of the default message handler. + /// Allows to tweak the output of [`q_debug!`](crate::q_debug!), [`q_info!`](crate::q_info!), [`q_warning!`](crate::q_warning!), [`q_critical!`](crate::q_critical!), and [`q_fatal!`](crate::q_fatal!). /// - /// # Safety - /// This function is marked as unsafe because it is not guaranteed to be thread-safe. - #[cxx_name = "qSetMessagePattern"] - unsafe fn q_set_message_pattern(pattern: &QString); - - #[cxx_name = "qmessagelogcontext_line"] - #[doc(hidden)] - fn line(context: &QMessageLogContext) -> i32; - - #[cxx_name = "qmessagelogcontext_file"] - #[doc(hidden)] - unsafe fn file(context: &QMessageLogContext) -> *const c_char; - - #[cxx_name = "qmessagelogcontext_function"] - #[doc(hidden)] - unsafe fn function(context: &QMessageLogContext) -> *const c_char; - - #[cxx_name = "qmessagelogcontext_category"] - #[doc(hidden)] - unsafe fn category(context: &QMessageLogContext) -> *const c_char; + /// See the [Qt documentation](https://doc.qt.io/qt/qtlogging.html#qSetMessagePattern) for pattern syntax. + #[rust_name = "q_set_message_pattern"] + fn qSetMessagePattern(pattern: &QString); + } #[namespace = "rust::cxxqtlib1"] unsafe extern "C++" { - include!("cxx-qt-lib/common.h"); - - #[doc(hidden)] - #[rust_name = "construct_qmessagelogcontext"] - unsafe fn construct<'a>( - file_name: *const c_char, - line_number: i32, - function_name: *const c_char, - category_name: *const c_char, - ) -> QMessageLogContext<'a>; + unsafe fn q_debug(file: *const c_char, line: i32, message: &QString); + unsafe fn q_info(file: *const c_char, line: i32, message: &QString); + unsafe fn q_warning(file: *const c_char, line: i32, message: &QString); + unsafe fn q_critical(file: *const c_char, line: i32, message: &QString); + unsafe fn q_fatal(file: *const c_char, line: i32, message: &QString); } } -/// The QMessageLogContext struct defines the context passed to the Qt message handler. -#[repr(C)] -#[derive(Clone, Copy)] -pub struct QMessageLogContext<'a> { - version: i32, - line: i32, - file: *const c_char, - function: *const c_char, - category: *const c_char, - _phantom: PhantomData<&'a c_char>, -} +use std::ffi::CStr; + +pub use ffi::q_set_message_pattern; -const_assert!( - size_of::() == (size_of::() * 2) + (size_of::<*const c_char>() * 3) -); - -impl<'a> QMessageLogContext<'a> { - pub fn new( - file: &'a CStr, - line: i32, - function: &'a CStr, - category: &'a CStr, - ) -> QMessageLogContext<'a> { - unsafe { - ffi::construct_qmessagelogcontext( - file.as_ptr(), - line, - function.as_ptr(), - category.as_ptr(), - ) - } +/// Backing function for the [`q_debug!`](crate::q_debug!) macro. See the macro's documentation for more details. +pub fn q_debug(file: &CStr, line: i32, message: &QString) { + // SAFETY: All strings are zero-terminated. + unsafe { + ffi::q_debug(file.as_ptr(), line, message); } +} - /// The line number given to the message handler. - pub fn line(&self) -> i32 { - ffi::line(self) +/// Backing function for the [`q_info!`](crate::q_info!) macro. See the macro's documentation for more details. +pub fn q_info(file: &CStr, line: i32, message: &QString) { + // SAFETY: All strings are zero-terminated. + unsafe { + ffi::q_info(file.as_ptr(), line, message); } +} - /// The file path given to the message handler. - pub fn file(&self) -> &'a CStr { - unsafe { CStr::from_ptr(ffi::file(self)) } +/// Backing function for the [`q_warning!`](crate::q_warning!) macro. See the macro's documentation for more details. +pub fn q_warning(file: &CStr, line: i32, message: &QString) { + // SAFETY: All strings are zero-terminated. + unsafe { + ffi::q_warning(file.as_ptr(), line, message); } +} - /// The name of the function given to the message handler. - pub fn function(&self) -> &'a CStr { - unsafe { CStr::from_ptr(ffi::function(self)) } +/// Backing function for the [`q_critical!`](crate::q_critical!) macro. See the macro's documentation for more details. +pub fn q_critical(file: &CStr, line: i32, message: &QString) { + // SAFETY: All strings are zero-terminated. + unsafe { + ffi::q_critical(file.as_ptr(), line, message); } +} - /// The category given to the message handler. - pub fn category(&self) -> &'a CStr { - unsafe { CStr::from_ptr(ffi::category(self)) } +/// Backing function for the [`q_fatal!`](crate::q_fatal!) macro. See the macro's documentation for more details. +pub fn q_fatal(file: &CStr, line: i32, message: &QString) { + // SAFETY: All strings are zero-terminated. + unsafe { + ffi::q_fatal(file.as_ptr(), line, message); } } -// Safety: -// -// Static checks on the C++ side ensure that QMessageLogContext is trivial. -unsafe impl ExternType for QMessageLogContext<'_> { - type Id = type_id!("QMessageLogContext"); - type Kind = cxx::kind::Trivial; +/// Calls the Qt message handler with a formatted debug message, using the first argument as the function name for the log context, the second argument as the format string for the log message, and the remaining arguments as arguments to format. If no message handler has been installed, the message is printed to stderr. Under Windows the message is sent to the console, if it is a console application; otherwise, it is sent to the debugger. On QNX, the message is sent to slogger2. This function does nothing if `QT_NO_DEBUG_OUTPUT` was defined during compilation. +/// +/// # Examples +/// +/// ```rust,ignore +/// use cxx_qt_lib::q_debug; +/// +/// fn somefunc(x: i32, y: i32) { +/// q_debug!("x: {x}, y: {y}"); +/// q_debug!("x: {}, y: {}", x, y); +/// } +/// ``` +#[macro_export] +macro_rules! q_debug { + ($($arg:tt)*) => ($crate::q_debug( + unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) }, + line!() as i32, + &$crate::QString::from(std::format_args!($($arg)*)) + )); +} + +/// Calls the Qt message handler with a formatted informational message, using the first argument as the function name for the log context, the second argument as the format string for the log message, and the remaining arguments as arguments to format. If no message handler has been installed, the message is printed to stderr. Under Windows the message is sent to the console, if it is a console application; otherwise, it is sent to the debugger. On QNX, the message is sent to slogger2. This function does nothing if `QT_NO_INFO_OUTPUT` was defined during compilation. +/// +/// # Examples +/// +/// ```rust,ignore +/// use cxx_qt_lib::q_info; +/// +/// fn somefunc(x: i32, y: i32) { +/// q_info!("x: {x}, y: {y}"); +/// q_info!("x: {}, y: {}", x, y); +/// } +/// ``` +#[macro_export] +macro_rules! q_info { + ($($arg:tt)*) => ($crate::q_info( + unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) }, + line!() as i32, + &$crate::QString::from(std::format_args!($($arg)*)) + )); +} + +/// Calls the Qt message handler with a formatted warning message, using the first argument as the function name for the log context, the second argument as the format string for the log message, and the remaining arguments as arguments to format. If no message handler has been installed, the message is printed to stderr. Under Windows the message is sent to the console, if it is a console application; otherwise, it is sent to the debugger. On QNX, the message is sent to slogger2. This function does nothing if `QT_NO_WARNING_OUTPUT` was defined during compilation. +/// +/// For debugging purposes, it is sometimes convenient to let the program abort for warning messages. This allows you then to inspect the core dump, or attach a debugger - see also [`q_fatal`]. To enable this, set the environment variable `QT_FATAL_WARNINGS` to a number `n`. The program terminates then for the `n`-th warning. That is, if the environment variable is set to 1, it will terminate on the first call; if it contains the value 10, it will exit on the 10th call. Any non-numeric value in the environment variable is equivalent to 1. +/// +/// # Examples +/// +/// ```rust,ignore +/// use cxx_qt_lib::q_warning; +/// +/// fn somefunc(x: i32, y: i32) { +/// q_warning!("x: {x}, y: {y}"); +/// q_warning!("x: {}, y: {}", x, y); +/// } +/// ``` +#[macro_export] +macro_rules! q_warning { + ($($arg:tt)*) => ($crate::q_warning( + unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) }, + line!() as i32, + &$crate::QString::from(std::format_args!($($arg)*)) + )); } -use crate::const_assert; -pub use ffi::{q_format_log_message, q_set_message_pattern, qt_message_output, QtMsgType}; +/// Calls the Qt message handler with a critical message, using the first argument as the function name for the log context, the second argument as the format string for the log message, and the remaining arguments as arguments to format. If no message handler has been installed, the message is printed to stderr. Under Windows the message is sent to the console, if it is a console application; otherwise, it is sent to the debugger. On QNX, the message is sent to slogger2. This function does nothing if `QT_NO_WARNING_OUTPUT` was defined during compilation. +/// +/// For debugging purposes, it is sometimes convenient to let the program abort for critical messages. This allows you then to inspect the core dump, or attach a debugger - see also [`q_fatal`]. To enable this, set the environment variable `QT_FATAL_CRITICALS` to a number `n`. The program terminates then for the `n`-th critical message. That is, if the environment variable is set to 1, it will terminate on the first call; if it contains the value 10, it will exit on the 10th call. Any non-numeric value in the environment variable is equivalent to 1. +/// +/// # Examples +/// +/// ```rust,ignore +/// use cxx_qt_lib::q_critical; +/// +/// fn somefunc(x: i32, y: i32) { +/// q_critical!("x: {x}, y: {y}"); +/// q_critical!("x: {}, y: {}", x, y); +/// } +/// ``` +#[macro_export] +macro_rules! q_critical { + ($($arg:tt)*) => ($crate::q_critical( + unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) }, + line!() as i32, + &$crate::QString::from(std::format_args!($($arg)*)) + )); +} + +/// +/// Calls the Qt message handler with a fatal message, using the first argument as the function name for the log context, the second argument as the format string for the log message, and the remaining arguments as arguments to format. If no message handler has been installed, the message is printed to stderr. Under Windows the message is sent to the console, if it is a console application; otherwise, it is sent to the debugger. On QNX, the message is sent to slogger2. +/// +/// If you are using the **default message handler** this function will abort to create a core dump. On Windows, for debug builds, this function will report a `_CRT_ERROR` enabling you to connect a debugger to the application. +/// +/// # Examples +/// +/// ```rust,ignore +/// use cxx_qt_lib::q_fatal; +/// +/// fn somefunc(x: i32, y: i32) { +/// q_fatal!("x: {x}, y: {y}"); +/// q_fatal!("x: {}, y: {}", x, y); +/// } +/// ``` +#[macro_export] +macro_rules! q_fatal { + ($($arg:tt)*) => ($crate::q_fatal( + unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) }, + line!() as i32, + &$crate::QString::from(std::format_args!($($arg)*)) + )); +} diff --git a/crates/cxx-qt-lib/src/lib.rs b/crates/cxx-qt-lib/src/lib.rs index 3ba11e1a0..4f2501f2c 100644 --- a/crates/cxx-qt-lib/src/lib.rs +++ b/crates/cxx-qt-lib/src/lib.rs @@ -30,5 +30,3 @@ pub use crate::qml::*; mod quickcontrols; #[cfg(feature = "qt_quickcontrols")] pub use crate::quickcontrols::*; - -mod util; diff --git a/crates/cxx-qt-lib/src/util.rs b/crates/cxx-qt-lib/src/util.rs deleted file mode 100644 index 7dd8ddecf..000000000 --- a/crates/cxx-qt-lib/src/util.rs +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company -// SPDX-FileContributor: Joshua Goins -// -// SPDX-License-Identifier: MIT OR Apache-2.0 - -/// Asserts that a boolean expression is true at compile time. -/// -/// See [`core::assert!`] for more information. -/// -/// ```compile_fail -/// const_assert!(5 == 4); -/// ``` -#[macro_export] -macro_rules! const_assert { - ($x:expr $(,)?) => { - const _: () = ::core::assert!($x); - }; -} From a3ddf73bafeff67ae335d5253409bcba4dc2f14b Mon Sep 17 00:00:00 2001 From: Joshua Booth Date: Wed, 25 Jun 2025 18:46:50 -0700 Subject: [PATCH 3/3] cxx-qt-lib: add standalone unit tests for logging --- tests/qt_types_standalone/CMakeLists.txt | 1 + tests/qt_types_standalone/cpp/main.cpp | 2 + tests/qt_types_standalone/cpp/qtlogging.h | 45 +++++++++++++++++++ tests/qt_types_standalone/rust/build.rs | 1 + tests/qt_types_standalone/rust/src/lib.rs | 1 + .../qt_types_standalone/rust/src/qtlogging.rs | 23 ++++++++++ 6 files changed, 73 insertions(+) create mode 100644 tests/qt_types_standalone/cpp/qtlogging.h create mode 100644 tests/qt_types_standalone/rust/src/qtlogging.rs diff --git a/tests/qt_types_standalone/CMakeLists.txt b/tests/qt_types_standalone/CMakeLists.txt index 2ccb78a40..2e16a2d64 100644 --- a/tests/qt_types_standalone/CMakeLists.txt +++ b/tests/qt_types_standalone/CMakeLists.txt @@ -104,6 +104,7 @@ add_executable(${APP_NAME} cpp/qstringlist.h cpp/qtime.h cpp/qtimezone.h + cpp/qtlogging.h cpp/qurl.h cpp/qvariant.h cpp/qvector.h diff --git a/tests/qt_types_standalone/cpp/main.cpp b/tests/qt_types_standalone/cpp/main.cpp index 692e98760..c7ab60bee 100644 --- a/tests/qt_types_standalone/cpp/main.cpp +++ b/tests/qt_types_standalone/cpp/main.cpp @@ -45,6 +45,7 @@ #include "qstringlist.h" #include "qtime.h" #include "qtimezone.h" +#include "qtlogging.h" #include "qurl.h" #include "qvariant.h" #include "qvector.h" @@ -97,6 +98,7 @@ main(int argc, char* argv[]) runTest(QScopedPointer(new QStringListTest)); runTest(QScopedPointer(new QTimeTest)); runTest(QScopedPointer(new QTimeZoneTest)); + runTest(QScopedPointer(new QtLoggingTest)); runTest(QScopedPointer(new QUrlTest)); runTest(QScopedPointer(new QVariantTest)); runTest(QScopedPointer(new QVectorTest)); diff --git a/tests/qt_types_standalone/cpp/qtlogging.h b/tests/qt_types_standalone/cpp/qtlogging.h new file mode 100644 index 000000000..35cea0f8e --- /dev/null +++ b/tests/qt_types_standalone/cpp/qtlogging.h @@ -0,0 +1,45 @@ +// clang-format off +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company +// clang-format on +// SPDX-FileContributor: Joshua Booth +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +#pragma once + +#include +#include + +#include "qt_types_standalone/src/qtlogging.cxx.h" + +static QtMessageHandler originalHandler = nullptr; +static QString loggedMessage{}; + +class QtLoggingTest : public QObject +{ + Q_OBJECT + +private: + static void logAndStore(QtMsgType type, + const QMessageLogContext& context, + const QString& msg) + { + if (type == QtMsgType::QtInfoMsg) { + loggedMessage = QStringLiteral("%1:%2 - %3") + .arg(QString::fromUtf8(context.file)) + .arg(context.line) + .arg(msg); + } + if (originalHandler) { + (*originalHandler)(type, context, msg); + } + } + +private Q_SLOTS: + void log() + { + originalHandler = qInstallMessageHandler(logAndStore); + const QString expectedMessage = log_info(QStringLiteral("test message")); + qInstallMessageHandler(originalHandler); + QCOMPARE(loggedMessage, expectedMessage); + } +}; diff --git a/tests/qt_types_standalone/rust/build.rs b/tests/qt_types_standalone/rust/build.rs index 51f6df496..88b1e0830 100644 --- a/tests/qt_types_standalone/rust/build.rs +++ b/tests/qt_types_standalone/rust/build.rs @@ -44,6 +44,7 @@ fn main() { .file("src/qstringlist.rs") .file("src/qtime.rs") .file("src/qtimezone.rs") + .file("src/qtlogging.rs") .file("src/qurl.rs") .file("src/qvariant.rs") .file("src/qvector.rs") diff --git a/tests/qt_types_standalone/rust/src/lib.rs b/tests/qt_types_standalone/rust/src/lib.rs index 40b8715fe..72477695b 100644 --- a/tests/qt_types_standalone/rust/src/lib.rs +++ b/tests/qt_types_standalone/rust/src/lib.rs @@ -41,6 +41,7 @@ mod qstring; mod qstringlist; mod qtime; mod qtimezone; +mod qtlogging; mod qurl; mod qvariant; mod qvector; diff --git a/tests/qt_types_standalone/rust/src/qtlogging.rs b/tests/qt_types_standalone/rust/src/qtlogging.rs new file mode 100644 index 000000000..636fc57ff --- /dev/null +++ b/tests/qt_types_standalone/rust/src/qtlogging.rs @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Joshua Booth +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use cxx_qt_lib::{q_info, QString}; + +#[cxx::bridge] +mod ffi { + extern "C++" { + include!("cxx-qt-lib/qstring.h"); + type QString = cxx_qt_lib::QString; + } + + extern "Rust" { + fn log_info(message: &QString) -> QString; + } +} + +fn log_info(message: &QString) -> QString { + q_info!("Message: {message}"); + QString::from(&format!("{}:{} - Message: {message}", file!(), line!() - 1)) +}