Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,14 @@ jobs:
build-essential \
cmake \
${{ matrix.appindicator }} \
dbus-x11 \
libglib2.0-dev \
libnotify-dev \
ninja-build \
xfce4-panel \
openbox \
imagemagick \
gnome-screenshot \
xvfb

- name: Setup Dependencies macOS
Expand All @@ -67,6 +72,7 @@ jobs:
cmake \
doxygen \
graphviz \
imagemagick \
ninja \
node

Expand All @@ -81,6 +87,7 @@ jobs:
mingw-w64-ucrt-x86_64-binutils
mingw-w64-ucrt-x86_64-cmake
mingw-w64-ucrt-x86_64-graphviz
mingw-w64-ucrt-x86_64-imagemagick
mingw-w64-ucrt-x86_64-ninja
mingw-w64-ucrt-x86_64-nodejs
mingw-w64-ucrt-x86_64-toolchain
Expand Down Expand Up @@ -132,10 +139,36 @@ jobs:
run: |
if [ "${{ runner.os }}" = "Linux" ]; then
export DISPLAY=:1
Xvfb ${DISPLAY} -screen 0 1024x768x24 &
Xvfb ${DISPLAY} -screen 0 1920x1080x24 &
XVFB_PID=$!
sleep 2
dbus-run-session -- bash -c '
openbox --config-file /etc/xdg/openbox/rc.xml &
WM_PID=$!
xfce4-panel --disable-wm-check &
PANEL_PID=$!
sleep 8
./test_tray --gtest_color=yes --gtest_output=xml:test_results.xml
status=$?
kill ${PANEL_PID} ${WM_PID} >/dev/null 2>&1 || true
exit ${status}
'
status=$?
kill ${XVFB_PID} >/dev/null 2>&1 || true
exit ${status}
else
./test_tray --gtest_color=yes --gtest_output=xml:test_results.xml
fi

./test_tray --gtest_color=yes --gtest_output=xml:test_results.xml
- name: Upload screenshots
if: >-
always() &&
(steps.test.outcome == 'success' || steps.test.outcome == 'failure')
uses: actions/upload-artifact@v6
with:
name: tray-screenshots-${{ runner.os }}${{ matrix.appindicator && format('-{0}', matrix.appindicator) || '' }}
path: build/tests/screenshots
if-no-files-found: error

