diff --git a/CHANGELOG.md b/CHANGELOG.md index 50d6ab6f7..83daeedcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add cxx-qt-lib-extras crate which contains: `QCommandLineOption`, `QCommandLineParser`, `QElapsedTimer`, `QApplication` - Serde support for `QString` (requires "serde" feature on cxx-qt-lib) - A new QuickControls module, which exposes `QQuickStyle`. This module is enabled by default and is behind the `qt_quickcontrols` feature. +- Support for `QMessageLogContext` and sending log messages to the Qt message handler. ### Changed diff --git a/crates/cxx-qt-lib/build.rs b/crates/cxx-qt-lib/build.rs index 93d3fbbb9..d080f7281 100644 --- a/crates/cxx-qt-lib/build.rs +++ b/crates/cxx-qt-lib/build.rs @@ -136,6 +136,7 @@ fn main() { "core/qstringlist", "core/qt", "core/qtime", + "core/qtlogging", "core/qurl", "core/qvariant/mod", "core/qvariant/qvariant_bool", @@ -254,6 +255,7 @@ fn main() { "core/qstring", "core/qstringlist", "core/qtime", + "core/qtlogging", "core/qurl", "core/qvariant/qvariant", "core/qvector/qvector", diff --git a/crates/cxx-qt-lib/include/core/qtlogging.h b/crates/cxx-qt-lib/include/core/qtlogging.h new file mode 100644 index 000000000..266df6da4 --- /dev/null +++ b/crates/cxx-qt-lib/include/core/qtlogging.h @@ -0,0 +1,48 @@ +// clang-format off +// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +// clang-format on +// SPDX-FileContributor: Joshua Goins +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +#pragma once + +#include "rust/cxx.h" +#include +#include + +int +qmessagelogcontext_line(const QMessageLogContext& context); + +void +qmessagelogcontext_set_line(QMessageLogContext& context, const int line); + +const char* +qmessagelogcontext_file(const QMessageLogContext& context); + +void +qmessagelogcontext_set_file(QMessageLogContext& context, const char* file); + +const char* +qmessagelogcontext_function(const QMessageLogContext& context); + +void +qmessagelogcontext_set_function(QMessageLogContext& context, + const char* function); + +const char* +qmessagelogcontext_category(const QMessageLogContext& context); + +void +qmessagelogcontext_set_category(QMessageLogContext& context, + const char* category); + +// Define namespace otherwise we hit a GCC bug +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56480 +namespace rust { + +template<> +struct IsRelocatable : ::std::true_type +{ +}; + +} // namespace rust \ No newline at end of file diff --git a/crates/cxx-qt-lib/src/core/mod.rs b/crates/cxx-qt-lib/src/core/mod.rs index 7f8d82a7e..432431ed2 100644 --- a/crates/cxx-qt-lib/src/core/mod.rs +++ b/crates/cxx-qt-lib/src/core/mod.rs @@ -98,6 +98,9 @@ pub use qvariant::{QVariant, QVariantValue}; mod qvector; pub use qvector::{QVector, QVectorElement}; +mod qtlogging; +pub use qtlogging::{qt_message_output, QMessageLogContext, QtMsgType}; + #[cxx::bridge] mod ffi { #[namespace = "rust::cxxqtlib1"] diff --git a/crates/cxx-qt-lib/src/core/qtlogging.cpp b/crates/cxx-qt-lib/src/core/qtlogging.cpp new file mode 100644 index 000000000..04edc6220 --- /dev/null +++ b/crates/cxx-qt-lib/src/core/qtlogging.cpp @@ -0,0 +1,71 @@ +// clang-format off +// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +// clang-format on +// SPDX-FileContributor: Joshua Goins +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +#include "cxx-qt-lib/qtlogging.h" + +#include "../assertion_utils.h" + +// 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, + alignof(intptr_t), + sizeof(intptr_t) * 4); + +static_assert(!::std::is_trivially_copy_assignable::value); +static_assert(!::std::is_trivially_copy_constructible::value); +static_assert(::std::is_trivially_destructible::value); + +static_assert(QTypeInfo::isRelocatable); + +int +qmessagelogcontext_line(const QMessageLogContext& context) +{ + return context.line; +} + +void +qmessagelogcontext_set_line(QMessageLogContext& context, const int line) +{ + context.line = line; +} + +const char* +qmessagelogcontext_file(const QMessageLogContext& context) +{ + return context.file; +} + +void +qmessagelogcontext_set_file(QMessageLogContext& context, const char* file) +{ + context.file = file; +} + +const char* +qmessagelogcontext_function(const QMessageLogContext& context) +{ + return context.function; +} + +void +qmessagelogcontext_set_function(QMessageLogContext& context, + const char* function) +{ + context.function = function; +} + +const char* +qmessagelogcontext_category(const QMessageLogContext& context) +{ + return context.category; +} + +void +qmessagelogcontext_set_category(QMessageLogContext& context, + const char* category) +{ + context.category = category; +} \ No newline at end of file diff --git a/crates/cxx-qt-lib/src/core/qtlogging.rs b/crates/cxx-qt-lib/src/core/qtlogging.rs new file mode 100644 index 000000000..53f696bc1 --- /dev/null +++ b/crates/cxx-qt-lib/src/core/qtlogging.rs @@ -0,0 +1,145 @@ +// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Joshua Goins +// +// 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; + +#[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++" { + include!("cxx-qt-lib/qstring.h"); + type QString = crate::QString; + + 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); + + #[cxx_name = "qmessagelogcontext_line"] + #[doc(hidden)] + fn line(context: &QMessageLogContext) -> i32; + + #[cxx_name = "qmessagelogcontext_set_line"] + #[doc(hidden)] + fn set_line(context: &mut QMessageLogContext, line: i32); + + #[cxx_name = "qmessagelogcontext_file"] + #[doc(hidden)] + unsafe fn file(context: &QMessageLogContext) -> *const c_char; + + #[cxx_name = "qmessagelogcontext_set_file"] + #[doc(hidden)] + unsafe fn set_file(context: &mut QMessageLogContext, file: *const c_char); + + #[cxx_name = "qmessagelogcontext_function"] + #[doc(hidden)] + unsafe fn function(context: &QMessageLogContext) -> *const c_char; + + #[cxx_name = "qmessagelogcontext_set_function"] + #[doc(hidden)] + unsafe fn set_function(context: &mut QMessageLogContext, function: *const c_char); + + #[cxx_name = "qmessagelogcontext_category"] + #[doc(hidden)] + unsafe fn category(context: &QMessageLogContext) -> *const c_char; + + #[cxx_name = "qmessagelogcontext_set_category"] + #[doc(hidden)] + unsafe fn set_category(context: &mut QMessageLogContext, category: *const c_char); + } + + #[namespace = "rust::cxxqtlib1"] + unsafe extern "C++" { + include!("cxx-qt-lib/common.h"); + + #[doc(hidden)] + #[rust_name = "qmessagelogcontext_default"] + fn construct() -> QMessageLogContext<'static>; + } +} + +/// The QMessageLogContext struct defines the context passed to the Qt message handler. +#[repr(C)] +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>, +} + +impl Default for QMessageLogContext<'_> { + fn default() -> Self { + ffi::qmessagelogcontext_default() + } +} + +impl<'a> QMessageLogContext<'a> { + pub fn new( + line: i32, + file: &'a *const c_char, + function: &'a *const c_char, + category: &'a *const c_char, + ) -> QMessageLogContext<'a> { + let mut context = QMessageLogContext::default(); + unsafe { + ffi::set_line(&mut context, line); + ffi::set_file(&mut context, *file); + ffi::set_function(&mut context, *function); + ffi::set_category(&mut context, *category); + } + + context + } + + /// The line number given to the message handler. + pub fn line(&self) -> i32 { + ffi::line(self) + } + + /// The file path given to the message handler. + pub fn file(&self) -> &CStr { + unsafe { CStr::from_ptr(ffi::file(self)) } + } + + /// The name of the function given to the message handler. + pub fn function(&self) -> &CStr { + unsafe { CStr::from_ptr(ffi::function(self)) } + } + + /// The category given to the message handler. + pub fn category(&self) -> &CStr { + unsafe { CStr::from_ptr(ffi::category(self)) } + } +} + +// 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; +} + +pub use ffi::{qt_message_output, QtMsgType};