From ccd606df6034c2d318c459b53ae348a041311735 Mon Sep 17 00:00:00 2001 From: Kai Blaschke Date: Thu, 4 Dec 2025 15:58:00 +0100 Subject: [PATCH 1/7] Update copyright in API headers --- src/api/include/projectM-4/audio.h | 2 +- src/api/include/projectM-4/callbacks.h | 2 +- src/api/include/projectM-4/core.h | 2 +- src/api/include/projectM-4/debug.h | 2 +- src/api/include/projectM-4/memory.h | 2 +- src/api/include/projectM-4/parameters.h | 2 +- src/api/include/projectM-4/projectM.h | 3 ++- src/api/include/projectM-4/render_opengl.h | 2 +- src/api/include/projectM-4/touch.h | 2 +- src/api/include/projectM-4/types.h | 2 +- src/api/include/projectM-4/user_sprites.h | 2 +- src/playlist/api/projectM-4/playlist.h | 2 +- src/playlist/api/projectM-4/playlist_callbacks.h | 2 +- src/playlist/api/projectM-4/playlist_core.h | 2 +- src/playlist/api/projectM-4/playlist_filter.h | 2 +- src/playlist/api/projectM-4/playlist_items.h | 2 +- src/playlist/api/projectM-4/playlist_memory.h | 2 +- src/playlist/api/projectM-4/playlist_playback.h | 2 +- src/playlist/api/projectM-4/playlist_types.h | 2 +- 19 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/api/include/projectM-4/audio.h b/src/api/include/projectM-4/audio.h index 81bbec40f8..20f6b50917 100644 --- a/src/api/include/projectM-4/audio.h +++ b/src/api/include/projectM-4/audio.h @@ -1,6 +1,6 @@ /** * @file audio.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Functions to pass in audio data to libprojectM. * @since 4.0.0 * diff --git a/src/api/include/projectM-4/callbacks.h b/src/api/include/projectM-4/callbacks.h index e652855b29..28934cf42b 100644 --- a/src/api/include/projectM-4/callbacks.h +++ b/src/api/include/projectM-4/callbacks.h @@ -1,6 +1,6 @@ /** * @file callbacks.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Functions and prototypes for projectM callbacks. * @since 4.0.0 * diff --git a/src/api/include/projectM-4/core.h b/src/api/include/projectM-4/core.h index 53c03b45e4..b54e698682 100644 --- a/src/api/include/projectM-4/core.h +++ b/src/api/include/projectM-4/core.h @@ -1,6 +1,6 @@ /** * @file core.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Core functions to instantiate, destroy and control projectM. * @since 4.0.0 * diff --git a/src/api/include/projectM-4/debug.h b/src/api/include/projectM-4/debug.h index 77b6231aef..32ff875f6e 100644 --- a/src/api/include/projectM-4/debug.h +++ b/src/api/include/projectM-4/debug.h @@ -1,6 +1,6 @@ /** * @file debug.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Debug functions for both libprojectM and preset developers. * @since 4.0.0 * diff --git a/src/api/include/projectM-4/memory.h b/src/api/include/projectM-4/memory.h index 2e2074eb1a..ba2d8379b6 100644 --- a/src/api/include/projectM-4/memory.h +++ b/src/api/include/projectM-4/memory.h @@ -1,6 +1,6 @@ /** * @file memory.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Memory allocation/deallocation helpers. * @since 4.0.0 * diff --git a/src/api/include/projectM-4/parameters.h b/src/api/include/projectM-4/parameters.h index 5bd7d901dd..3885a3d754 100644 --- a/src/api/include/projectM-4/parameters.h +++ b/src/api/include/projectM-4/parameters.h @@ -1,6 +1,6 @@ /** * @file parameters.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Functions to set and retrieve all sorts of projectM parameters and setting. * @since 4.0.0 * diff --git a/src/api/include/projectM-4/projectM.h b/src/api/include/projectM-4/projectM.h index 2e93d7b5c1..5b646af4d9 100644 --- a/src/api/include/projectM-4/projectM.h +++ b/src/api/include/projectM-4/projectM.h @@ -1,6 +1,6 @@ /** * @file projectM.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Convenience include file that includes all other API headers. * @since 4.0.0 * @@ -29,6 +29,7 @@ #include "projectM-4/callbacks.h" #include "projectM-4/core.h" #include "projectM-4/debug.h" +#include "projectM-4/logging.h" #include "projectM-4/memory.h" #include "projectM-4/parameters.h" #include "projectM-4/render_opengl.h" diff --git a/src/api/include/projectM-4/render_opengl.h b/src/api/include/projectM-4/render_opengl.h index 74027105b0..1191e1cbe9 100644 --- a/src/api/include/projectM-4/render_opengl.h +++ b/src/api/include/projectM-4/render_opengl.h @@ -1,6 +1,6 @@ /** * @file render_opengl.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Functions to configure and render projectM visuals using OpenGL. * * projectM -- Milkdrop-esque visualisation SDK diff --git a/src/api/include/projectM-4/touch.h b/src/api/include/projectM-4/touch.h index b73c5131e6..c25ca6d937 100644 --- a/src/api/include/projectM-4/touch.h +++ b/src/api/include/projectM-4/touch.h @@ -1,6 +1,6 @@ /** * @file touch.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Touch-related functions to add random waveforms. * @since 4.0.0 * diff --git a/src/api/include/projectM-4/types.h b/src/api/include/projectM-4/types.h index 56b3730645..d890ddc1c2 100644 --- a/src/api/include/projectM-4/types.h +++ b/src/api/include/projectM-4/types.h @@ -1,6 +1,6 @@ /** * @file types.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Types and enumerations used in the other API headers. * @since 4.0.0 * diff --git a/src/api/include/projectM-4/user_sprites.h b/src/api/include/projectM-4/user_sprites.h index e777f5916a..1f76646f65 100644 --- a/src/api/include/projectM-4/user_sprites.h +++ b/src/api/include/projectM-4/user_sprites.h @@ -1,6 +1,6 @@ /** * @file user_sprites.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Types and enumerations used in the other API headers. * @since 4.2.0 * diff --git a/src/playlist/api/projectM-4/playlist.h b/src/playlist/api/projectM-4/playlist.h index 26fb5c3931..f917b98972 100644 --- a/src/playlist/api/projectM-4/playlist.h +++ b/src/playlist/api/projectM-4/playlist.h @@ -1,6 +1,6 @@ /** * @file playlist.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Optional playlist API for libprojectM. * @since 4.0.0 * diff --git a/src/playlist/api/projectM-4/playlist_callbacks.h b/src/playlist/api/projectM-4/playlist_callbacks.h index a81ea9a223..7735660b02 100644 --- a/src/playlist/api/projectM-4/playlist_callbacks.h +++ b/src/playlist/api/projectM-4/playlist_callbacks.h @@ -1,6 +1,6 @@ /** * @file playlist_callbacks.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Functions and prototypes for projectM playlist callbacks. * @since 4.0.0 * diff --git a/src/playlist/api/projectM-4/playlist_core.h b/src/playlist/api/projectM-4/playlist_core.h index 5fb69ddffd..dcf99f898c 100644 --- a/src/playlist/api/projectM-4/playlist_core.h +++ b/src/playlist/api/projectM-4/playlist_core.h @@ -1,6 +1,6 @@ /** * @file playlist_core.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Core functions to instantiate, destroy and connect a projectM playlist. * @since 4.0.0 * diff --git a/src/playlist/api/projectM-4/playlist_filter.h b/src/playlist/api/projectM-4/playlist_filter.h index 7f775405d9..ec850fcd3a 100644 --- a/src/playlist/api/projectM-4/playlist_filter.h +++ b/src/playlist/api/projectM-4/playlist_filter.h @@ -1,6 +1,6 @@ /** * @file playlist_filter.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Playlist filter functions. * @since 4.0.0 * diff --git a/src/playlist/api/projectM-4/playlist_items.h b/src/playlist/api/projectM-4/playlist_items.h index 8f3558a027..42314426d8 100644 --- a/src/playlist/api/projectM-4/playlist_items.h +++ b/src/playlist/api/projectM-4/playlist_items.h @@ -1,6 +1,6 @@ /** * @file playlist_items.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Playlist item management functions. * @since 4.0.0 * diff --git a/src/playlist/api/projectM-4/playlist_memory.h b/src/playlist/api/projectM-4/playlist_memory.h index 40e9d5ed07..eae772168e 100644 --- a/src/playlist/api/projectM-4/playlist_memory.h +++ b/src/playlist/api/projectM-4/playlist_memory.h @@ -1,6 +1,6 @@ /** * @file playlist_memory.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Memory allocation/deallocation helpers. * @since 4.0.0 * diff --git a/src/playlist/api/projectM-4/playlist_playback.h b/src/playlist/api/projectM-4/playlist_playback.h index f3b0861428..bb376a29ab 100644 --- a/src/playlist/api/projectM-4/playlist_playback.h +++ b/src/playlist/api/projectM-4/playlist_playback.h @@ -1,6 +1,6 @@ /** * @file playlist_playback.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Playback control functions. * @since 4.0.0 * diff --git a/src/playlist/api/projectM-4/playlist_types.h b/src/playlist/api/projectM-4/playlist_types.h index e2ba425f74..589838247a 100644 --- a/src/playlist/api/projectM-4/playlist_types.h +++ b/src/playlist/api/projectM-4/playlist_types.h @@ -1,6 +1,6 @@ /** * @file playlist_types.h - * @copyright 2003-2024 projectM Team + * @copyright 2003-2025 projectM Team * @brief Types and enumerations used in the playlist API headers. * @since 4.0.0 * From 8e228952ac1535897f768e68bfc5f32a35b9eb4c Mon Sep 17 00:00:00 2001 From: Kai Blaschke Date: Fri, 5 Dec 2025 09:08:03 +0100 Subject: [PATCH 2/7] Use PUBLIC_HEADER CMake target property to install API headers --- src/api/CMakeLists.txt | 44 ++++++++++++++++--------------------- src/playlist/CMakeLists.txt | 37 ++++++++++++++----------------- 2 files changed, 35 insertions(+), 46 deletions(-) diff --git a/src/api/CMakeLists.txt b/src/api/CMakeLists.txt index 1674d9aa68..84f14c597e 100644 --- a/src/api/CMakeLists.txt +++ b/src/api/CMakeLists.txt @@ -1,8 +1,19 @@ add_library(projectM_api INTERFACE) -target_sources(projectM_api - PRIVATE +configure_file(version.h.in "${CMAKE_CURRENT_BINARY_DIR}/include/projectM-4/version.h" @ONLY) + +include(GenerateExportHeader) + +set(PROJECTM_EXPORT_HEADER "${CMAKE_CURRENT_BINARY_DIR}/include/projectM-4/projectM_export.h") + +generate_export_header(projectM_api + BASE_NAME projectM + EXPORT_FILE_NAME "${PROJECTM_EXPORT_HEADER}" + ) + +set(PROJECTM_PUBLIC_HEADERS "${PROJECTM_EXPORT_HEADER}" + "${CMAKE_CURRENT_BINARY_DIR}/include/projectM-4/version.h" include/projectM-4/audio.h include/projectM-4/callbacks.h include/projectM-4/core.h @@ -15,9 +26,15 @@ target_sources(projectM_api include/projectM-4/user_sprites.h ) +target_sources(projectM_api + PRIVATE + ${PROJECTM_PUBLIC_HEADERS} + ) + set_target_properties(projectM_api PROPERTIES EXPORT_NAME API FOLDER libprojectM + PUBLIC_HEADER "${PROJECTM_PUBLIC_HEADERS}" ) target_include_directories(projectM_api @@ -27,17 +44,6 @@ target_include_directories(projectM_api "$" ) -configure_file(version.h.in "${CMAKE_CURRENT_BINARY_DIR}/include/projectM-4/version.h" @ONLY) - -include(GenerateExportHeader) - -set(PROJECTM_EXPORT_HEADER "${CMAKE_CURRENT_BINARY_DIR}/include/projectM-4/projectM_export.h") - -generate_export_header(projectM_api - BASE_NAME projectM - EXPORT_FILE_NAME "${PROJECTM_EXPORT_HEADER}" - ) - add_library(libprojectM::API ALIAS projectM_api) @@ -51,16 +57,4 @@ if(ENABLE_INSTALL) PUBLIC_HEADER DESTINATION "${PROJECTM_INCLUDE_DIR}/projectM-4" COMPONENT Devel ) - install(FILES - "${CMAKE_CURRENT_BINARY_DIR}/include/projectM-4/projectM_export.h" - "${CMAKE_CURRENT_BINARY_DIR}/include/projectM-4/version.h" - DESTINATION "${PROJECTM_INCLUDE_DIR}/projectM-4" - COMPONENT Devel - ) - - install(DIRECTORY include/projectM-4 - DESTINATION "${PROJECTM_INCLUDE_DIR}" - COMPONENT Devel - ) - endif() \ No newline at end of file diff --git a/src/playlist/CMakeLists.txt b/src/playlist/CMakeLists.txt index 05ff116b9d..bcd50540a3 100644 --- a/src/playlist/CMakeLists.txt +++ b/src/playlist/CMakeLists.txt @@ -2,7 +2,22 @@ if(NOT ENABLE_PLAYLIST) return() endif() +set(PROJECTM_PLAYLIST_EXPORT_HEADER "${CMAKE_CURRENT_BINARY_DIR}/include/projectM-4/projectM_playlist_export.h") + +set(PROJECTM_PLAYLIST_PUBLIC_HEADERS + "${PROJECTM_PLAYLIST_EXPORT_HEADER}" + api/projectM-4/playlist.h + api/projectM-4/playlist_callbacks.h + api/projectM-4/playlist_core.h + api/projectM-4/playlist_filter.h + api/projectM-4/playlist_items.h + api/projectM-4/playlist_memory.h + api/projectM-4/playlist_playback.h + api/projectM-4/playlist_types.h + ) + add_library(projectM_playlist_main OBJECT + ${PROJECTM_PLAYLIST_PUBLIC_HEADERS} Filter.cpp Filter.hpp Item.cpp @@ -11,14 +26,6 @@ add_library(projectM_playlist_main OBJECT Playlist.hpp PlaylistCWrapper.cpp PlaylistCWrapper.hpp - api/projectM-4/playlist.h - api/projectM-4/playlist_callbacks.h - api/projectM-4/playlist_core.h - api/projectM-4/playlist_filter.h - api/projectM-4/playlist_items.h - api/projectM-4/playlist_memory.h - api/projectM-4/playlist_playback.h - api/projectM-4/playlist_types.h ) target_include_directories(projectM_playlist_main @@ -45,6 +52,7 @@ set_target_properties(projectM_playlist PROPERTIES EXPORT_NAME playlist FOLDER libprojectM OUTPUT_NAME ${PROJECTM_LIBRARY_BASE_OUTPUT_NAME}-playlist + PUBLIC_HEADER "${PROJECTM_PLAYLIST_PUBLIC_HEADERS}" ) target_include_directories(projectM_playlist @@ -88,8 +96,6 @@ endif() include(GenerateExportHeader) -set(PROJECTM_PLAYLIST_EXPORT_HEADER "${CMAKE_CURRENT_BINARY_DIR}/include/projectM-4/projectM_playlist_export.h") - generate_export_header(projectM_playlist BASE_NAME projectM_playlist EXPORT_FILE_NAME "${PROJECTM_PLAYLIST_EXPORT_HEADER}" @@ -107,17 +113,6 @@ if(ENABLE_INSTALL) PUBLIC_HEADER DESTINATION "${PROJECTM_INCLUDE_DIR}/projectM-4" COMPONENT Devel ) - install(FILES - "${CMAKE_CURRENT_BINARY_DIR}/include/projectM-4/projectM_playlist_export.h" - DESTINATION "${PROJECTM_INCLUDE_DIR}/projectM-4" - COMPONENT Devel - ) - - install(DIRECTORY api/projectM-4 - DESTINATION "${PROJECTM_INCLUDE_DIR}" - COMPONENT Devel - ) - # For use from an installed package (system install, vcpkg, homebrew etc.) include(CMakePackageConfigHelpers) From c23f1b9613c5936152af78deecea5fb047a1148a Mon Sep 17 00:00:00 2001 From: Kai Blaschke Date: Wed, 3 Dec 2025 20:10:42 +0100 Subject: [PATCH 3/7] Add a new logging class --- src/libprojectM/CMakeLists.txt | 2 + src/libprojectM/Logging.cpp | 77 ++++++++ src/libprojectM/Logging.hpp | 148 ++++++++++++++++ tests/libprojectM/CMakeLists.txt | 3 +- tests/libprojectM/LoggingTest.cpp | 286 ++++++++++++++++++++++++++++++ 5 files changed, 515 insertions(+), 1 deletion(-) create mode 100644 src/libprojectM/Logging.cpp create mode 100644 src/libprojectM/Logging.hpp create mode 100644 tests/libprojectM/LoggingTest.cpp diff --git a/src/libprojectM/CMakeLists.txt b/src/libprojectM/CMakeLists.txt index 4dc2f196c6..8acfb82cff 100644 --- a/src/libprojectM/CMakeLists.txt +++ b/src/libprojectM/CMakeLists.txt @@ -13,6 +13,8 @@ add_subdirectory(UserSprites) add_library(projectM_main OBJECT "${PROJECTM_EXPORT_HEADER}" + Logging.cpp + Logging.hpp Preset.hpp PresetFactory.cpp PresetFactory.hpp diff --git a/src/libprojectM/Logging.cpp b/src/libprojectM/Logging.cpp new file mode 100644 index 0000000000..e283eaf524 --- /dev/null +++ b/src/libprojectM/Logging.cpp @@ -0,0 +1,77 @@ +#include "Logging.hpp" + +#include + +namespace libprojectM { + +Logging::UserCallback Logging::m_globalCallback = {}; +thread_local Logging::UserCallback Logging::m_threadCallback = {}; + +Logging::LogLevel Logging::m_globalLogLevel = LogLevel::NotSet; +thread_local Logging::LogLevel Logging::m_threadLogLevel = LogLevel::NotSet; + +const Logging::LogLevel Logging::m_defaultLogLevel = LogLevel::Information; + +void Logging::SetGlobalCallback(const UserCallback callback) +{ + m_globalCallback = callback; +} + +void Logging::SetThreadCallback(const UserCallback callback) +{ + m_threadCallback = callback; +} + +void Logging::SetGlobalLogLevel(const LogLevel logLevel) +{ + m_globalLogLevel = logLevel; +} + +void Logging::SetThreadLogLevel(const LogLevel logLevel) +{ + m_threadLogLevel = logLevel; +} + +auto Logging::GetLogLevel() -> LogLevel +{ + if (m_threadLogLevel != LogLevel::NotSet) + { + return m_threadLogLevel; + } + + if (m_globalLogLevel != LogLevel::NotSet) + { + return m_globalLogLevel; + } + + return m_defaultLogLevel; +} + +auto Logging::HasCallback() -> bool +{ + return GetLoggingCallback().callbackFunction != nullptr; +} + +void Logging::Log(const std::string& message, LogLevel severity) +{ + auto callback = GetLoggingCallback(); + + if (callback.callbackFunction == nullptr) + { + return; + } + + callback.callbackFunction(message.c_str(), static_cast(severity), callback.userData); +} + +auto Logging::GetLoggingCallback() -> UserCallback +{ + if (m_threadCallback.callbackFunction != nullptr) + { + return m_threadCallback; + } + + return m_globalCallback; +} + +} // namespace libprojectM diff --git a/src/libprojectM/Logging.hpp b/src/libprojectM/Logging.hpp new file mode 100644 index 0000000000..6cec3a25a9 --- /dev/null +++ b/src/libprojectM/Logging.hpp @@ -0,0 +1,148 @@ +#pragma once + +#include +#include + +namespace libprojectM { + +/** + * @class Logging + * @brief A simple logger implementation to forward messages to the outside app. + * + * This class wraps logging functionality which can be used at either a global or thread-local + * level, for both callbacks and log levels. + */ +class Logging +{ +public: + /** + * The configurable log levels. If not set, "Information" is used by default. + */ + enum class LogLevel : uint8_t + { + NotSet, //!< Log level not set + Trace, //!< Most verbose logging, used to trace individual function calls or values. + Debug, //!< Debug-related messages for relevant data and developer information. + Information, //!< Not-too-frequent messages possibly relevant for users and developers. + Warning, //!< Something is wrong, but doesn't affect execution in a major way. + Error, //!< A recoverable error occurred which negatively affects projectM, e.g. shader compilation issues. + Fatal //!< An unrecoverable error occurred and the projectM instance cannot continue execution. + }; + + /** + * The application callback function. + */ + using CallbackFunction = void (*)(const char* message, int severity, void* userData); + + /** + * A struct holding the user callback function and data pointer. + */ + struct UserCallback { + CallbackFunction callbackFunction{}; //!< Function pointer of the user callback. + void* userData{}; //!< User data pointer for the callback. + }; + + Logging() = delete; + + /** + * Sets the global callback function pointer used across all threads. + * @param callback A UserCallback struct with the new function and user data pointers. + */ + static void SetGlobalCallback(UserCallback callback); + + /** + * Sets the thread-specific callback function pointer only used in the thread which registered it. + * @param callback A UserCallback struct with the new function and user data pointers. + */ + static void SetThreadCallback(UserCallback callback); + + /** + * Sets the global log level used across all threads. + * @param logLevel The log level to use. If set to LogLevel::NotSet, the value of m_defaultLogLevel is used. + */ + static void SetGlobalLogLevel(LogLevel logLevel); + + /** + * Sets the thread-specific log level only used in the thread which set it. + * @param logLevel The log level to use. If set to LogLevel::NotSet, the value of m_defaultLogLevel is used. + */ + static void SetThreadLogLevel(LogLevel logLevel); + + /** + * Returns the effective log level for the current thread. + * @return The log level set for this thread, or, if LogLevel::NotSet, the global log level. + * If no global log level is set, it returns the value of m_defaultLogLevel. + */ + static auto GetLogLevel() -> LogLevel; + + /** + * Returns whether a callback is registered or not. + * @return true if a callback is registered for the current thread or globally, false if none is registered. + */ + static auto HasCallback() -> bool; + + /** + * @brief Passes a log message with the given severity to the active thread or global callback. + * If no callbacks are registered, this function does nothing. + * @param message + * @param severity + */ + static void Log(const std::string& message, LogLevel severity); + + /** + * The default log level used if no log level is set (LogLevel::Information) + */ + static const LogLevel m_defaultLogLevel; + +private: + /** + * @brief Returns the active callback for this thread. + * If the thread has a local callback, this is returned, otherwise the global callback. + * @return A pointer to the active callback function, or nullptr if none is registered. + */ + static auto GetLoggingCallback() -> UserCallback; + + static UserCallback m_globalCallback; //!< The global callback function. + thread_local static UserCallback m_threadCallback; //!< The thread-specific callback function. + + static LogLevel m_globalLogLevel; //!< The global log level. + thread_local static LogLevel m_threadLogLevel; //!< The thread-specific log level. +}; + +#define LOG_TRACE(message) \ + if (Logging::HasCallback() && Logging::GetLogLevel() == Logging::LogLevel::Trace) \ + { \ + Logging::Log(message, Logging::LogLevel::Trace); \ + } + +#define LOG_DEBUG(message) \ + if (Logging::HasCallback() && Logging::GetLogLevel() <= Logging::LogLevel::Debug) \ + { \ + Logging::Log(message, Logging::LogLevel::Debug); \ + } + +#define LOG_INFO(message) \ + if (Logging::HasCallback() && Logging::GetLogLevel() <= Logging::LogLevel::Information) \ + { \ + Logging::Log(message, Logging::LogLevel::Information); \ + } + +#define LOG_WARN(message) \ + if (Logging::HasCallback() && Logging::GetLogLevel() <= Logging::LogLevel::Warning) \ + { \ + Logging::Log(message, Logging::LogLevel::Warning); \ + } + +#define LOG_ERROR(message) \ + if (Logging::HasCallback() && Logging::GetLogLevel() <= Logging::LogLevel::Error) \ + { \ + Logging::Log(message, Logging::LogLevel::Error); \ + } + +#define LOG_FATAL(message) \ + if (Logging::HasCallback() && Logging::GetLogLevel() <= Logging::LogLevel::Fatal) \ + { \ + Logging::Log(message, Logging::LogLevel::Fatal); \ + } + +} // namespace libprojectM diff --git a/tests/libprojectM/CMakeLists.txt b/tests/libprojectM/CMakeLists.txt index 23475b3f5e..016a15a9d8 100644 --- a/tests/libprojectM/CMakeLists.txt +++ b/tests/libprojectM/CMakeLists.txt @@ -27,8 +27,9 @@ test_api_headers(TestMainAPIHeaders ) add_executable(projectM-unittest - WaveformAlignerTest.cpp + LoggingTest.cpp PresetFileParserTest.cpp + WaveformAlignerTest.cpp $ $ diff --git a/tests/libprojectM/LoggingTest.cpp b/tests/libprojectM/LoggingTest.cpp new file mode 100644 index 0000000000..e4cb779d05 --- /dev/null +++ b/tests/libprojectM/LoggingTest.cpp @@ -0,0 +1,286 @@ +#include +#include + +#include + +#include +#include + +using libprojectM::Logging; + +class LoggingTest : public ::testing::Test +{ +protected: + void SetUp() override + { + Logging::SetThreadCallback({}); + Logging::SetGlobalCallback({}); + Logging::SetThreadLogLevel(Logging::LogLevel::NotSet); + Logging::SetGlobalLogLevel(Logging::LogLevel::NotSet); + + m_logFlag1 = false; + m_logFlag2 = false; + } + + static void loggingCallback1(const char*, int, void*) + { + m_logFlag1 = true; + } + + static void loggingCallback2(const char*, int, void*) + { + m_logFlag2 = true; + } + + static std::atomic_bool m_logFlag1; + static std::atomic_bool m_logFlag2; + + static std::atomic_int m_logSeverity1; + static std::atomic_int m_logSeverity2; +}; + +std::atomic_bool LoggingTest::m_logFlag1{}; +std::atomic_bool LoggingTest::m_logFlag2{}; + +std::atomic_int LoggingTest::m_logSeverity1{}; +std::atomic_int LoggingTest::m_logSeverity2{}; + +class LoggingTestWithLevels : public LoggingTest, public ::testing::WithParamInterface +{ +}; + +TEST_F(LoggingTest, GlobalCallback) +{ + EXPECT_FALSE(m_logFlag1); + + Logging::Log("Test", Logging::LogLevel::Information); + EXPECT_FALSE(m_logFlag1); + + EXPECT_FALSE(Logging::HasCallback()); + + Logging::SetGlobalCallback({&LoggingTest::loggingCallback1, nullptr}); + + EXPECT_TRUE(Logging::HasCallback()); + + Logging::Log("Test", Logging::LogLevel::Information); + EXPECT_TRUE(m_logFlag1); +} + +TEST_F(LoggingTest, ThreadCallback) +{ + EXPECT_FALSE(m_logFlag1); + + Logging::Log("Test", Logging::LogLevel::Information); + EXPECT_FALSE(m_logFlag1); + + EXPECT_FALSE(Logging::HasCallback()); + + Logging::SetThreadCallback({&LoggingTest::loggingCallback1, nullptr}); + + EXPECT_TRUE(Logging::HasCallback()); + + Logging::Log("Test", Logging::LogLevel::Information); + EXPECT_TRUE(m_logFlag1); +} + +TEST_F(LoggingTest, ThreadCallbackOtherThread) +{ + EXPECT_FALSE(m_logFlag1); + EXPECT_FALSE(m_logFlag2); + + Logging::Log("Test", Logging::LogLevel::Information); + + EXPECT_FALSE(m_logFlag1); + EXPECT_FALSE(m_logFlag2); + + Logging::SetGlobalCallback({&LoggingTest::loggingCallback1, nullptr}); + + // Register thread-local callback, should override global callback + { + auto loggingThreadFunc = [&]() -> void { + Logging::SetThreadCallback({&LoggingTest::loggingCallback2, nullptr}); + Logging::Log("Test", Logging::LogLevel::Information); + }; + + std::thread loggingThread{loggingThreadFunc}; + loggingThread.join(); + } + + EXPECT_FALSE(m_logFlag1); + EXPECT_TRUE(m_logFlag2); +} + +TEST_F(LoggingTest, GlobalCallbackOtherThread) +{ + EXPECT_FALSE(m_logFlag1); + EXPECT_FALSE(m_logFlag2); + + Logging::Log("Test", Logging::LogLevel::Information); + + EXPECT_FALSE(m_logFlag1); + EXPECT_FALSE(m_logFlag2); + + Logging::SetGlobalCallback({&LoggingTest::loggingCallback1, nullptr}); + + // No thread-local callback, should use global callback + { + auto loggingGlobalFunc = [&]() -> void { + Logging::Log("Test", Logging::LogLevel::Information); + }; + + std::thread loggingThread{loggingGlobalFunc}; + loggingThread.join(); + } + + EXPECT_TRUE(m_logFlag1); + EXPECT_FALSE(m_logFlag2); +} + +TEST_F(LoggingTest, GlobalCallbackUserData) +{ + static void* returnedPointer{}; + + const Logging::CallbackFunction logCallback = [](const char*, int, void* userData) -> void { + returnedPointer = userData; + }; + + Logging::Log("Test", Logging::LogLevel::Information); + EXPECT_FALSE(m_logFlag1); + + EXPECT_FALSE(Logging::HasCallback()); + + // We use the lambda address to "randomize" the data. + auto* testPointer{reinterpret_cast(logCallback)}; + Logging::SetGlobalCallback({logCallback, testPointer}); + + EXPECT_TRUE(Logging::HasCallback()); + + Logging::Log("Test", Logging::LogLevel::Information); + EXPECT_EQ(returnedPointer, testPointer); +} + +TEST_F(LoggingTest, ThreadCallbackUserData) +{ + static void* returnedPointer{}; + + const Logging::CallbackFunction logCallback = [](const char*, int, void* userData) -> void { + returnedPointer = userData; + }; + + Logging::Log("Test", Logging::LogLevel::Information); + EXPECT_FALSE(m_logFlag1); + + EXPECT_FALSE(Logging::HasCallback()); + + // We use the lambda address to "randomize" the data. + auto* testPointer{reinterpret_cast(logCallback)}; + Logging::SetThreadCallback({logCallback, testPointer}); + + EXPECT_TRUE(Logging::HasCallback()); + + Logging::Log("Test", Logging::LogLevel::Information); + EXPECT_EQ(returnedPointer, testPointer); +} + +TEST_F(LoggingTest, MessageInGlobalCallback) +{ + static std::string logMessage; + + const Logging::CallbackFunction logCallback = [](const char* message, int, void*) -> void { + logMessage = message; + }; + + Logging::SetGlobalCallback({logCallback, nullptr}); + logMessage = {}; + + Logging::Log("This is a test message", Logging::LogLevel::Information); + ASSERT_EQ(logMessage, "This is a test message"); +} + +TEST_F(LoggingTest, MessageInThreadCallback) +{ + static std::string logMessage; + + const Logging::CallbackFunction logCallback = [](const char* message, int, void*) -> void { + logMessage = message; + }; + + Logging::SetThreadCallback({logCallback, nullptr}); + logMessage = {}; + + Logging::Log("This is a test message", Logging::LogLevel::Information); + ASSERT_EQ(logMessage, "This is a test message"); +} + +TEST_P(LoggingTestWithLevels, SeverityInGlobalCallback) +{ + static Logging::LogLevel logLevel; + + Logging::CallbackFunction logCallback = [](const char*, int level, void*) -> void { + logLevel = static_cast(level); + }; + + Logging::SetGlobalCallback({logCallback, nullptr}); + logLevel = Logging::LogLevel::NotSet; + + Logging::Log("Test", GetParam()); + ASSERT_EQ(logLevel, GetParam()); +} + +TEST_P(LoggingTestWithLevels, SeverityInThreadCallback) +{ + static Logging::LogLevel logLevel; + + Logging::CallbackFunction logCallback = [](const char*, int level, void*) -> void { + logLevel = static_cast(level); + }; + + Logging::SetThreadCallback({logCallback, nullptr}); + logLevel = Logging::LogLevel::NotSet; + + Logging::Log("Test", GetParam()); + ASSERT_EQ(logLevel, GetParam()); +} + +TEST_P(LoggingTestWithLevels, LogLevelGlobal) +{ + Logging::SetGlobalLogLevel(GetParam()); + if (GetParam() == Logging::LogLevel::NotSet) + { + EXPECT_EQ(Logging::GetLogLevel(), Logging::m_defaultLogLevel); + } + else + { + EXPECT_EQ(Logging::GetLogLevel(), GetParam()); + } +} + +TEST_P(LoggingTestWithLevels, LogLevelThread) +{ + Logging::SetThreadLogLevel(GetParam()); + if (GetParam() == Logging::LogLevel::NotSet) + { + EXPECT_EQ(Logging::GetLogLevel(), Logging::m_defaultLogLevel); + } + else + { + EXPECT_EQ(Logging::GetLogLevel(), GetParam()); + } +} + +TEST_P(LoggingTestWithLevels, LogLevelThreadAndGlobal) +{ + Logging::SetGlobalLogLevel(Logging::LogLevel::Fatal); + Logging::SetThreadLogLevel(GetParam()); + if (GetParam() == Logging::LogLevel::NotSet) + { + // Fallback to global + EXPECT_EQ(Logging::GetLogLevel(), Logging::LogLevel::Fatal); + } + else + { + EXPECT_EQ(Logging::GetLogLevel(), GetParam()); + } +} + +INSTANTIATE_TEST_SUITE_P(LogLevel, LoggingTestWithLevels, testing::Values(Logging::LogLevel::NotSet, Logging::LogLevel::Trace, Logging::LogLevel::Debug, Logging::LogLevel::Information, Logging::LogLevel::Warning, Logging::LogLevel::Error, Logging::LogLevel::Fatal)); From c6e02c9e1a09003e33e62874c776ace1ccefab0f Mon Sep 17 00:00:00 2001 From: Kai Blaschke Date: Thu, 4 Dec 2025 16:32:33 +0100 Subject: [PATCH 4/7] Add logging API functions and callbacks --- src/api/CMakeLists.txt | 1 + src/api/include/projectM-4/callbacks.h | 1 - src/api/include/projectM-4/logging.h | 94 ++++++++++++++++++++++++++ src/api/include/projectM-4/types.h | 16 ++++- src/libprojectM/ProjectMCWrapper.cpp | 26 +++++++ 5 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 src/api/include/projectM-4/logging.h diff --git a/src/api/CMakeLists.txt b/src/api/CMakeLists.txt index 84f14c597e..681f099ac6 100644 --- a/src/api/CMakeLists.txt +++ b/src/api/CMakeLists.txt @@ -18,6 +18,7 @@ set(PROJECTM_PUBLIC_HEADERS include/projectM-4/callbacks.h include/projectM-4/core.h include/projectM-4/debug.h + include/projectM-4/logging.h include/projectM-4/memory.h include/projectM-4/projectM.h include/projectM-4/render_opengl.h diff --git a/src/api/include/projectM-4/callbacks.h b/src/api/include/projectM-4/callbacks.h index 28934cf42b..798abb3e56 100644 --- a/src/api/include/projectM-4/callbacks.h +++ b/src/api/include/projectM-4/callbacks.h @@ -91,4 +91,3 @@ PROJECTM_EXPORT void projectm_set_preset_switch_failed_event_callback(projectm_h #ifdef __cplusplus } // extern "C" #endif - diff --git a/src/api/include/projectM-4/logging.h b/src/api/include/projectM-4/logging.h new file mode 100644 index 0000000000..0630d80808 --- /dev/null +++ b/src/api/include/projectM-4/logging.h @@ -0,0 +1,94 @@ +/** + * @file logging.h + * @copyright 2003-2025 projectM Team + * @brief Functions for registering log callbacks and setting log levels. + * @since 4.2.0 + * + * projectM -- Milkdrop-esque visualisation SDK + * Copyright (C)2003-2024 projectM Team + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * See 'LICENSE.txt' included within this release + * + */ + +#pragma once + +#include "projectM-4/types.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Callback function that is executed if projectM wants to log a message. + * + * The message pointer is only valid inside the callback. Make a copy of the string inside the + * function to use it after the callback returns. Applications must not free the message pointer. + * + * @param message The message to be logged. + * @param log_level The log level this message was created for. + * @param user_data A user-defined data pointer that was provided when registering the callback, + * e.g. context information. + * @since 4.2.0 + */ +typedef void (*projectm_log_callback)(const char* message, projectm_log_level log_level, void* user_data); + + +/** + * @brief Sets a callback function that will be called for each message to be logged by projectM. + * + * The logging callback is independent of any projectM instance. Applications can set the callback + * globally, or only for a single thread, or both. If both a global and a thread-specific callback + * are set, the thread-specific callback takes precedence and the global callback will not be called. + * + * Since log messages will only be emitted by projectM when within an API call and the callback runs + * in the same thread as the projectM instance, applications can keep track of the instance making + * the call. + * + * If the application runs multiple projectM instances in separate threads, a global log callback + * (or the same thread callback registered in multiple threads) may be called in parallel from each + * thread, so the application has to take care about any possible race conditions in its own + * logging code and make sure it's thread-safe. + * + * To remove a callback, pass NULL as the callback argument. Thread-specific callbacks are removed + * automatically when the thread is terminated, though it is good practice to reset the callback at + * the end of a thread. + * + * @param callback A pointer to the callback function. + * @param current_thread_only If true, the callback is only set for the thread calling the function. + * @param user_data A pointer to any data that will be sent back in the callback, e.g. context + * information. + * @since 4.2.0 + * @note Log messages can contain line breaks. + */ +PROJECTM_EXPORT void projectm_set_log_callback(projectm_log_callback callback, + bool current_thread_only, + void* user_data); + +/** + * Sets the minimum log level for which the callback should be called. + * @param instance The projectM instance handle. + * @param log_level The new log level to set. If set to PROJECTM_LOG_LEVEL_NOTSET, the global or default setting will be used. + * @param current_thread_only If true, the log level is only set for the thread calling the function. + * @since 4.2.0 + */ +PROJECTM_EXPORT void projectm_set_log_level(projectm_log_level log_level, + bool current_thread_only); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/src/api/include/projectM-4/types.h b/src/api/include/projectM-4/types.h index d890ddc1c2..1f05b4b854 100644 --- a/src/api/include/projectM-4/types.h +++ b/src/api/include/projectM-4/types.h @@ -78,7 +78,21 @@ typedef enum PROJECTM_TOUCH_TYPE_DOUBLE_LINE //!< Draws a double-line waveform. } projectm_touch_type; +/** + * Log level constants for use with the logging API functions. + * @since 4.2.0 + */ +typedef enum +{ + PROJECTM_LOG_LEVEL_NOTSET = 0, //!< No specific log level, use default (INFO). + PROJECTM_LOG_LEVEL_TRACE = 1, //!< Verbose trace logging. Only enabled in debug builds by default. + PROJECTM_LOG_LEVEL_DEBUG = 2, //!< Development-related debug logging. Only enabled in debug builds by default. + PROJECTM_LOG_LEVEL_INFO = 3, //!< Informational messages. + PROJECTM_LOG_LEVEL_WARN = 4, //!< Warnings about non-critical issues. + PROJECTM_LOG_LEVEL_ERROR = 5, //!< Recoverable errors, e.g. shader compilation or I/O errors. + PROJECTM_LOG_LEVEL_FATAL = 6 //!< Irrecoverable errors preventing projectM from working. +} projectm_log_level; + #ifdef __cplusplus } // extern "C" #endif - diff --git a/src/libprojectM/ProjectMCWrapper.cpp b/src/libprojectM/ProjectMCWrapper.cpp index 506f12ceeb..839731b7ec 100644 --- a/src/libprojectM/ProjectMCWrapper.cpp +++ b/src/libprojectM/ProjectMCWrapper.cpp @@ -2,6 +2,8 @@ #include +#include + #include