- name: Generate gcov report
id: test_report
Expand Down
24 changes: 22 additions & 2 deletions src/tray_darwin.m
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,26 @@ - (IBAction)menuCallback:(id)sender {
static NSApplication *app;
static NSStatusBar *statusBar;
static NSStatusItem *statusItem;
static int loopResult = 0;

#define QUIT_EVENT_SUBTYPE 0x0DED ///< NSEvent subtype used to signal exit.

static void drain_quit_events(void) {
while (YES) {
NSEvent *event = [app nextEventMatchingMask:ULONG_MAX
untilDate:[NSDate distantPast]
inMode:[NSString stringWithUTF8String:"kCFRunLoopDefaultMode"]
dequeue:TRUE];
if (event == nil) {
break;
}
if (event.type == NSEventTypeApplicationDefined && event.subtype == QUIT_EVENT_SUBTYPE) {
continue;
}
[app sendEvent:event];
}
}

static NSMenu *_tray_menu(struct tray_menu *m) {
NSMenu *menu = [[NSMenu alloc] init];
[menu setAutoenablesItems:FALSE];
Expand All @@ -67,13 +84,15 @@ - (IBAction)menuCallback:(id)sender {
}

int tray_init(struct tray *tray) {
loopResult = 0;
AppDelegate *delegate = [[AppDelegate alloc] init];
app = [NSApplication sharedApplication];
[app setDelegate:delegate];
statusBar = [NSStatusBar systemStatusBar];
statusItem = [statusBar statusItemWithLength:NSVariableStatusItemLength];
tray_update(tray);
[app activateIgnoringOtherApps:TRUE];
drain_quit_events();
return 0;
}

Expand All @@ -85,12 +104,13 @@ int tray_loop(int blocking) {
dequeue:TRUE];
if (event) {
if (event.type == NSEventTypeApplicationDefined && event.subtype == QUIT_EVENT_SUBTYPE) {
return -1;
loopResult = -1;
return loopResult;
}

[app sendEvent:event];
}
return 0;
return loopResult;
}

void tray_update(struct tray *tray) {
Expand Down
1 change: 1 addition & 0 deletions src/tray_linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ int tray_init(struct tray *tray) {
if (gtk_init_check(0, NULL) == FALSE) {
return -1;
}
loop_result = 0;
notify_init("tray-icon");
indicator = app_indicator_new(TRAY_APPINDICATOR_ID, tray->icon, APP_INDICATOR_CATEGORY_APPLICATION_STATUS);
if (indicator == NULL || !IS_APP_INDICATOR(indicator)) {
Expand Down
9 changes: 9 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,17 @@ if (WIN32)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # cmake-lint: disable=C0103
endif ()

# extra libraries for tests
if (APPLE)
set(TEST_LIBS "-framework Cocoa")
elseif (WIN32)
set(TEST_LIBS gdi32 gdiplus)
endif()

file(GLOB_RECURSE TEST_SOURCES
${CMAKE_SOURCE_DIR}/tests/conftest.cpp
${CMAKE_SOURCE_DIR}/tests/utils.cpp
${CMAKE_SOURCE_DIR}/tests/screenshot_utils.cpp
${CMAKE_SOURCE_DIR}/tests/test_*.cpp)

add_executable(${PROJECT_NAME}
Expand All @@ -29,6 +37,7 @@ add_executable(${PROJECT_NAME}
set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 17)
target_link_directories(${PROJECT_NAME} PRIVATE ${TRAY_EXTERNAL_DIRECTORIES})
target_link_libraries(${PROJECT_NAME}
${TEST_LIBS}
${TRAY_EXTERNAL_LIBRARIES}
gtest
gtest_main) # if we use this we don't need our own main function
Expand Down
55 changes: 54 additions & 1 deletion tests/conftest.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// standard includes
#include <array>
#include <filesystem>
#include <mutex>

// lib includes
#include <gtest/gtest.h>

// test includes
#include "tests/screenshot_utils.h"
#include "tests/utils.h"

// Undefine the original TEST macro
Expand Down Expand Up @@ -34,7 +36,8 @@ class BaseTest: public ::testing::Test {
BaseTest():
sbuf {nullptr},
pipe_stdout {nullptr},
pipe_stderr {nullptr} {
pipe_stderr {nullptr},
screenshotsReady {false} {
// intentionally empty
}

Expand All @@ -59,6 +62,8 @@ class BaseTest: public ::testing::Test {
testBinaryDir = std::filesystem::current_path();
}

initializeScreenshotsOnce();

sbuf = std::cout.rdbuf(); // save cout buffer (std::cout)
std::cout.rdbuf(cout_buffer.rdbuf()); // redirect cout to buffer (std::cout)
}
Expand Down Expand Up @@ -102,6 +107,19 @@ class BaseTest: public ::testing::Test {
std::streambuf *sbuf;
FILE *pipe_stdout;
FILE *pipe_stderr;
bool screenshotsReady;

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);
});
}

int exec(const char *cmd) {
std::array<char, 128> buffer {};
Expand All @@ -124,6 +142,41 @@ class BaseTest: public ::testing::Test {
}
return returnCode;
}

bool ensureScreenshotReady() {
if (screenshotsReady) {
return true;
}
std::string reason;
if (!screenshot::is_available(&reason)) {
screenshotUnavailableReason = reason;
return false;
}
auto root = screenshot::output_root();
if (root.empty()) {
screenshotUnavailableReason = "Screenshot output directory not initialized";
return false;
}
screenshotsReady = true;
return true;
}

bool captureScreenshot(const std::string &name) {
if (!screenshotsReady) {
return false;
}
bool ok = screenshot::capture(name);
if (!ok) {
std::cout << "Failed to capture screenshot: " << name << std::endl;
}
return ok;
}

std::filesystem::path screenshotsRoot() const {
return screenshot::output_root();
}

std::string screenshotUnavailableReason;
};

class LinuxTest: public BaseTest {
Expand Down
Loading
Loading