From f701250fe7448eb2605a02d83030626cde0b186d Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 28 Jun 2026 20:23:27 -0400 Subject: [PATCH 01/10] Use lizardbyte-common test support in tests Switch the test suite to shared helpers from `lizardbyte-common` by enabling and linking `lizardbyte::test_support` in `tests/CMakeLists.txt`. Update `conftest.cpp` to inherit from `lizardbyte::common::testing::BaseTest`, use its argument/platform fixtures, and rely on its output capture flow, then remove the now-redundant local `tests/utils.*` helpers. Also bump the `third-party/lizardbyte-common` submodule to the version that provides this test support. --- .gitmodules | 4 --- tests/CMakeLists.txt | 11 +++++-- tests/conftest.cpp | 62 ++++++++--------------------------- tests/utils.cpp | 20 ----------- tests/utils.h | 10 ------ third-party/googletest | 1 - third-party/lizardbyte-common | 2 +- 7 files changed, 24 insertions(+), 86 deletions(-) delete mode 100644 tests/utils.cpp delete mode 100644 tests/utils.h delete mode 160000 third-party/googletest diff --git a/.gitmodules b/.gitmodules index e21742bb..67e53761 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,10 +2,6 @@ path = third-party/doxyconfig url = https://github.com/LizardByte/doxyconfig.git branch = master -[submodule "third-party/googletest"] - path = third-party/googletest - url = https://github.com/google/googletest.git - branch = v1.14.x [submodule "third-party/lizardbyte-common"] path = third-party/lizardbyte-common url = https://github.com/LizardByte/lizardbyte-common.git diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b48cac66..95669192 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.13) project(test_tray) # Add GoogleTest directory to the project -set(GTEST_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../third-party/googletest") +set(GTEST_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../third-party/lizardbyte-common/third-party/googletest") # For Windows: prevent overriding parent compiler/linker runtime settings. if(WIN32) @@ -16,6 +16,13 @@ set(INSTALL_GMOCK OFF) add_subdirectory("${GTEST_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/googletest") include_directories("${GTEST_SOURCE_DIR}/googletest/include" "${GTEST_SOURCE_DIR}") +set(LIZARDBYTE_COMMON_BUILD_TEST_SUPPORT ON CACHE BOOL "Build lizardbyte-common GoogleTest support helpers" FORCE) +if(NOT TARGET lizardbyte::common) + add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../third-party/lizardbyte-common" + "${CMAKE_CURRENT_BINARY_DIR}/lizardbyte-common") +elseif(NOT TARGET lizardbyte::test_support) + message(FATAL_ERROR "tray tests require lizardbyte::test_support") +endif() # extra libraries for tests if (APPLE) set(TEST_LIBS "-framework Cocoa") @@ -25,7 +32,6 @@ endif() file(GLOB_RECURSE TEST_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/conftest.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/utils.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/screenshot_utils.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/unit/test_*.cpp" ) @@ -40,6 +46,7 @@ target_include_directories(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME} ${TEST_LIBS} tray::tray + lizardbyte::test_support gtest gtest_main # if we use this we don't need our own main function ) diff --git a/tests/conftest.cpp b/tests/conftest.cpp index 6ae7c7b9..e919a733 100644 --- a/tests/conftest.cpp +++ b/tests/conftest.cpp @@ -4,12 +4,12 @@ #include // lib includes -#include +#define LIZARDBYTE_COMMON_TESTING_KEEP_GTEST_TEST +#define LIZARDBYTE_COMMON_TESTING_NO_GLOBAL_ALIASES +#include // test includes #include "tests/screenshot_utils.h" -#include "tests/utils.h" - // Undefine the original TEST macro #undef TEST @@ -26,7 +26,7 @@ * * @todo Retain the color of the original output. */ -class BaseTest: public ::testing::Test { +class BaseTest: public ::lizardbyte::common::testing::BaseTest { protected: // https://stackoverflow.com/a/58369622/11214013 @@ -38,10 +38,12 @@ class BaseTest: public ::testing::Test { ~BaseTest() override = default; void SetUp() override { + ::lizardbyte::common::testing::BaseTest::SetUp(); + // todo: only run this one time, instead of every time a test is run // see: https://stackoverflow.com/questions/2435277/googletest-accessing-the-environment-from-a-test // get command line args from the test executable - testArgs = ::testing::internal::GetArgvs(); + testArgs = getArgs(); // then get the directory of the test executable // std::string path = ::testing::internal::GetArgvs()[0]; @@ -57,30 +59,22 @@ class BaseTest: public ::testing::Test { } initializeScreenshotsOnce(); - - sbuf = std::cout.rdbuf(); // save cout buffer (std::cout) - std::cout.rdbuf(cout_buffer.rdbuf()); // redirect cout to buffer (std::cout) } void TearDown() override { - std::cout.rdbuf(sbuf); // restore cout buffer - - // get test info const ::testing::TestInfo *const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); + const bool failed = test_info != nullptr && test_info->result()->Failed(); + + ::lizardbyte::common::testing::BaseTest::TearDown(); - if (test_info->result()->Failed()) { + if (failed) { std::cout << std::endl - << "Test failed: " << test_info->name() << std::endl - << std::endl - << "Captured cout:" << std::endl - << cout_buffer.str() << std::endl << "Captured stdout:" << std::endl << stdout_buffer.str() << std::endl << "Captured stderr:" << std::endl << stderr_buffer.str() << std::endl; } - sbuf = nullptr; // clear sbuf if (pipe_stdout) { pclose(pipe_stdout); pipe_stdout = nullptr; @@ -95,10 +89,8 @@ class BaseTest: public ::testing::Test { std::vector testArgs; // CLI arguments used std::filesystem::path testBinary; // full path of this binary std::filesystem::path testBinaryDir; // full directory of this binary - std::stringstream cout_buffer; // declare cout_buffer std::stringstream stdout_buffer; // declare stdout_buffer std::stringstream stderr_buffer; // declare stderr_buffer - std::streambuf *sbuf {nullptr}; FILE *pipe_stdout {nullptr}; FILE *pipe_stderr {nullptr}; bool screenshotsReady {false}; @@ -171,32 +163,6 @@ class BaseTest: public ::testing::Test { std::string screenshotUnavailableReason; }; -class LinuxTest: public BaseTest { -protected: - void SetUp() override { -#ifndef __linux__ - GTEST_SKIP_("Skipping, this test is for Linux only."); -#endif - BaseTest::SetUp(); - } -}; - -class MacOSTest: public BaseTest { -protected: - void SetUp() override { -#if !defined(__APPLE__) || !defined(__MACH__) - GTEST_SKIP_("Skipping, this test is for macOS only."); -#endif - BaseTest::SetUp(); - } -}; - -class WindowsTest: public BaseTest { -protected: - void SetUp() override { // NOSONAR(cpp:S1185) - contains platform skip logic, not a trivial override -#ifndef _WIN32 - GTEST_SKIP_("Skipping, this test is for Windows only."); -#endif - BaseTest::SetUp(); - } -}; +using LinuxTest = ::lizardbyte::common::testing::LinuxTest; +using MacOSTest = ::lizardbyte::common::testing::MacOSTest; +using WindowsTest = ::lizardbyte::common::testing::WindowsTest; diff --git a/tests/utils.cpp b/tests/utils.cpp deleted file mode 100644 index da5f665b..00000000 --- a/tests/utils.cpp +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @file utils.cpp - * @brief Utility functions - */ -// test includes -#include "utils.h" - -/** - * @brief Set an environment variable. - * @param name Name of the environment variable - * @param value Value of the environment variable - * @return 0 on success, non-zero error code on failure - */ -int setEnv(const std::string &name, const std::string &value) { -#ifdef _WIN32 - return _putenv_s(name.c_str(), value.c_str()); -#else - return setenv(name.c_str(), value.c_str(), 1); -#endif -} diff --git a/tests/utils.h b/tests/utils.h deleted file mode 100644 index 40812795..00000000 --- a/tests/utils.h +++ /dev/null @@ -1,10 +0,0 @@ -/** - * @file utils.h - * @brief Reusable functions for tests. - */ -#pragma once - -// standard includes -#include - -int setEnv(const std::string &name, const std::string &value); diff --git a/third-party/googletest b/third-party/googletest deleted file mode 160000 index f8d7d77c..00000000 --- a/third-party/googletest +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f8d7d77c06936315286eb55f8de22cd23c188571 diff --git a/third-party/lizardbyte-common b/third-party/lizardbyte-common index 8d7dcc97..0fcde420 160000 --- a/third-party/lizardbyte-common +++ b/third-party/lizardbyte-common @@ -1 +1 @@ -Subproject commit 8d7dcc97d0795e4eb2efdb50a86f83060ed47934 +Subproject commit 0fcde420419c77fdde04ffb04ceda0b560bd1cfc From dbfd12f16921dcb46e8a3e00b97fd45ad2a4be31 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 28 Jun 2026 23:44:12 -0400 Subject: [PATCH 02/10] Simplify test fixture and bump common submodule Refactors `tests/conftest.cpp` to rely on shared behavior from `lizardbyte-common` by removing the custom stdout/stderr capture, `exec()` helper, and related teardown plumbing from `BaseTest`. It also updates the fixture comment to reflect tray-specific helpers and advances the `third-party/lizardbyte-common` submodule to the newer commit. --- tests/conftest.cpp | 62 +---------------------------------- third-party/lizardbyte-common | 2 +- 2 files changed, 2 insertions(+), 62 deletions(-) diff --git a/tests/conftest.cpp b/tests/conftest.cpp index e919a733..7b7634b8 100644 --- a/tests/conftest.cpp +++ b/tests/conftest.cpp @@ -1,5 +1,4 @@ // standard includes -#include #include #include @@ -20,19 +19,10 @@ /** * @brief Base class for tests. * - * This class provides a base test fixture for all tests. - * - * ``cout``, ``stderr``, and ``stdout`` are redirected to a buffer, and the buffer is printed if the test fails. - * - * @todo Retain the color of the original output. + * This class provides a base test fixture for all tests and adds tray-specific helpers. */ class BaseTest: public ::lizardbyte::common::testing::BaseTest { protected: - // https://stackoverflow.com/a/58369622/11214013 - - // we can possibly use some internal googletest functions to capture stdout and stderr, but I have not tested this - // https://stackoverflow.com/a/33186201/11214013 - BaseTest() = default; ~BaseTest() override = default; @@ -61,38 +51,10 @@ class BaseTest: public ::lizardbyte::common::testing::BaseTest { initializeScreenshotsOnce(); } - void TearDown() override { - const ::testing::TestInfo *const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); - const bool failed = test_info != nullptr && test_info->result()->Failed(); - - ::lizardbyte::common::testing::BaseTest::TearDown(); - - if (failed) { - std::cout << std::endl - << "Captured stdout:" << std::endl - << stdout_buffer.str() << std::endl - << "Captured stderr:" << std::endl - << stderr_buffer.str() << std::endl; - } - - if (pipe_stdout) { - pclose(pipe_stdout); - pipe_stdout = nullptr; - } - if (pipe_stderr) { - pclose(pipe_stderr); - pipe_stderr = nullptr; - } - } - // functions and variables std::vector testArgs; // CLI arguments used std::filesystem::path testBinary; // full path of this binary std::filesystem::path testBinaryDir; // full directory of this binary - std::stringstream stdout_buffer; // declare stdout_buffer - std::stringstream stderr_buffer; // declare stderr_buffer - FILE *pipe_stdout {nullptr}; - FILE *pipe_stderr {nullptr}; bool screenshotsReady {false}; void initializeScreenshotsOnce() { @@ -107,28 +69,6 @@ class BaseTest: public ::lizardbyte::common::testing::BaseTest { }); } - int exec(const char *cmd) { - std::array buffer {}; - pipe_stdout = popen((std::string(cmd) + " 2>&1").c_str(), "r"); - pipe_stderr = popen((std::string(cmd) + " 2>&1").c_str(), "r"); - if (!pipe_stdout || !pipe_stderr) { - throw std::runtime_error("popen() failed!"); - } - while (fgets(buffer.data(), buffer.size(), pipe_stdout) != nullptr) { - stdout_buffer << buffer.data(); - } - while (fgets(buffer.data(), buffer.size(), pipe_stderr) != nullptr) { - stderr_buffer << buffer.data(); - } - int returnCode = pclose(pipe_stdout); - pipe_stdout = nullptr; - if (returnCode != 0) { - std::cout << "Error: " << stderr_buffer.str() << std::endl - << "Return code: " << returnCode << std::endl; - } - return returnCode; - } - bool ensureScreenshotReady() { if (screenshotsReady) { return true; diff --git a/third-party/lizardbyte-common b/third-party/lizardbyte-common index 0fcde420..d219f380 160000 --- a/third-party/lizardbyte-common +++ b/third-party/lizardbyte-common @@ -1 +1 @@ -Subproject commit 0fcde420419c77fdde04ffb04ceda0b560bd1cfc +Subproject commit d219f38090db20119906c80860833592a6be669a From e69b7696e3a44a0d694be5def2e816160eb39890 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 29 Jun 2026 10:17:44 -0400 Subject: [PATCH 03/10] Encapsulate screenshot state in test fixture Refactors `tests/conftest.cpp` to make fixture internals private, rename state members with trailing underscores, and expose read-only accessors for test binary dir and screenshot skip reason. It also makes screenshot initialization/capture helpers const-correct and updates `tests/unit/test_tray.cpp` to use the new accessor methods. --- tests/conftest.cpp | 67 ++++++++++++++++++++++------------------ tests/unit/test_tray.cpp | 6 ++-- 2 files changed, 40 insertions(+), 33 deletions(-) diff --git a/tests/conftest.cpp b/tests/conftest.cpp index 7b7634b8..933cd9fe 100644 --- a/tests/conftest.cpp +++ b/tests/conftest.cpp @@ -33,60 +33,42 @@ class BaseTest: public ::lizardbyte::common::testing::BaseTest { // todo: only run this one time, instead of every time a test is run // see: https://stackoverflow.com/questions/2435277/googletest-accessing-the-environment-from-a-test // get command line args from the test executable - testArgs = getArgs(); + testArgs_ = getArgs(); // then get the directory of the test executable // std::string path = ::testing::internal::GetArgvs()[0]; - testBinary = testArgs[0]; + testBinary_ = testArgs_[0]; // get the directory of the test executable - testBinaryDir = std::filesystem::path(testBinary).parent_path(); + testBinaryDir_ = std::filesystem::path(testBinary_).parent_path(); // If testBinaryDir is empty or `.` then set it to the current directory // maybe some better options here: https://stackoverflow.com/questions/875249/how-to-get-current-directory - if (testBinaryDir.empty() || testBinaryDir.string() == ".") { - testBinaryDir = std::filesystem::current_path(); + if (testBinaryDir_.empty() || testBinaryDir_.string() == ".") { + testBinaryDir_ = std::filesystem::current_path(); } initializeScreenshotsOnce(); } - // functions and variables - std::vector testArgs; // CLI arguments used - std::filesystem::path testBinary; // full path of this binary - std::filesystem::path testBinaryDir; // full directory of this binary - bool screenshotsReady {false}; - - void initializeScreenshotsOnce() { - static std::once_flag screenshotInitFlag; - std::call_once(screenshotInitFlag, [this]() { - auto root = testBinaryDir; - if (!root.empty()) { - std::error_code ec; - std::filesystem::remove_all(root / "screenshots", ec); - } - screenshot::initialize(root); - }); - } - bool ensureScreenshotReady() { - if (screenshotsReady) { + if (screenshotsReady_) { return true; } if (std::string reason; !screenshot::is_available(&reason)) { - screenshotUnavailableReason = reason; + screenshotUnavailableReason_ = reason; return false; } if (const auto root = screenshot::output_root(); root.empty()) { - screenshotUnavailableReason = "Screenshot output directory not initialized"; + screenshotUnavailableReason_ = "Screenshot output directory not initialized"; return false; } - screenshotsReady = true; + screenshotsReady_ = true; return true; } - bool captureScreenshot(const std::string &name) { - if (!screenshotsReady) { + bool captureScreenshot(const std::string &name) const { + if (!screenshotsReady_) { return false; } bool ok = screenshot::capture(name); @@ -100,7 +82,32 @@ class BaseTest: public ::lizardbyte::common::testing::BaseTest { return screenshot::output_root(); } - std::string screenshotUnavailableReason; + [[nodiscard]] const std::filesystem::path &testBinaryDir() const { + return testBinaryDir_; + } + + [[nodiscard]] const std::string &screenshotUnavailableReason() const { + return screenshotUnavailableReason_; + } + +private: + void initializeScreenshotsOnce() const { + static std::once_flag screenshotInitFlag; + std::call_once(screenshotInitFlag, [this]() { + auto root = testBinaryDir_; + if (!root.empty()) { + std::error_code ec; + std::filesystem::remove_all(root / "screenshots", ec); + } + screenshot::initialize(root); + }); + } + + std::vector testArgs_; // CLI arguments used + std::filesystem::path testBinary_; // full path of this binary + std::filesystem::path testBinaryDir_; // full directory of this binary + bool screenshotsReady_ {false}; + std::string screenshotUnavailableReason_; }; using LinuxTest = ::lizardbyte::common::testing::LinuxTest; diff --git a/tests/unit/test_tray.cpp b/tests/unit/test_tray.cpp index 103896e8..69b9ca5a 100644 --- a/tests/unit/test_tray.cpp +++ b/tests/unit/test_tray.cpp @@ -172,14 +172,14 @@ class TrayTest: public BaseTest { // NOSONAR(cpp:S3656) - fixture members must // Skip tests if screenshot tooling is not available if (!ensureScreenshotReady()) { - GTEST_SKIP() << "Screenshot tooling missing: " << screenshotUnavailableReason; + GTEST_SKIP() << "Screenshot tooling missing: " << screenshotUnavailableReason(); } if (screenshot::output_root().empty()) { GTEST_SKIP() << "Screenshot output path not initialized"; } // Ensure icon files exist in test binary directory - std::filesystem::path projectRoot = testBinaryDir.parent_path(); + std::filesystem::path projectRoot = testBinaryDir().parent_path(); auto ensureIconInTestDir = [&projectRoot, this](const char *iconName) { std::filesystem::path iconSource; @@ -192,7 +192,7 @@ class TrayTest: public BaseTest { // NOSONAR(cpp:S3656) - fixture members must } if (!iconSource.empty()) { - std::filesystem::path iconDest = testBinaryDir / iconName; + std::filesystem::path iconDest = testBinaryDir() / iconName; if (!std::filesystem::exists(iconDest)) { std::error_code ec; std::filesystem::copy_file(iconSource, iconDest, ec); From f94b7864d5ada138f2cbf0947036ba58637ae915 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 29 Jun 2026 10:21:24 -0400 Subject: [PATCH 04/10] Update lizardbyte-common submodule Bump `third-party/lizardbyte-common` to `60bf936e134359c65c8a044369db227f976947e5`. --- third-party/lizardbyte-common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third-party/lizardbyte-common b/third-party/lizardbyte-common index d219f380..60bf936e 160000 --- a/third-party/lizardbyte-common +++ b/third-party/lizardbyte-common @@ -1 +1 @@ -Subproject commit d219f38090db20119906c80860833592a6be669a +Subproject commit 60bf936e134359c65c8a044369db227f976947e5 From 80b6572d1f135dc5cda53c80f19d68558ba0188c Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 29 Jun 2026 10:27:28 -0400 Subject: [PATCH 05/10] Sonar fixes --- tests/unit/test_tray.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/test_tray.cpp b/tests/unit/test_tray.cpp index 69b9ca5a..c7ad21fd 100644 --- a/tests/unit/test_tray.cpp +++ b/tests/unit/test_tray.cpp @@ -99,7 +99,7 @@ class TrayTest: public BaseTest { // NOSONAR(cpp:S3656) - fixture members must } // Dismisses the open menu from a background thread. - void closeMenu() { + void closeMenu() const { #if defined(TRAY_WINAPI) PostMessage(tray_get_hwnd(), WM_CANCELMODE, 0, 0); std::this_thread::sleep_for(std::chrono::milliseconds(100)); @@ -115,7 +115,7 @@ class TrayTest: public BaseTest { // NOSONAR(cpp:S3656) - fixture members must } // Capture a screenshot while the tray menu is open, then dismiss and exit. - void captureMenuStateAndExit(const char *screenshotName) { + void captureMenuStateAndExit(const char *screenshotName) const { std::atomic_bool exitRequested {false}; std::thread capture_thread([this, screenshotName, &exitRequested]() { // NOSONAR(cpp:S6168) - std::jthread is unavailable on AppleClang 17/libc++ used in CI EXPECT_TRUE(captureScreenshot(screenshotName)); @@ -226,7 +226,7 @@ class TrayTest: public BaseTest { // NOSONAR(cpp:S3656) - fixture members must // Process pending events to allow tray icon to appear. // Call this ONLY before screenshots to ensure the icon is visible. - void WaitForTrayReady() { + void WaitForTrayReady() const { #if defined(TRAY_QT) for (int i = 0; i < 100; i++) { tray_loop(0); From e2cb8dc4f4352cf141227d52ae3160fdefe3797c Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 29 Jun 2026 14:03:59 -0400 Subject: [PATCH 06/10] Bump lizardbyte-common submodule Updates the third-party/lizardbyte-common submodule reference from 60bf936 to 3f29834 to pull in the latest upstream common-library changes. --- third-party/lizardbyte-common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third-party/lizardbyte-common b/third-party/lizardbyte-common index 60bf936e..3f29834c 160000 --- a/third-party/lizardbyte-common +++ b/third-party/lizardbyte-common @@ -1 +1 @@ -Subproject commit 60bf936e134359c65c8a044369db227f976947e5 +Subproject commit 3f29834c942d71d626f2daf67323fc61426bf524 From 951224a030a156c2e7c80fa251ed8dd32b95c7fb Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 29 Jun 2026 18:06:15 -0400 Subject: [PATCH 07/10] docs(readme): update heading --- README.md | 20 ++++++++++++++++---- docs/Doxyfile | 4 +++- tray.svg | 4 ++++ 3 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 tray.svg diff --git a/README.md b/README.md index 0c9408a4..e061e04b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,20 @@ -# Overview +
+ tray icon +

