From f9950b7163c8fd9ac000621014f0a36f0df6b0cb Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Wed, 17 Jan 2024 14:07:40 -0500 Subject: [PATCH] Add bindings for QtMsgType, QMessageLogContext and qt_message_output This allows Rust and CXX-Qt applications to send messages to the Qt logger. --- CHANGELOG.md | 1 + .../include/core/qtlogging.h | 38 +++++ crates/cxx-qt-lib-headers/src/lib.rs | 1 + crates/cxx-qt-lib/build.rs | 2 + crates/cxx-qt-lib/src/core/mod.rs | 3 + crates/cxx-qt-lib/src/core/qtlogging.cpp | 48 ++++++ crates/cxx-qt-lib/src/core/qtlogging.rs | 139 ++++++++++++++++++ 7 files changed, 232 insertions(+) create mode 100644 crates/cxx-qt-lib-headers/include/core/qtlogging.h create mode 100644 crates/cxx-qt-lib/src/core/qtlogging.cpp create mode 100644 crates/cxx-qt-lib/src/core/qtlogging.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 442d6c589..05294c2e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `internal_pointer_mut()` function on `QModelIndex` - `c_void` in CXX-Qt-lib for easy access to `void *` - `CxxQtThread` is now marked as `Sync` so that it can be used by reference +- Support for `QMessageLogContext` and sending log messages to the Qt message handler. ### Changed diff --git a/crates/cxx-qt-lib-headers/include/core/qtlogging.h b/crates/cxx-qt-lib-headers/include/core/qtlogging.h new file mode 100644 index 000000000..1b483e22f --- /dev/null +++ b/crates/cxx-qt-lib-headers/include/core/qtlogging.h @@ -0,0 +1,38 @@ +// 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 +#include +#include "rust/cxx.h" + +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-headers/src/lib.rs b/crates/cxx-qt-lib-headers/src/lib.rs index abc7fb28b..a150f1fed 100644 --- a/crates/cxx-qt-lib-headers/src/lib.rs +++ b/crates/cxx-qt-lib-headers/src/lib.rs @@ -64,6 +64,7 @@ pub fn write_headers(directory: impl AsRef) { (include_str!("../include/core/qt.h"), "qt.h"), (include_str!("../include/core/qtime.h"), "qtime.h"), (include_str!("../include/core/qtimezone.h"), "qtimezone.h"), + (include_str!("../include/core/qtlogging.h"), "qtlogging.h"), (include_str!("../include/core/qurl.h"), "qurl.h"), (include_str!("../include/core/qvariant.h"), "qvariant.h"), (include_str!("../include/core/qvector.h"), "qvector.h"), diff --git a/crates/cxx-qt-lib/build.rs b/crates/cxx-qt-lib/build.rs index b71d383b2..46001b99c 100644 --- a/crates/cxx-qt-lib/build.rs +++ b/crates/cxx-qt-lib/build.rs @@ -98,6 +98,7 @@ fn main() { "core/qstringlist", "core/qt", "core/qtime", + "core/qtlogging", "core/qurl", "core/qvariant/mod", "core/qvariant/qvariant_bool", @@ -227,6 +228,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/src/core/mod.rs b/crates/cxx-qt-lib/src/core/mod.rs index cd2d2e315..b31d7dbf4 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::{QMessageLogContext, QtMsgType, qt_message_output}; + #[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..9b52e0a8d --- /dev/null +++ b/crates/cxx-qt-lib/src/core/qtlogging.cpp @@ -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 +#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_copyable::value, + "QMessageLogContext must be trivially copyable"); + +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; +} 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..95fc38bdc --- /dev/null +++ b/crates/cxx-qt-lib/src/core/qtlogging.rs @@ -0,0 +1,139 @@ +// 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 { + #[repr(i32)] + enum QtMsgType { + QtDebugMsg = 0, + QtInfoMsg = 4, + QtWarningMsg = 1, + QtFatalMsg = 3, + QtCriticalMsg = 2, + QtSystemMsg = 2 + } + + unsafe extern "C++" { + include!("cxx-qt-lib/common.h"); + + include!("cxx-qt-lib/qstring.h"); + type QString = crate::QString; + + include!("cxx-qt-lib/qtlogging.h"); + type QMessageLogContext<'a> = crate::QMessageLogContext<'a>; + type QtMsgType; + + fn qt_message_output(msgType: QtMsgType, context: &QMessageLogContext, string: &QString); + + #[cxx_name = "qmessagelogcontext_line"] + fn line(context: &QMessageLogContext) -> i32; + + #[cxx_name = "qmessagelogcontext_set_line"] + fn set_line(context: &mut QMessageLogContext, line: i32); + + #[cxx_name = "qmessagelogcontext_file"] + unsafe fn file(context: &QMessageLogContext) -> *const c_char; + + #[cxx_name = "qmessagelogcontext_set_file"] + unsafe fn set_file(context: &mut QMessageLogContext, file: *const c_char); + + #[cxx_name = "qmessagelogcontext_function"] + unsafe fn function(context: &QMessageLogContext) -> *const c_char; + + #[cxx_name = "qmessagelogcontext_set_function"] + unsafe fn set_function(context: &mut QMessageLogContext, function: *const c_char); + + #[cxx_name = "qmessagelogcontext_category"] + unsafe fn category(context: &QMessageLogContext) -> *const c_char; + + #[cxx_name = "qmessagelogcontext_set_category"] + unsafe fn set_category(context: &mut QMessageLogContext, category: *const c_char); + } + + #[namespace = "rust::cxxqtlib1"] + unsafe extern "C++" { + #[doc(hidden)] + #[rust_name = "qmessagelogcontext_default"] + fn construct() -> QMessageLogContext<'static>; + } +} + +#[repr(C)] +pub struct QMessageLogContext<'a> { + version: i32, + line: i32, + file: &'a *const c_char, + function: &'a *const c_char, + category: &'a *const c_char +} + +impl Default for QMessageLogContext<'_> { + fn default() -> Self { + ffi::qmessagelogcontext_default() + } +} + +impl<'a> QMessageLogContext<'a> { + pub fn line(&self) -> i32 { + ffi::line(&self) + } + + pub fn set_line(&mut self, line: i32) { + ffi::set_line(self, line); + } + + pub fn file(&self) -> &CStr { + unsafe { + CStr::from_ptr(ffi::file(self)) + } + } + + pub fn set_file(&mut self, file: &'a CStr) { + unsafe { + ffi::set_file(self, file.as_ptr()); + } + } + + pub fn function(&self) -> &CStr { + unsafe { + CStr::from_ptr(ffi::function(self)) + } + } + + pub fn set_function(&mut self, function: &'a CStr) { + unsafe { + ffi::set_function(self, function.as_ptr()); + } + } + + pub fn category(&self) -> &CStr { + unsafe { + CStr::from_ptr(ffi::category(self)) + } + } + + pub fn set_category(&mut self, category: &'a CStr) { + unsafe { + ffi::set_category(self, category.as_ptr()); + } + } +} + +// 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::{ + QtMsgType, qt_message_output +}; +