Skip to content

Commit

Permalink
cxx-qt-lib: Add bindings for QMessageLogContext and qt_message_output
Browse files Browse the repository at this point in the history
This allows Rust and CXX-Qt applications to send messages to the Qt
logger.
  • Loading branch information
redstrate committed Jan 15, 2025
1 parent e84662a commit bbb4cde
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 0 deletions.
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"

#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;
}
129 changes: 129 additions & 0 deletions crates/cxx-qt-lib/src/core/qtlogging.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// 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 {
/// 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_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> {
version: i32,
line: i32,
file: *const c_char,
function: *const c_char,
category: *const c_char,
_phantom: PhantomData<&'a c_char>,
}

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::core::qtlogging::ffi::category;
pub use ffi::{qt_message_output, QtMsgType};

0 comments on commit bbb4cde

Please sign in to comment.