From 30754e30e6970415150bd495dd8eca4aa9f4b23e Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 6 Sep 2026 10:23:13 -0400 Subject: [PATCH 1/3] test: adjust Linux tray fallback position on Plasma MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the Linux-specific fallback menu anchor when only a bottom inset is detected. Instead of using the far-right edge, position the tray point at roughly three-quarters of the screen width to better match KDE Plasma’s default bottom panel layout, where the rightmost control is often “Peek at Desktop” rather than a tray icon. --- src/QtTrayMenu.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/QtTrayMenu.cpp b/src/QtTrayMenu.cpp index 4b37a0ed..2f4ea1f2 100644 --- a/src/QtTrayMenu.cpp +++ b/src/QtTrayMenu.cpp @@ -61,7 +61,13 @@ namespace { if (topInset > 0) { *position = QPoint(screenGeometry.right() - (topInset / 2), screenGeometry.top() + (topInset / 2)); } else if (bottomInset > 0) { +#if defined(__linux__) + // Plasma's default bottom panel places the system tray three quarters across the screen. + // Its far-right control is Peek at Desktop, rather than a tray icon. + *position = QPoint(screenGeometry.left() + ((screenGeometry.width() * 3) / 4), screenGeometry.bottom() - (bottomInset / 2)); +#else *position = QPoint(screenGeometry.right() - (bottomInset / 2), screenGeometry.bottom() - (bottomInset / 2)); +#endif } else if (rightInset > 0) { *position = QPoint(screenGeometry.right() - (rightInset / 2), screenGeometry.bottom() - (rightInset / 2)); } else if (leftInset > 0) { From 0ca2579e4631c3ec4a66e9821b92b1310ef029b7 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 6 Sep 2026 10:42:04 -0400 Subject: [PATCH 2/3] test: gate tray test hooks behind build flag Wraps mouse-position test helpers and related Qt/state code in `TRAY_ENABLE_TEST_HOOKS` so test-only APIs are excluded from normal builds. CMake now defines this flag for top-level test builds and for the test target, keeping production/library consumers free of test hooks while preserving test functionality. --- CMakeLists.txt | 3 +++ src/QtTrayMenu.cpp | 31 +++++++++++++++++++++---------- src/QtTrayMenu.h | 10 +++++++++- src/tray.h | 2 ++ src/tray_qt.cpp | 2 ++ tests/CMakeLists.txt | 1 + 6 files changed, 38 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a487c529..88cf108e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,6 +95,9 @@ endif() add_library(${PROJECT_NAME} STATIC ${TRAY_SOURCES}) set_property(TARGET ${PROJECT_NAME} PROPERTY C_STANDARD 99) set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 17) +if(TRAY_IS_TOP_LEVEL AND BUILD_TESTS) + target_compile_definitions(${PROJECT_NAME} PRIVATE TRAY_ENABLE_TEST_HOOKS) +endif() target_include_directories(${PROJECT_NAME} PUBLIC $ diff --git a/src/QtTrayMenu.cpp b/src/QtTrayMenu.cpp index 2f4ea1f2..fe04988d 100644 --- a/src/QtTrayMenu.cpp +++ b/src/QtTrayMenu.cpp @@ -3,18 +3,25 @@ * @brief Definitions for Qt tray menu implemenation */ // standard includes -#include #include -#include // qt includes #include -#include #include #include -#include #include +// conditional includes +#ifdef TRAY_ENABLE_TEST_HOOKS + // standard + #include + #include + + // qt + #include + #include +#endif + // local includes #include "QtTrayMenu.h" @@ -23,6 +30,7 @@ #endif namespace { +#ifdef TRAY_ENABLE_TEST_HOOKS constexpr int DEFAULT_PANEL_THICKNESS = 24; constexpr int CURSOR_POSITION_POLL_INTERVAL_MS = 10; constexpr int CURSOR_POSITION_TIMEOUT_MS = 500; @@ -61,26 +69,27 @@ namespace { if (topInset > 0) { *position = QPoint(screenGeometry.right() - (topInset / 2), screenGeometry.top() + (topInset / 2)); } else if (bottomInset > 0) { -#if defined(__linux__) + #if defined(__linux__) // Plasma's default bottom panel places the system tray three quarters across the screen. // Its far-right control is Peek at Desktop, rather than a tray icon. *position = QPoint(screenGeometry.left() + ((screenGeometry.width() * 3) / 4), screenGeometry.bottom() - (bottomInset / 2)); -#else + #else *position = QPoint(screenGeometry.right() - (bottomInset / 2), screenGeometry.bottom() - (bottomInset / 2)); -#endif + #endif } else if (rightInset > 0) { *position = QPoint(screenGeometry.right() - (rightInset / 2), screenGeometry.bottom() - (rightInset / 2)); } else if (leftInset > 0) { *position = QPoint(screenGeometry.left() + (leftInset / 2), screenGeometry.bottom() - (leftInset / 2)); } else { -#if defined(_WIN32) + #if defined(_WIN32) *position = QPoint(screenGeometry.right() - (DEFAULT_PANEL_THICKNESS / 2), screenGeometry.bottom() - (DEFAULT_PANEL_THICKNESS / 2)); -#else + #else *position = QPoint(screenGeometry.right() - (DEFAULT_PANEL_THICKNESS / 2), screenGeometry.top() + (DEFAULT_PANEL_THICKNESS / 2)); -#endif + #endif } return true; } +#endif } // namespace QtTrayMenu::QtTrayMenu(QObject *parent, const bool debug): @@ -407,6 +416,7 @@ void QtTrayMenu::clearMessageCallback() const { notificationCallback = nullptr; } +#ifdef TRAY_ENABLE_TEST_HOOKS bool QtTrayMenu::positionMouseOverIcon() { if (!trayIcon) { return false; @@ -448,3 +458,4 @@ bool QtTrayMenu::restoreMousePosition() { } return restored; } +#endif diff --git a/src/QtTrayMenu.h b/src/QtTrayMenu.h index 96218e72..7ef31c2f 100644 --- a/src/QtTrayMenu.h +++ b/src/QtTrayMenu.h @@ -12,10 +12,14 @@ // qt includes #include #include -#include #include #include +// conditional includes +#ifdef TRAY_ENABLE_TEST_HOOKS + #include +#endif + // local includes #include "tray.h" @@ -111,6 +115,7 @@ class QtTrayMenu: public QObject { */ void clearMessageCallback() const; +#ifdef TRAY_ENABLE_TEST_HOOKS /** * @brief Move the mouse cursor to the center of the tray icon. * @return true if the tray icon has valid screen geometry and the cursor was moved @@ -122,6 +127,7 @@ class QtTrayMenu: public QObject { * @return true if a saved position existed and the cursor was restored */ bool restoreMousePosition(); +#endif /** * @brief Check if QtTrayMenu supports messages @@ -163,8 +169,10 @@ class QtTrayMenu: public QObject { bool blockingEventLoop = false; struct tray_menu *getTrayMenuItem(const QAction *action); mutable std::function notificationCallback = nullptr; +#ifdef TRAY_ENABLE_TEST_HOOKS QPoint savedMousePosition; bool mousePositionSaved = false; +#endif private slots: void onExitRequested(); diff --git a/src/tray.h b/src/tray.h index 6a63a462..8fbfb7ae 100644 --- a/src/tray.h +++ b/src/tray.h @@ -70,6 +70,7 @@ extern "C" { */ void tray_show_menu(void); +#ifdef TRAY_ENABLE_TEST_HOOKS /** * @brief Position the mouse over the tray icon (for testing purposes). * @return 0 on success, -1 if the tray icon geometry is unavailable. @@ -81,6 +82,7 @@ extern "C" { * @return 0 on success, -1 if no saved position exists or the cursor could not be restored. */ int tray_restore_mouse_position(void); +#endif /** * @brief Simulate a notification click, invoking the notification callback (for testing purposes). diff --git a/src/tray_qt.cpp b/src/tray_qt.cpp index eb1c9ea2..811c0629 100644 --- a/src/tray_qt.cpp +++ b/src/tray_qt.cpp @@ -221,6 +221,7 @@ extern "C" { tray_qt::state().trayMenu->showMenu(); } +#ifdef TRAY_ENABLE_TEST_HOOKS int tray_position_mouse_over_icon(void) { if (tray_qt::state().trayMenu == nullptr) { return -1; @@ -234,6 +235,7 @@ extern "C" { } return tray_qt::state().trayMenu->restoreMousePosition() ? 0 : -1; } +#endif void tray_simulate_menu_item_click(int index) { if (tray_qt::state().trayMenu == nullptr) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 33c73672..3e5c70b0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -53,6 +53,7 @@ target_link_libraries(${PROJECT_NAME} gtest gtest_main # if we use this we don't need our own main function ) +target_compile_definitions(${PROJECT_NAME} PRIVATE TRAY_ENABLE_TEST_HOOKS) target_compile_definitions(${PROJECT_NAME} PUBLIC ${TEST_DEFINITIONS}) target_link_options(${PROJECT_NAME} PRIVATE) From c5df854df9eba5e693910877e9281a35ba59dca5 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 6 Sep 2026 10:59:55 -0400 Subject: [PATCH 3/3] Stabilize tooltip hover test timing Extract tooltip wait logic into a dedicated `WaitForTooltipReady()` helper and use it in `TestTooltipDisplayOnHover`. This centralizes the asynchronous shell-tooltip delay handling and improves test readability and consistency. --- tests/unit/test_tray.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/unit/test_tray.cpp b/tests/unit/test_tray.cpp index dc2dbb62..0a72b9bc 100644 --- a/tests/unit/test_tray.cpp +++ b/tests/unit/test_tray.cpp @@ -249,6 +249,14 @@ class TrayTest: public BaseTest { } } + // Native tray tooltips are displayed asynchronously by the desktop shell. + void WaitForTooltipReady() const { + for (int i = 0; i < 40; ++i) { + tray_loop(0); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + } + void WaitForNotificationReady() const { WaitForTrayReady(); #if defined(_WIN32) || defined(__APPLE__) @@ -478,10 +486,7 @@ TEST_F(TrayTest, TestTooltipDisplayOnHover) { WaitForTrayReady(); ASSERT_EQ(tray_position_mouse_over_icon(), 0); - for (int i = 0; i < 20; ++i) { - tray_loop(0); - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - } + WaitForTooltipReady(); EXPECT_TRUE(captureScreenshot("tray_tooltip_hover")); EXPECT_EQ(tray_restore_mouse_position(), 0); }