tray

+

Cross-platform implementation of a system tray icon with a popup menu and notifications.

+
-[![GitHub Workflow Status (CI)](https://img.shields.io/github/actions/workflow/status/lizardbyte/tray/ci.yml.svg?branch=master&label=CI%20build&logo=github&style=for-the-badge)](https://github.com/LizardByte/tray/actions/workflows/ci.yml?query=branch%3Amaster) -[![Codecov](https://img.shields.io/codecov/c/gh/LizardByte/tray?token=HSX66JNEOL&style=for-the-badge&logo=codecov&label=codecov)](https://codecov.io/gh/LizardByte/tray) -[![GitHub stars](https://img.shields.io/github/stars/lizardbyte/tray.svg?logo=github&style=for-the-badge)](https://github.com/LizardByte/tray) +
+ GitHub stars + GitHub Workflow Status (CI) + Read the Docs + Codecov + SonarCloud +
## About diff --git a/docs/Doxyfile b/docs/Doxyfile index 3f004c67..37c4eaf2 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -24,7 +24,9 @@ # project metadata DOCSET_BUNDLE_ID = dev.lizardbyte.tray DOCSET_PUBLISHER_ID = dev.lizardbyte.tray.documentation -PROJECT_BRIEF = "Cross-platform, super tiny C99 implementation of a system tray icon with a popup menu and notifications." +PROJECT_BRIEF = "Cross-platform implementation of a system tray icon with a popup menu and notifications." +PROJECT_ICON = ../tray.svg +PROJECT_LOGO = ../tray.svg PROJECT_NAME = tray # project specific settings diff --git a/tray.svg b/tray.svg new file mode 100644 index 00000000..393f9161 --- /dev/null +++ b/tray.svg @@ -0,0 +1,4 @@ + + + + From 9e2051424591c106e85c0201fa08fcb54d017c5f Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 29 Jun 2026 20:24:02 -0400 Subject: [PATCH 08/10] Polish README sections and add docs run config Adds a JetBrains CMake run configuration (`docs`) to build and open generated HTML documentation in Explorer. Updates `README.md` structure by introducing an `Overview` heading and renaming major sections with emoji-prefixed titles for clearer navigation. --- .run/docs.run.xml | 7 +++++++ README.md | 38 +++++++++++++++++++++++--------------- 2 files changed, 30 insertions(+), 15 deletions(-) create mode 100644 .run/docs.run.xml diff --git a/.run/docs.run.xml b/.run/docs.run.xml new file mode 100644 index 00000000..5e21d885 --- /dev/null +++ b/.run/docs.run.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/README.md b/README.md index e061e04b..d333a204 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,9 @@ SonarCloud -## About +# Overview + +## â„šī¸ About Cross-platform, super tiny C99 implementation of a system tray icon with a popup menu and notifications. @@ -32,23 +34,29 @@ This fork adds the following features: - refactored code, e.g., moved source code into the `src` directory - doxygen documentation and readthedocs configuration -## Screenshots +## đŸ–ŧī¸ Screenshots
- -- Linux![linux](docs/images/screenshot_linux.png) -- macOS![macOS](docs/images/screenshot_macos.png) -- Windows![windows](docs/images/screenshot_windows.png) - +
    +
  • Linux
    + ![linux](docs/images/screenshot_linux.png) +
  • +
  • macOS
    + ![macOS](docs/images/screenshot_macos.png) +
  • +
  • Windows
    + ![windows](docs/images/screenshot_windows.png) +
  • +
-## Supported platforms +## đŸ–Ĩī¸ Supported platforms * Linux/Qt (Qt5 or Qt6 Widgets) * Windows XP or newer (shellapi.h) * MacOS (Cocoa/AppKit) -## Prerequisites +## 📋 Prerequisites * CMake * [Ninja](https://ninja-build.org/), to have the same build commands on all platforms. @@ -88,7 +96,7 @@ Install either Qt6 _or_ Qt5 as well as libnotify development packages. The Linux -## Building +## đŸ› ī¸ Building ```bash mkdir -p build @@ -96,7 +104,7 @@ cmake -G Ninja -B build -S . ninja -C build ``` -## Python Tooling +## âš™ī¸ Python Tooling Install [uv](https://docs.astral.sh/uv/) and initialize the shared tooling submodule: @@ -106,7 +114,7 @@ uv run --project third-party/lizardbyte-common --locked --only-group lint-c \ python third-party/lizardbyte-common/scripts/update_clang_format.py ``` -## Demo +## â–ļī¸ Demo Execute the `tray_example` application: @@ -114,7 +122,7 @@ Execute the `tray_example` application: ./build/tray_example ``` -## Tests +## ✅ Tests Execute the `tests` application: @@ -122,7 +130,7 @@ Execute the `tests` application: ./build/tests/test_tray ``` -## API +## 📚 API Tray structure defines an icon and a menu. Menu is a NULL-terminated array of items. @@ -157,7 +165,7 @@ All functions are meant to be called from the UI thread only. Menu arrays must be terminated with a NULL item, e.g. the last item in the array must have text field set to NULL. -## License +## 📄 License This software is distributed under [MIT license](http://www.opensource.org/licenses/mit-license.php), so feel free to integrate it in your commercial products. From 7296f7985779daf17da5a8ef96ea5c2a6e89a9a6 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 30 Jun 2026 09:47:05 -0400 Subject: [PATCH 09/10] Remove docs badge and bump common submodule Drops the Read the Docs badge from the README header and updates `third-party/lizardbyte-common` to commit `49aa8c6`, pulling in the latest shared common changes. --- README.md | 1 - third-party/lizardbyte-common | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index d333a204..dd136bda 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@
GitHub stars GitHub Workflow Status (CI) - Read the Docs Codecov SonarCloud
diff --git a/third-party/lizardbyte-common b/third-party/lizardbyte-common index 3f29834c..49aa8c60 160000 --- a/third-party/lizardbyte-common +++ b/third-party/lizardbyte-common @@ -1 +1 @@ -Subproject commit 3f29834c942d71d626f2daf67323fc61426bf524 +Subproject commit 49aa8c6040487a83a57ee454dae30a4c93275f5b From b7bb578c682b3b5d2e131caca497abe4b24b622d Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 30 Jun 2026 17:54:49 -0400 Subject: [PATCH 10/10] Bump lizardbyte-common submodule Updates `third-party/lizardbyte-common` to commit `06cd442b808f02f1674f3192a84d25b9a503c482` from `49aa8c6040487a83a57ee454dae30a4c93275f5b`. --- third-party/lizardbyte-common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third-party/lizardbyte-common b/third-party/lizardbyte-common index 49aa8c60..06cd442b 160000 --- a/third-party/lizardbyte-common +++ b/third-party/lizardbyte-common @@ -1 +1 @@ -Subproject commit 49aa8c6040487a83a57ee454dae30a4c93275f5b +Subproject commit 06cd442b808f02f1674f3192a84d25b9a503c482