Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cxx-qt-lib: Add bindings for QMessageLogContext and qt_message_output #814

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- `QDateTime::from_string` to parse `QDateTime` from a `QString`.
- Support for further types: `QUuid`
- Support for `QMessageLogContext` and sending log messages to the Qt message handler.

### Fixed

Expand Down
2 changes: 2 additions & 0 deletions crates/cxx-qt-lib/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ fn main() {
"core/qstringlist",
"core/qt",
"core/qtime",
"core/qtlogging",
"core/qurl",
"core/quuid",
"core/qvariant/mod",
Expand Down Expand Up @@ -276,6 +277,7 @@ fn main() {
"core/qstring",
"core/qstringlist",
"core/qtime",
"core/qtlogging",
"core/qurl",
"core/quuid",
"core/qvariant/qvariant",
Expand Down
39 changes: 39 additions & 0 deletions crates/cxx-qt-lib/include/core/qtlogging.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// clang-format off
// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
// clang-format on
// SPDX-FileContributor: Joshua Goins <[email protected]>
//
// SPDX-License-Identifier: MIT OR Apache-2.0
#pragma once

#include "rust/cxx.h"
#include <QDebug>
#include <QtCore/qlogging.h>

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);

// Define namespace otherwise we hit a GCC bug
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56480
namespace rust {

template<>
struct IsRelocatable<QMessageLogContext> : ::std::true_type
{};

} // namespace rust
3 changes: 3 additions & 0 deletions crates/cxx-qt-lib/src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,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"]
Expand Down
57 changes: 57 additions & 0 deletions crates/cxx-qt-lib/src/core/qtlogging.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// clang-format off
// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
// clang-format on
// SPDX-FileContributor: Joshua Goins <[email protected]>
//
// SPDX-License-Identifier: MIT OR Apache-2.0
#include "cxx-qt-lib/qtlogging.h"
redstrate marked this conversation as resolved.
Show resolved Hide resolved

#include <cxx-qt-lib/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, {
int version;
int line;
const char* file;
const char* function;
const char* category;
});

static_assert(!::std::is_trivially_copy_assignable<QMessageLogContext>::value);
static_assert(
!::std::is_trivially_copy_constructible<QMessageLogContext>::value);
static_assert(::std::is_trivially_destructible<QMessageLogContext>::value);

QMessageLogContext
construct_qmessagelogcontext(const char* fileName,
int lineNumber,
const char* functionName,
const char* categoryName)
{
return QMessageLogContext(fileName, lineNumber, functionName, categoryName);
}

int
qmessagelogcontext_line(const QMessageLogContext& context)
{
return context.line;
}

const char*
qmessagelogcontext_file(const QMessageLogContext& context)
{
return context.file;
}

const char*
qmessagelogcontext_function(const QMessageLogContext& context)
{
return context.function;
}

const char*
qmessagelogcontext_category(const QMessageLogContext& context)
{
return context.category;
}
Comment on lines +35 to +57
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just occured to me that we technically don't need these...
As the Rust struct has the right memory layout with named members we can just do context.category, etc. in Rust.
We technically don't even need the constructor, as we can just create the struct entirely in Rust and it should still construct a compatible struct.

However, as we only assert the size and alignment and not the ordering of the members, I'm totally fine with keeping these functions just to be sure.

134 changes: 134 additions & 0 deletions crates/cxx-qt-lib/src/core/qtlogging.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
// SPDX-FileContributor: Joshua Goins <[email protected]>
//
// 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 {
redstrate marked this conversation as resolved.
Show resolved Hide resolved
/// 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,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question while waiting: do we rename enum values to something more sensible? in this case the names are weird to avoid collisions, since in qtlogging.h these were just an enum. hence why there' prefixed with Qt and suffixed with Msg.

}

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);
redstrate marked this conversation as resolved.
Show resolved Hide resolved

#[cxx_name = "qmessagelogcontext_line"]
redstrate marked this conversation as resolved.
Show resolved Hide resolved
#[doc(hidden)]
fn line(context: &QMessageLogContext) -> i32;
redstrate marked this conversation as resolved.
Show resolved Hide resolved

#[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;
}

#[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>;
}
}

/// The QMessageLogContext struct defines the context passed to the Qt message handler.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct QMessageLogContext<'a> {
redstrate marked this conversation as resolved.
Show resolved Hide resolved
version: i32,
line: i32,
file: *const c_char,
function: *const c_char,
category: *const c_char,
_phantom: PhantomData<&'a c_char>,
}

const_assert!(
size_of::<QMessageLogContext>() == (size_of::<i32>() * 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(),
)
}
}

/// 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) -> &'a CStr {
unsafe { CStr::from_ptr(ffi::file(self)) }
}

/// The name of the function given to the message handler.
pub fn function(&self) -> &'a CStr {
unsafe { CStr::from_ptr(ffi::function(self)) }
}

/// The category given to the message handler.
pub fn category(&self) -> &'a 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;
}

use crate::const_assert;
use crate::core::qtlogging::ffi::category;
pub use ffi::{qt_message_output, QtMsgType};
2 changes: 2 additions & 0 deletions crates/cxx-qt-lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ pub use crate::qml::*;
mod quickcontrols;
#[cfg(feature = "qt_quickcontrols")]
pub use crate::quickcontrols::*;

mod util;
18 changes: 18 additions & 0 deletions crates/cxx-qt-lib/src/util.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
// SPDX-FileContributor: Joshua Goins <[email protected]>
//
// 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);
};
}