mirror of
https://github.com/LizardByte/Sunshine.git
synced 2026-08-07 10:20:46 +00:00
test(tray): add system tray tests (#4221)
This commit is contained in:
@@ -151,17 +151,20 @@ jobs:
|
|||||||
|
|
||||||
./appimagelint-x86_64.AppImage ./artifacts/sunshine.AppImage
|
./appimagelint-x86_64.AppImage ./artifacts/sunshine.AppImage
|
||||||
|
|
||||||
- name: Install test deps
|
- name: Install screenshot dependency
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update -y
|
sudo apt-get update -y
|
||||||
sudo apt-get install -y \
|
sudo apt-get install -y imagemagick
|
||||||
x11-xserver-utils \
|
|
||||||
xvfb
|
|
||||||
|
|
||||||
# clean apt cache
|
# clean apt cache
|
||||||
sudo apt-get clean
|
sudo apt-get clean
|
||||||
sudo rm -rf /var/lib/apt/lists/*
|
sudo rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
- name: Setup virtual desktop
|
||||||
|
uses: LizardByte/actions/actions/virtual_desktop@d0ae7f82215a479fe2b74f4088c53ee6460513dd # v2026.728.214955
|
||||||
|
with:
|
||||||
|
environment: mate
|
||||||
|
|
||||||
- name: Setup python
|
- name: Setup python
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||||
@@ -182,12 +185,17 @@ jobs:
|
|||||||
- name: Run tests
|
- name: Run tests
|
||||||
id: test
|
id: test
|
||||||
working-directory: build/tests
|
working-directory: build/tests
|
||||||
run: |
|
run: ./test_sunshine --gtest_color=yes --gtest_output=xml:test_results.xml
|
||||||
export DISPLAY=:1
|
|
||||||
Xvfb ${DISPLAY} -screen 0 1024x768x24 &
|
|
||||||
sleep 5 # give Xvfb time to start
|
|
||||||
|
|
||||||
./test_sunshine --gtest_color=yes --gtest_output=xml:test_results.xml
|
- name: Upload system tray screenshots
|
||||||
|
if: >-
|
||||||
|
always() &&
|
||||||
|
(steps.test.outcome == 'success' || steps.test.outcome == 'failure')
|
||||||
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
with:
|
||||||
|
name: sunshine-screenshots-${{ matrix.name }}
|
||||||
|
path: build/tests/screenshots
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Generate gcov report
|
- name: Generate gcov report
|
||||||
id: test_report
|
id: test_report
|
||||||
|
|||||||
@@ -94,6 +94,9 @@ jobs:
|
|||||||
qtbase \
|
qtbase \
|
||||||
qtsvg
|
qtsvg
|
||||||
|
|
||||||
|
- name: Configure macOS screen recording
|
||||||
|
uses: LizardByte/tray/.github/actions/configure-macos-screen-recording@master
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||||
with:
|
with:
|
||||||
@@ -182,6 +185,16 @@ jobs:
|
|||||||
working-directory: build/tests
|
working-directory: build/tests
|
||||||
run: ./test_sunshine --gtest_color=yes --gtest_output=xml:test_results.xml
|
run: ./test_sunshine --gtest_color=yes --gtest_output=xml:test_results.xml
|
||||||
|
|
||||||
|
- name: Upload system tray screenshots
|
||||||
|
if: >-
|
||||||
|
always() &&
|
||||||
|
(steps.test.outcome == 'success' || steps.test.outcome == 'failure')
|
||||||
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
with:
|
||||||
|
name: sunshine-screenshots-${{ matrix.name }}
|
||||||
|
path: build/tests/screenshots
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Generate gcov report
|
- name: Generate gcov report
|
||||||
id: test_report
|
id: test_report
|
||||||
# any except canceled or skipped
|
# any except canceled or skipped
|
||||||
|
|||||||
@@ -348,12 +348,34 @@ jobs:
|
|||||||
shell: msys2 {0}
|
shell: msys2 {0}
|
||||||
run: cat ./build/cpack_artifacts/_CPack_Packages/win64/WIX/wix.log || true
|
run: cat ./build/cpack_artifacts/_CPack_Packages/win64/WIX/wix.log || true
|
||||||
|
|
||||||
|
- name: Initialize tray icon
|
||||||
|
working-directory: build/tests
|
||||||
|
env:
|
||||||
|
SUNSHINE_CONFIGURE_TRAY_ICONS: '1'
|
||||||
|
run: >-
|
||||||
|
./test_sunshine.exe
|
||||||
|
--gtest_color=yes
|
||||||
|
--gtest_filter=SystemTrayTest.InitializesTrayForWorkflowConfiguration
|
||||||
|
|
||||||
|
- name: Configure Windows tray screenshots
|
||||||
|
uses: LizardByte/tray/.github/actions/configure-windows-tray-screenshots@master
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
id: test
|
id: test
|
||||||
shell: msys2 {0}
|
shell: msys2 {0}
|
||||||
working-directory: build/tests
|
working-directory: build/tests
|
||||||
run: ./test_sunshine.exe --gtest_color=yes --gtest_output=xml:test_results.xml
|
run: ./test_sunshine.exe --gtest_color=yes --gtest_output=xml:test_results.xml
|
||||||
|
|
||||||
|
- name: Upload system tray screenshots
|
||||||
|
if: >-
|
||||||
|
always() &&
|
||||||
|
(steps.test.outcome == 'success' || steps.test.outcome == 'failure')
|
||||||
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
with:
|
||||||
|
name: sunshine-screenshots-${{ matrix.name }}
|
||||||
|
path: build/tests/screenshots
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Generate gcov report
|
- name: Generate gcov report
|
||||||
id: test_report
|
id: test_report
|
||||||
# any except canceled or skipped
|
# any except canceled or skipped
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
name: Publish Screenshots
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: ["CI"]
|
||||||
|
types:
|
||||||
|
- completed
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
if: github.event.workflow_run.conclusion == 'success' || github.event.workflow_run.conclusion == 'failure'
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
statuses: write
|
||||||
|
uses: LizardByte/tray/.github/workflows/_publish-screenshots.yml@master
|
||||||
|
with:
|
||||||
|
artifact-name-prefix: sunshine-screenshots-
|
||||||
|
source-run-id: ${{ github.event.workflow_run.id }}
|
||||||
@@ -29,6 +29,13 @@ foreach(asset ${ALL_ASSETS}) # Copy assets to build directory, excluding the we
|
|||||||
DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/assets")
|
DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/assets")
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
# Copy the primary application icon into the built web assets for the system tray.
|
||||||
|
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/assets/web/images")
|
||||||
|
configure_file(
|
||||||
|
"${CMAKE_SOURCE_DIR}/sunshine.svg"
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/assets/web/images/logo-sunshine.svg"
|
||||||
|
COPYONLY)
|
||||||
|
|
||||||
# install built vite assets
|
# install built vite assets
|
||||||
install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/assets/web"
|
install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/assets/web"
|
||||||
DESTINATION "${SUNSHINE_ASSETS_DIR}")
|
DESTINATION "${SUNSHINE_ASSETS_DIR}")
|
||||||
|
|||||||
@@ -145,9 +145,6 @@ set(CPACK_RPM_PACKAGE_AUTOREQ ON)
|
|||||||
install(FILES "${CMAKE_SOURCE_DIR}/sunshine.svg"
|
install(FILES "${CMAKE_SOURCE_DIR}/sunshine.svg"
|
||||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/apps"
|
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/apps"
|
||||||
RENAME "${PROJECT_FQDN}.svg")
|
RENAME "${PROJECT_FQDN}.svg")
|
||||||
install(FILES "${CMAKE_SOURCE_DIR}/sunshine.svg"
|
|
||||||
DESTINATION "${SUNSHINE_ASSETS_DIR}/web/images"
|
|
||||||
RENAME "logo-sunshine.svg")
|
|
||||||
|
|
||||||
# tray icon
|
# tray icon
|
||||||
if(${SUNSHINE_TRAY} STREQUAL 1)
|
if(${SUNSHINE_TRAY} STREQUAL 1)
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ makedepends=(
|
|||||||
|
|
||||||
checkdepends=(
|
checkdepends=(
|
||||||
'gcovr'
|
'gcovr'
|
||||||
|
'imagemagick'
|
||||||
)
|
)
|
||||||
|
|
||||||
optdepends=(
|
optdepends=(
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ BuildRequires: systemd-udev
|
|||||||
BuildRequires: uv
|
BuildRequires: uv
|
||||||
%{?sysusers_requires_compat}
|
%{?sysusers_requires_compat}
|
||||||
# for unit tests
|
# for unit tests
|
||||||
|
BuildRequires: ImageMagick
|
||||||
BuildRequires: xorg-x11-server-Xvfb
|
BuildRequires: xorg-x11-server-Xvfb
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
@@ -99,6 +100,7 @@ BuildRequires: udev
|
|||||||
BuildRequires: vulkan-devel
|
BuildRequires: vulkan-devel
|
||||||
%endif
|
%endif
|
||||||
# for unit tests
|
# for unit tests
|
||||||
|
BuildRequires: ImageMagick
|
||||||
BuildRequires: xvfb-run
|
BuildRequires: xvfb-run
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ cleanup:
|
|||||||
|
|
||||||
modules:
|
modules:
|
||||||
# Test dependencies
|
# Test dependencies
|
||||||
|
- "modules/imagemagick.json"
|
||||||
- "modules/xvfb/xvfb.json"
|
- "modules/xvfb/xvfb.json"
|
||||||
|
|
||||||
# Build dependencies
|
# Build dependencies
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "imagemagick",
|
||||||
|
"cleanup": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"config-opts": [
|
||||||
|
"--disable-docs",
|
||||||
|
"--disable-static",
|
||||||
|
"--with-modules=no",
|
||||||
|
"--with-perl=no",
|
||||||
|
"--with-x"
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "archive",
|
||||||
|
"url": "https://download.imagemagick.org/archive/releases/ImageMagick-7.1.2-29.tar.xz",
|
||||||
|
"sha256": "bab096dc4b3089be4c0ebfdb90a232b85b7d982a99f722b30512fb243ea1e4ce"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -70,6 +70,7 @@ class Sunshine < Formula
|
|||||||
depends_on "gcovr" => [:build, :test]
|
depends_on "gcovr" => [:build, :test]
|
||||||
depends_on "lizardbyte/homebrew/#{CUDA_FORMULA}" => :build
|
depends_on "lizardbyte/homebrew/#{CUDA_FORMULA}" => :build
|
||||||
depends_on "python3" => :build
|
depends_on "python3" => :build
|
||||||
|
depends_on "imagemagick" => :test
|
||||||
depends_on "at-spi2-core"
|
depends_on "at-spi2-core"
|
||||||
depends_on "avahi"
|
depends_on "avahi"
|
||||||
depends_on "cairo"
|
depends_on "cairo"
|
||||||
|
|||||||
@@ -336,6 +336,7 @@ function add_debian_based_deps() {
|
|||||||
"git"
|
"git"
|
||||||
"glslang-tools" # Vulkan shader compiler
|
"glslang-tools" # Vulkan shader compiler
|
||||||
"graphviz"
|
"graphviz"
|
||||||
|
"imagemagick" # necessary for system tray screenshots in unit tests
|
||||||
"libcap-dev" # KMS
|
"libcap-dev" # KMS
|
||||||
"libcurl4-openssl-dev"
|
"libcurl4-openssl-dev"
|
||||||
"libdrm-dev" # KMS
|
"libdrm-dev" # KMS
|
||||||
@@ -441,6 +442,7 @@ function add_fedora_deps() {
|
|||||||
"git"
|
"git"
|
||||||
"glslc"
|
"glslc"
|
||||||
"graphviz"
|
"graphviz"
|
||||||
|
"ImageMagick" # necessary for system tray screenshots in unit tests
|
||||||
"libappstream-glib"
|
"libappstream-glib"
|
||||||
"libcap-devel"
|
"libcap-devel"
|
||||||
"libcurl-devel"
|
"libcurl-devel"
|
||||||
|
|||||||
+40
-29
@@ -5,6 +5,27 @@
|
|||||||
// macros
|
// macros
|
||||||
#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1
|
#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @def TRAY_ICON
|
||||||
|
* @brief Path to the default system tray icon.
|
||||||
|
*/
|
||||||
|
#define TRAY_ICON WEB_DIR "images/logo-sunshine.svg"
|
||||||
|
/**
|
||||||
|
* @def TRAY_ICON_PLAYING
|
||||||
|
* @brief Path to the system tray icon used while streaming.
|
||||||
|
*/
|
||||||
|
#define TRAY_ICON_PLAYING WEB_DIR "images/sunshine-playing.svg"
|
||||||
|
/**
|
||||||
|
* @def TRAY_ICON_PAUSING
|
||||||
|
* @brief Path to the system tray icon used while streaming is paused.
|
||||||
|
*/
|
||||||
|
#define TRAY_ICON_PAUSING WEB_DIR "images/sunshine-pausing.svg"
|
||||||
|
/**
|
||||||
|
* @def TRAY_ICON_LOCKED
|
||||||
|
* @brief Path to the system tray icon used for pairing requests.
|
||||||
|
*/
|
||||||
|
#define TRAY_ICON_LOCKED WEB_DIR "images/sunshine-locked.svg"
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
/**
|
/**
|
||||||
* @def WIN32_LEAN_AND_MEAN
|
* @def WIN32_LEAN_AND_MEAN
|
||||||
@@ -13,36 +34,7 @@
|
|||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#include <accctrl.h>
|
#include <accctrl.h>
|
||||||
#include <aclapi.h>
|
#include <aclapi.h>
|
||||||
/**
|
|
||||||
* @def TRAY_ICON
|
|
||||||
* @brief Macro for TRAY ICON.
|
|
||||||
*/
|
|
||||||
#define TRAY_ICON WEB_DIR "images/sunshine.ico"
|
|
||||||
/**
|
|
||||||
* @def TRAY_ICON_PLAYING
|
|
||||||
* @brief Macro for TRAY ICON PLAYING.
|
|
||||||
*/
|
|
||||||
#define TRAY_ICON_PLAYING WEB_DIR "images/sunshine-playing.ico"
|
|
||||||
/**
|
|
||||||
* @def TRAY_ICON_PAUSING
|
|
||||||
* @brief Macro for TRAY ICON PAUSING.
|
|
||||||
*/
|
|
||||||
#define TRAY_ICON_PAUSING WEB_DIR "images/sunshine-pausing.ico"
|
|
||||||
/**
|
|
||||||
* @def TRAY_ICON_LOCKED
|
|
||||||
* @brief Macro for TRAY ICON LOCKED.
|
|
||||||
*/
|
|
||||||
#define TRAY_ICON_LOCKED WEB_DIR "images/sunshine-locked.ico"
|
|
||||||
#elif defined(__linux__) || defined(linux) || defined(__linux) || defined(__FreeBSD__)
|
|
||||||
#define TRAY_ICON WEB_DIR "images/logo-sunshine.svg"
|
|
||||||
#define TRAY_ICON_PLAYING WEB_DIR "images/sunshine-playing.svg"
|
|
||||||
#define TRAY_ICON_PAUSING WEB_DIR "images/sunshine-pausing.svg"
|
|
||||||
#define TRAY_ICON_LOCKED WEB_DIR "images/sunshine-locked.svg"
|
|
||||||
#elif defined(__APPLE__) || defined(__MACH__)
|
#elif defined(__APPLE__) || defined(__MACH__)
|
||||||
#define TRAY_ICON WEB_DIR "images/logo-sunshine-16.png"
|
|
||||||
#define TRAY_ICON_PLAYING WEB_DIR "images/sunshine-playing-16.png"
|
|
||||||
#define TRAY_ICON_PAUSING WEB_DIR "images/sunshine-pausing-16.png"
|
|
||||||
#define TRAY_ICON_LOCKED WEB_DIR "images/sunshine-locked-16.png"
|
|
||||||
#include <CoreFoundation/CoreFoundation.h>
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
#include <dispatch/dispatch.h>
|
#include <dispatch/dispatch.h>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@@ -176,6 +168,25 @@ namespace system_tray {
|
|||||||
.allIconPaths = {TRAY_ICON, TRAY_ICON_LOCKED, TRAY_ICON_PLAYING, TRAY_ICON_PAUSING},
|
.allIconPaths = {TRAY_ICON, TRAY_ICON_LOCKED, TRAY_ICON_PLAYING, TRAY_ICON_PAUSING},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef SUNSHINE_TESTS
|
||||||
|
const struct tray &tray_data_for_testing() {
|
||||||
|
return tray;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool tray_initialized_for_testing() {
|
||||||
|
return tray_initialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset_tray_data_for_testing() {
|
||||||
|
tray.icon = tray.allIconPaths[0];
|
||||||
|
tray.tooltip = PROJECT_NAME;
|
||||||
|
tray.notification_icon = nullptr;
|
||||||
|
tray.notification_text = nullptr;
|
||||||
|
tray.notification_title = nullptr;
|
||||||
|
tray.notification_cb = nullptr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get resource path.
|
* @brief Get resource path.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -96,4 +96,25 @@ namespace system_tray {
|
|||||||
* @return 0 if initialization was successful, non-zero otherwise.
|
* @return 0 if initialization was successful, non-zero otherwise.
|
||||||
*/
|
*/
|
||||||
int init_tray_threaded();
|
int init_tray_threaded();
|
||||||
|
|
||||||
|
#ifdef SUNSHINE_TESTS
|
||||||
|
/**
|
||||||
|
* @brief Get the tray data used by the system tray implementation.
|
||||||
|
*
|
||||||
|
* @return Read-only tray data for unit-test assertions.
|
||||||
|
*/
|
||||||
|
const struct tray &tray_data_for_testing();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check whether the system tray is initialized.
|
||||||
|
*
|
||||||
|
* @return true if the system tray is initialized; otherwise, false.
|
||||||
|
*/
|
||||||
|
bool tray_initialized_for_testing();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Restore the persistent tray data to its initial state between tests.
|
||||||
|
*/
|
||||||
|
void reset_tray_data_for_testing();
|
||||||
|
#endif
|
||||||
} // namespace system_tray
|
} // namespace system_tray
|
||||||
|
|||||||
@@ -110,6 +110,58 @@ endif()
|
|||||||
file(GLOB_RECURSE TEST_SOURCES CONFIGURE_DEPENDS
|
file(GLOB_RECURSE TEST_SOURCES CONFIGURE_DEPENDS
|
||||||
${CMAKE_SOURCE_DIR}/tests/*.h
|
${CMAKE_SOURCE_DIR}/tests/*.h
|
||||||
${CMAKE_SOURCE_DIR}/tests/*.cpp)
|
${CMAKE_SOURCE_DIR}/tests/*.cpp)
|
||||||
|
if(SUNSHINE_ENABLE_TRAY AND (WIN32 OR APPLE OR CMAKE_SYSTEM_NAME STREQUAL "Linux"))
|
||||||
|
set(SUNSHINE_TRAY_TEST_ICON_SOURCE_DIR
|
||||||
|
"${CMAKE_SOURCE_DIR}/src_assets/common/assets/web/public/images")
|
||||||
|
set(SUNSHINE_TRAY_TEST_ICON_DESTINATION_DIR
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/test_assets/web/images")
|
||||||
|
file(MAKE_DIRECTORY "${SUNSHINE_TRAY_TEST_ICON_DESTINATION_DIR}")
|
||||||
|
|
||||||
|
configure_file(
|
||||||
|
"${CMAKE_SOURCE_DIR}/sunshine.svg"
|
||||||
|
"${SUNSHINE_TRAY_TEST_ICON_DESTINATION_DIR}/logo-sunshine.svg"
|
||||||
|
COPYONLY)
|
||||||
|
|
||||||
|
set(SUNSHINE_TRAY_TEST_ICONS
|
||||||
|
sunshine-locked.svg
|
||||||
|
sunshine-playing.svg
|
||||||
|
sunshine-pausing.svg)
|
||||||
|
|
||||||
|
foreach(icon IN LISTS SUNSHINE_TRAY_TEST_ICONS)
|
||||||
|
configure_file(
|
||||||
|
"${SUNSHINE_TRAY_TEST_ICON_SOURCE_DIR}/${icon}"
|
||||||
|
"${SUNSHINE_TRAY_TEST_ICON_DESTINATION_DIR}/${icon}"
|
||||||
|
COPYONLY)
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
add_library(sunshine_tray_test_helpers STATIC
|
||||||
|
${CMAKE_SOURCE_DIR}/third-party/tray/tests/notification_utils.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/third-party/tray/tests/screenshot_utils.cpp)
|
||||||
|
set_target_properties(sunshine_tray_test_helpers PROPERTIES
|
||||||
|
CXX_STANDARD 17
|
||||||
|
CXX_STANDARD_REQUIRED ON)
|
||||||
|
target_include_directories(sunshine_tray_test_helpers PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/third-party/tray)
|
||||||
|
target_link_libraries(sunshine_tray_test_helpers PRIVATE
|
||||||
|
lizardbyte::common)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
target_link_libraries(sunshine_tray_test_helpers PRIVATE
|
||||||
|
gdi32
|
||||||
|
gdiplus)
|
||||||
|
else()
|
||||||
|
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Gui)
|
||||||
|
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Gui)
|
||||||
|
set(SUNSHINE_TRAY_TEST_QT_GUI_TARGET Qt${QT_VERSION_MAJOR}::Gui)
|
||||||
|
|
||||||
|
target_link_libraries(sunshine_tray_test_helpers PRIVATE
|
||||||
|
${SUNSHINE_TRAY_TEST_QT_GUI_TARGET})
|
||||||
|
if(APPLE)
|
||||||
|
target_link_libraries(sunshine_tray_test_helpers PRIVATE
|
||||||
|
"-framework Cocoa")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
# Add macOS-specific test files only when building tests for macOS
|
# Add macOS-specific test files only when building tests for macOS
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
file(GLOB_RECURSE MACOS_TEST_SOURCES CONFIGURE_DEPENDS
|
file(GLOB_RECURSE MACOS_TEST_SOURCES CONFIGURE_DEPENDS
|
||||||
@@ -173,6 +225,15 @@ set(TEST_LINK_LIBRARIES
|
|||||||
${PLATFORM_LIBRARIES}
|
${PLATFORM_LIBRARIES}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(SUNSHINE_ENABLE_TRAY AND (WIN32 OR APPLE OR CMAKE_SYSTEM_NAME STREQUAL "Linux"))
|
||||||
|
list(APPEND TEST_LINK_LIBRARIES
|
||||||
|
sunshine_tray_test_helpers)
|
||||||
|
if(SUNSHINE_TRAY_TEST_QT_GUI_TARGET)
|
||||||
|
list(APPEND TEST_LINK_LIBRARIES
|
||||||
|
${SUNSHINE_TRAY_TEST_QT_GUI_TARGET})
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
# Add the specific libgcov library if found
|
# Add the specific libgcov library if found
|
||||||
if(GCOV_LINK_LIBRARY)
|
if(GCOV_LINK_LIBRARY)
|
||||||
list(APPEND TEST_LINK_LIBRARIES ${GCOV_LINK_LIBRARY})
|
list(APPEND TEST_LINK_LIBRARIES ${GCOV_LINK_LIBRARY})
|
||||||
|
|||||||
@@ -0,0 +1,421 @@
|
|||||||
|
/**
|
||||||
|
* @file tests/unit/test_system_tray.cpp
|
||||||
|
* @brief Tests for Sunshine's system tray integration.
|
||||||
|
*/
|
||||||
|
#include "../tests_common.h"
|
||||||
|
|
||||||
|
// standard includes
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
// Only test the system tray if it is enabled.
|
||||||
|
#if defined(SUNSHINE_TRAY) && SUNSHINE_TRAY >= 1
|
||||||
|
|
||||||
|
// lib includes
|
||||||
|
#include <tray.h>
|
||||||
|
|
||||||
|
// local includes
|
||||||
|
#include <src/system_tray.h>
|
||||||
|
|
||||||
|
#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
|
||||||
|
// lib includes
|
||||||
|
#include <lizardbyte/common/env.h>
|
||||||
|
|
||||||
|
// test includes
|
||||||
|
#include "third-party/tray/tests/notification_utils.h"
|
||||||
|
#include "third-party/tray/tests/screenshot_utils.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
/**
|
||||||
|
* @brief Process pending Qt tray events without blocking.
|
||||||
|
*
|
||||||
|
* @param iterations Maximum number of event iterations to process.
|
||||||
|
* @param delay Delay between event iterations.
|
||||||
|
*/
|
||||||
|
void pump_tray_events(const int iterations = 100, const std::chrono::milliseconds delay = 5ms) {
|
||||||
|
for (int i = 0; i < iterations; ++i) {
|
||||||
|
if (tray_loop(0) != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Wait for the threaded tray worker to finish initialization.
|
||||||
|
*
|
||||||
|
* @param timeout Maximum time to wait.
|
||||||
|
* @return true if the tray initialized before the timeout; otherwise, false.
|
||||||
|
*/
|
||||||
|
bool wait_for_tray_initialization(const std::chrono::milliseconds timeout) {
|
||||||
|
const auto deadline = std::chrono::steady_clock::now() + timeout;
|
||||||
|
while (std::chrono::steady_clock::now() < deadline) {
|
||||||
|
if (system_tray::tray_initialized_for_testing()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(10ms);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Verify the persistent menu exposed by Sunshine.
|
||||||
|
*/
|
||||||
|
void verify_menu() {
|
||||||
|
const auto &tray_data = system_tray::tray_data_for_testing();
|
||||||
|
ASSERT_NE(tray_data.menu, nullptr);
|
||||||
|
|
||||||
|
EXPECT_STREQ(tray_data.menu[0].text, "Open Sunshine");
|
||||||
|
EXPECT_NE(tray_data.menu[0].cb, nullptr);
|
||||||
|
EXPECT_STREQ(tray_data.menu[1].text, "-");
|
||||||
|
EXPECT_EQ(tray_data.menu[1].cb, nullptr);
|
||||||
|
EXPECT_STREQ(tray_data.menu[2].text, "Donate");
|
||||||
|
ASSERT_NE(tray_data.menu[2].submenu, nullptr);
|
||||||
|
EXPECT_STREQ(tray_data.menu[2].submenu[0].text, "GitHub Sponsors");
|
||||||
|
EXPECT_STREQ(tray_data.menu[2].submenu[1].text, "Patreon");
|
||||||
|
EXPECT_STREQ(tray_data.menu[2].submenu[2].text, "PayPal");
|
||||||
|
EXPECT_EQ(tray_data.menu[2].submenu[3].text, nullptr);
|
||||||
|
EXPECT_STREQ(tray_data.menu[3].text, "-");
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
EXPECT_STREQ(tray_data.menu[4].text, "Reset Display Device Config");
|
||||||
|
EXPECT_NE(tray_data.menu[4].cb, nullptr);
|
||||||
|
EXPECT_STREQ(tray_data.menu[5].text, "Restart");
|
||||||
|
EXPECT_NE(tray_data.menu[5].cb, nullptr);
|
||||||
|
EXPECT_STREQ(tray_data.menu[6].text, "Quit");
|
||||||
|
EXPECT_NE(tray_data.menu[6].cb, nullptr);
|
||||||
|
EXPECT_EQ(tray_data.menu[7].text, nullptr);
|
||||||
|
#else
|
||||||
|
EXPECT_STREQ(tray_data.menu[4].text, "Restart");
|
||||||
|
EXPECT_NE(tray_data.menu[4].cb, nullptr);
|
||||||
|
EXPECT_STREQ(tray_data.menu[5].text, "Quit");
|
||||||
|
EXPECT_NE(tray_data.menu[5].cb, nullptr);
|
||||||
|
EXPECT_EQ(tray_data.menu[6].text, nullptr);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Verify the current user-visible tray state.
|
||||||
|
*
|
||||||
|
* @param icon_index Index of the expected tray icon.
|
||||||
|
* @param tooltip Expected tray tooltip.
|
||||||
|
* @param notification_title Expected notification title, or nullptr when none is expected.
|
||||||
|
* @param notification_text Expected notification text, or nullptr when none is expected.
|
||||||
|
* @param notification_icon_index Index of the expected notification icon, or no value when none is expected.
|
||||||
|
* @param expects_notification_callback Whether a notification callback is expected.
|
||||||
|
*/
|
||||||
|
void verify_state(
|
||||||
|
const std::size_t icon_index,
|
||||||
|
const char *tooltip,
|
||||||
|
const char *notification_title,
|
||||||
|
const char *notification_text,
|
||||||
|
const std::optional<std::size_t> notification_icon_index,
|
||||||
|
const bool expects_notification_callback
|
||||||
|
) {
|
||||||
|
const auto &tray_data = system_tray::tray_data_for_testing();
|
||||||
|
ASSERT_GE(tray_data.iconPathCount, 4);
|
||||||
|
|
||||||
|
EXPECT_STREQ(tray_data.icon, tray_data.allIconPaths[icon_index]);
|
||||||
|
EXPECT_STREQ(tray_data.tooltip, tooltip);
|
||||||
|
EXPECT_STREQ(tray_data.notification_title, notification_title);
|
||||||
|
EXPECT_STREQ(tray_data.notification_text, notification_text);
|
||||||
|
if (notification_icon_index.has_value()) {
|
||||||
|
EXPECT_STREQ(tray_data.notification_icon, tray_data.allIconPaths[*notification_icon_index]);
|
||||||
|
} else {
|
||||||
|
EXPECT_EQ(tray_data.notification_icon, nullptr);
|
||||||
|
}
|
||||||
|
if (expects_notification_callback) {
|
||||||
|
EXPECT_NE(tray_data.notification_cb, nullptr);
|
||||||
|
} else {
|
||||||
|
EXPECT_EQ(tray_data.notification_cb, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Verify every user-visible tray state transition.
|
||||||
|
*/
|
||||||
|
#ifndef _WIN32
|
||||||
|
void verify_state_transitions() {
|
||||||
|
verify_state(0, PROJECT_NAME, nullptr, nullptr, std::nullopt, false);
|
||||||
|
|
||||||
|
system_tray::update_tray_playing("Moonlight");
|
||||||
|
verify_state(2, "Streaming started for Moonlight", "Stream Started", "Streaming started for Moonlight", 2, false);
|
||||||
|
|
||||||
|
system_tray::update_tray_pausing("Moonlight");
|
||||||
|
verify_state(3, "Streaming paused for Moonlight", "Stream Paused", "Streaming paused for Moonlight", 3, false);
|
||||||
|
|
||||||
|
system_tray::update_tray_stopped("Moonlight");
|
||||||
|
verify_state(0, PROJECT_NAME, "Application Stopped", "Application Moonlight successfully stopped", 0, false);
|
||||||
|
|
||||||
|
system_tray::update_tray_require_pin();
|
||||||
|
verify_state(0, PROJECT_NAME, "Incoming Pairing Request", "Click here to complete the pairing process", 1, true);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Fixture that resets the process-wide tray state between tests.
|
||||||
|
*/
|
||||||
|
class SystemTrayTest: public testing::Test {
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* @brief Reset any tray state left by an earlier test.
|
||||||
|
*/
|
||||||
|
void SetUp() override {
|
||||||
|
EXPECT_EQ(system_tray::end_tray(), 0);
|
||||||
|
#ifndef _WIN32
|
||||||
|
std::ignore = tray_loop(0);
|
||||||
|
std::ignore = tray_restore_mouse_position();
|
||||||
|
#endif
|
||||||
|
system_tray::reset_tray_data_for_testing();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Shut down and reset the tray after a test.
|
||||||
|
*/
|
||||||
|
void TearDown() override {
|
||||||
|
#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
|
||||||
|
dismissNativeNotifications();
|
||||||
|
#endif
|
||||||
|
EXPECT_EQ(system_tray::end_tray(), 0);
|
||||||
|
#ifdef _WIN32
|
||||||
|
std::this_thread::sleep_for(250ms);
|
||||||
|
#endif
|
||||||
|
#ifndef _WIN32
|
||||||
|
std::ignore = tray_loop(0);
|
||||||
|
std::ignore = tray_restore_mouse_position();
|
||||||
|
#endif
|
||||||
|
system_tray::reset_tray_data_for_testing();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize the tray and process its initial events.
|
||||||
|
*
|
||||||
|
* @return The result returned by system_tray::init_tray().
|
||||||
|
*/
|
||||||
|
int initialize_tray() const {
|
||||||
|
const int result = system_tray::init_tray();
|
||||||
|
#ifndef _WIN32
|
||||||
|
if (result == 0) {
|
||||||
|
pump_tray_events();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(SystemTrayTest, UpdatesAreIgnoredBeforeInitialization) {
|
||||||
|
EXPECT_FALSE(system_tray::tray_initialized_for_testing());
|
||||||
|
EXPECT_EQ(system_tray::process_tray_events(), 1);
|
||||||
|
|
||||||
|
system_tray::update_tray_playing("Moonlight");
|
||||||
|
system_tray::update_tray_pausing("Moonlight");
|
||||||
|
system_tray::update_tray_stopped("Moonlight");
|
||||||
|
system_tray::update_tray_require_pin();
|
||||||
|
|
||||||
|
const auto &tray_data = system_tray::tray_data_for_testing();
|
||||||
|
EXPECT_STREQ(tray_data.icon, tray_data.allIconPaths[0]);
|
||||||
|
EXPECT_STREQ(tray_data.tooltip, PROJECT_NAME);
|
||||||
|
EXPECT_EQ(tray_data.notification_title, nullptr);
|
||||||
|
EXPECT_EQ(tray_data.notification_text, nullptr);
|
||||||
|
EXPECT_EQ(tray_data.notification_icon, nullptr);
|
||||||
|
EXPECT_EQ(tray_data.notification_cb, nullptr);
|
||||||
|
EXPECT_EQ(system_tray::end_tray(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
TEST_F(SystemTrayTest, LifecycleMenuAndStateTransitions) {
|
||||||
|
if (const int result = initialize_tray(); result != 0) {
|
||||||
|
GTEST_SKIP() << "System tray is unavailable in this environment (code " << result << ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_TRUE(system_tray::tray_initialized_for_testing());
|
||||||
|
verify_menu();
|
||||||
|
verify_state_transitions();
|
||||||
|
|
||||||
|
std::jthread exit_thread([]() {
|
||||||
|
std::this_thread::sleep_for(100ms);
|
||||||
|
std::ignore = system_tray::end_tray();
|
||||||
|
});
|
||||||
|
EXPECT_NE(system_tray::process_tray_events(), 0);
|
||||||
|
exit_thread.join();
|
||||||
|
EXPECT_FALSE(system_tray::tray_initialized_for_testing());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
TEST_F(SystemTrayTest, InitializesTrayForWorkflowConfiguration) {
|
||||||
|
if (std::getenv("SUNSHINE_CONFIGURE_TRAY_ICONS") == nullptr) {
|
||||||
|
GTEST_SKIP() << "Only required while configuring Windows runner tray icon visibility";
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_EQ(system_tray::init_tray_threaded(), 0);
|
||||||
|
ASSERT_TRUE(wait_for_tray_initialization(10s));
|
||||||
|
verify_menu();
|
||||||
|
EXPECT_EQ(system_tray::end_tray(), 0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
|
||||||
|
/**
|
||||||
|
* @brief Fixture that stores visual tray evidence in the test output directory.
|
||||||
|
*/
|
||||||
|
class SystemTrayVisualTest: public SystemTrayTest {
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* @brief Initialize a clean screenshot output directory for this test suite.
|
||||||
|
*/
|
||||||
|
static void SetUpTestSuite() {
|
||||||
|
const std::filesystem::path test_binary_dir {SUNSHINE_TEST_BIN_DIR};
|
||||||
|
std::error_code remove_error;
|
||||||
|
std::filesystem::remove_all(test_binary_dir / "screenshots", remove_error);
|
||||||
|
screenshot::initialize(test_binary_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Wait for a tray notification to be rendered.
|
||||||
|
*/
|
||||||
|
static void wait_for_notification() {
|
||||||
|
#ifndef _WIN32
|
||||||
|
pump_tray_events();
|
||||||
|
#endif
|
||||||
|
#if defined(_WIN32) || defined(__APPLE__)
|
||||||
|
if (lizardbyte::common::is_github_actions()) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
std::this_thread::sleep_for(2s);
|
||||||
|
#else
|
||||||
|
pump_tray_events(40, 50ms);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
std::this_thread::sleep_for(500ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Capture a screenshot and dismiss its native notification.
|
||||||
|
*
|
||||||
|
* @param name Screenshot filename without the PNG extension.
|
||||||
|
*/
|
||||||
|
static void capture_notification(const std::string &name) {
|
||||||
|
wait_for_notification();
|
||||||
|
EXPECT_TRUE(screenshot::capture(name));
|
||||||
|
waitForNativeNotificationTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize the tray on the platform's production execution path.
|
||||||
|
*
|
||||||
|
* @return Zero when initialization succeeds; otherwise, a non-zero error code.
|
||||||
|
*/
|
||||||
|
int initialize_visual_tray() const {
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (const int result = system_tray::init_tray_threaded(); result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return wait_for_tray_initialization(10s) ? 0 : 1;
|
||||||
|
#else
|
||||||
|
return initialize_tray();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(SystemTrayVisualTest, CapturesIconTooltipNotificationsAndMenu) {
|
||||||
|
std::string unavailable_reason;
|
||||||
|
if (!screenshot::is_available(&unavailable_reason)) {
|
||||||
|
GTEST_SKIP() << "Screenshot tooling is unavailable: " << unavailable_reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &tray_data = system_tray::tray_data_for_testing();
|
||||||
|
ASSERT_EQ(tray_data.iconPathCount, 4);
|
||||||
|
for (int icon_index = 0; icon_index < tray_data.iconPathCount; ++icon_index) {
|
||||||
|
ASSERT_NE(tray_data.allIconPaths[icon_index], nullptr);
|
||||||
|
ASSERT_TRUE(std::filesystem::is_regular_file(tray_data.allIconPaths[icon_index]))
|
||||||
|
<< "Missing system tray test icon: " << tray_data.allIconPaths[icon_index];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const int result = initialize_visual_tray(); result != 0) {
|
||||||
|
GTEST_SKIP() << "System tray is unavailable in this environment (code " << result << ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_menu();
|
||||||
|
verify_state(0, PROJECT_NAME, nullptr, nullptr, std::nullopt, false);
|
||||||
|
EXPECT_TRUE(screenshot::capture("sunshine_tray_initial"));
|
||||||
|
|
||||||
|
const int tooltip_position_result = tray_position_mouse_over_icon();
|
||||||
|
if (lizardbyte::common::is_github_actions()) {
|
||||||
|
ASSERT_EQ(tooltip_position_result, 0);
|
||||||
|
}
|
||||||
|
if (tooltip_position_result == 0) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
std::this_thread::sleep_for(1s);
|
||||||
|
#else
|
||||||
|
pump_tray_events(20, 50ms);
|
||||||
|
#endif
|
||||||
|
EXPECT_TRUE(screenshot::capture("sunshine_tray_tooltip"));
|
||||||
|
EXPECT_EQ(tray_restore_mouse_position(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
dismissNativeNotifications();
|
||||||
|
system_tray::update_tray_playing("Moonlight");
|
||||||
|
verify_state(2, "Streaming started for Moonlight", "Stream Started", "Streaming started for Moonlight", 2, false);
|
||||||
|
capture_notification("sunshine_tray_streaming");
|
||||||
|
system_tray::update_tray_pausing("Moonlight");
|
||||||
|
verify_state(3, "Streaming paused for Moonlight", "Stream Paused", "Streaming paused for Moonlight", 3, false);
|
||||||
|
capture_notification("sunshine_tray_paused");
|
||||||
|
system_tray::update_tray_stopped("Moonlight");
|
||||||
|
verify_state(0, PROJECT_NAME, "Application Stopped", "Application Moonlight successfully stopped", 0, false);
|
||||||
|
capture_notification("sunshine_tray_stopped");
|
||||||
|
system_tray::update_tray_require_pin();
|
||||||
|
verify_state(0, PROJECT_NAME, "Incoming Pairing Request", "Click here to complete the pairing process", 1, true);
|
||||||
|
capture_notification("sunshine_tray_pairing_request");
|
||||||
|
|
||||||
|
int menu_position_result = -1;
|
||||||
|
if (lizardbyte::common::is_github_actions()) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
std::this_thread::sleep_for(500ms);
|
||||||
|
#else
|
||||||
|
pump_tray_events();
|
||||||
|
#endif
|
||||||
|
menu_position_result = tray_position_mouse_over_icon();
|
||||||
|
ASSERT_EQ(menu_position_result, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::atomic_bool capture_result {false};
|
||||||
|
std::jthread capture_thread([&capture_result]() {
|
||||||
|
capture_result.store(screenshot::capture("sunshine_tray_menu"), std::memory_order_release);
|
||||||
|
std::ignore = system_tray::end_tray();
|
||||||
|
});
|
||||||
|
|
||||||
|
tray_show_menu();
|
||||||
|
if (menu_position_result == 0) {
|
||||||
|
EXPECT_EQ(tray_restore_mouse_position(), 0);
|
||||||
|
}
|
||||||
|
#ifdef _WIN32
|
||||||
|
capture_thread.join();
|
||||||
|
#else
|
||||||
|
EXPECT_NE(system_tray::process_tray_events(), 0);
|
||||||
|
capture_thread.join();
|
||||||
|
#endif
|
||||||
|
EXPECT_TRUE(capture_result.load(std::memory_order_acquire));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
TEST(SystemTrayDisabled, ReportsDisabledBuild) {
|
||||||
|
GTEST_SKIP() << "System tray support is disabled in this build";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user