diff --git a/.codeql-prebuild-cpp-Linux.sh b/.codeql-prebuild-cpp-Linux.sh
index 800a89e..f88d64e 100644
--- a/.codeql-prebuild-cpp-Linux.sh
+++ b/.codeql-prebuild-cpp-Linux.sh
@@ -20,9 +20,12 @@ sudo rm -rf /var/lib/apt/lists/*
# build
mkdir -p build
-cd build || exit 1
-cmake -G Ninja ..
-ninja
+cmake \
+ -DBUILD_DOCS=OFF \
+ -B build \
+ -G Ninja \
+ -S .
+ninja -C build
# skip autobuild
echo "skip_autobuild=true" >> "$GITHUB_OUTPUT"
diff --git a/.codeql-prebuild-cpp-Windows.sh b/.codeql-prebuild-cpp-Windows.sh
index d32da0b..2406033 100644
--- a/.codeql-prebuild-cpp-Windows.sh
+++ b/.codeql-prebuild-cpp-Windows.sh
@@ -13,9 +13,12 @@ pacman --noconfirm -S \
# build
mkdir -p build
-cd build || exit 1
-cmake -G Ninja ..
-ninja
+cmake \
+ -DBUILD_DOCS=OFF \
+ -B build \
+ -G Ninja \
+ -S .
+ninja -C build
# skip autobuild
echo "skip_autobuild=true" >> "$GITHUB_OUTPUT"
diff --git a/.codeql-prebuild-cpp-macOS.sh b/.codeql-prebuild-cpp-macOS.sh
index 0f1aeeb..f9fbba1 100644
--- a/.codeql-prebuild-cpp-macOS.sh
+++ b/.codeql-prebuild-cpp-macOS.sh
@@ -8,9 +8,12 @@ brew install \
# build
mkdir -p build
-cd build || exit 1
-cmake -G Ninja ..
-ninja
+cmake \
+ -DBUILD_DOCS=OFF \
+ -B build \
+ -G Ninja \
+ -S .
+ninja -C build
# skip autobuild
echo "skip_autobuild=true" >> "$GITHUB_OUTPUT"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 38e4f8c..9eaa651 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -64,7 +64,10 @@ jobs:
run: |
brew install \
cmake \
- ninja
+ doxygen \
+ graphviz \
+ ninja \
+ node
- name: Setup Dependencies Windows
if: runner.os == 'Windows'
@@ -73,9 +76,12 @@ jobs:
msystem: ucrt64
update: true
install: >-
+ doxygen
mingw-w64-ucrt-x86_64-binutils
mingw-w64-ucrt-x86_64-cmake
+ mingw-w64-ucrt-x86_64-graphviz
mingw-w64-ucrt-x86_64-ninja
+ mingw-w64-ucrt-x86_64-nodejs
mingw-w64-ucrt-x86_64-toolchain
- name: Setup python
@@ -101,20 +107,32 @@ jobs:
- name: Build
run: |
mkdir -p build
- cd build
- cmake -DCMAKE_BUILD_TYPE:STRING=Debug -G Ninja ..
- ninja
+
+ if [ "${{ runner.os }}" = "Linux" ]; then
+ # Doxygen from Ubuntu is too old, need Doxygen >= 1.10
+ DOCS=OFF
+ else
+ DOCS=ON
+ fi
+
+ cmake \
+ -DBUILD_DOCS=${DOCS} \
+ -DCMAKE_BUILD_TYPE:STRING=Debug \
+ -B build \
+ -G Ninja \
+ -S .
+ ninja -C build
- name: Run tests
id: test
- working-directory: build
+ working-directory: build/tests
run: |
if [ "${{ runner.os }}" = "Linux" ]; then
export DISPLAY=:1
Xvfb ${DISPLAY} -screen 0 1024x768x24 &
fi
- ./tests/test_tray --gtest_color=yes
+ ./test_tray --gtest_color=yes
- name: Generate gcov report
# any except canceled or skipped
diff --git a/.gitignore b/.gitignore
index 5073c82..fd5b8a8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,3 +40,6 @@
# build directories
build/
cmake-*/
+
+# doxyconfig
+docs/*-doxyconfig*
diff --git a/.gitmodules b/.gitmodules
index 6cbf29c..e3099df 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,7 @@
+[submodule "third-party/doxyconfig"]
+ 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
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000..a2c132d
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,40 @@
+---
+# .readthedocs.yaml
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+version: 2
+
+build:
+ os: ubuntu-24.04
+ tools:
+ python: "miniconda-latest"
+ commands:
+ # because we are overriding the build commands, we need to setup the environment ourselves
+ - cat third-party/doxyconfig/environment.yml
+ - conda env create --quiet --name ${READTHEDOCS_VERSION} --file third-party/doxyconfig/environment.yml
+ - npm install "@fortawesome/fontawesome-free"
+ - mkdir -p ${READTHEDOCS_OUTPUT}html/assets/fontawesome/css
+ - mkdir -p ${READTHEDOCS_OUTPUT}html/assets/fontawesome/js
+ - cp node_modules/@fortawesome/fontawesome-free/css/all.min.css ${READTHEDOCS_OUTPUT}html/assets/fontawesome/css
+ - cp node_modules/@fortawesome/fontawesome-free/js/all.min.js ${READTHEDOCS_OUTPUT}html/assets/fontawesome/js
+ - cp -r node_modules/@fortawesome/fontawesome-free/webfonts ${READTHEDOCS_OUTPUT}html/assets/fontawesome/
+ - |
+ wget "https://raw.githubusercontent.com/LizardByte/.github/master/branding/logos/favicon.ico" \
+ -O ${READTHEDOCS_OUTPUT}lizardbyte.ico
+ - |
+ wget "https://raw.githubusercontent.com/LizardByte/.github/master/branding/logos/logo-128x128.png" \
+ -O ${READTHEDOCS_OUTPUT}lizardbyte.png
+ - cp ./third-party/doxyconfig/Doxyfile ./docs/Doxyfile-doxyconfig
+ - cp ./third-party/doxyconfig/header.html ./docs/header-doxyconfig.html
+ - cat ./docs/Doxyfile >> ./docs/Doxyfile-doxyconfig
+ - cd docs && doxygen Doxyfile-doxyconfig
+
+# using conda, we can get newer doxygen and graphviz than ubuntu provide
+# https://github.com/readthedocs/readthedocs.org/issues/8151#issuecomment-890359661
+conda:
+ environment: third-party/doxyconfig/environment.yml
+
+submodules:
+ include: all
+ recursive: true
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1df79fe..52567ba 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,8 +1,11 @@
+#
+# Project configuration
+#
cmake_minimum_required(VERSION 3.13 FATAL_ERROR) # target_link_directories
-
-project(tray
- LANGUAGES C
- DESCRIPTION "A cross-platform system tray library")
+project(tray VERSION 0.0.0
+ DESCRIPTION "A cross-platform system tray library"
+ HOMEPAGE_URL "https://app.lizardbyte.dev"
+ LANGUAGES C)
set(PROJECT_LICENSE "MIT")
@@ -11,11 +14,22 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
endif()
+# Add our custom CMake modules to the global path
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
-# options
+#
+# Project optional configuration
+#
+option(BUILD_DOCS "Build documentation" ON)
option(BUILD_TESTS "Build tests" ON)
+#
+# Documentation
+#
+if(BUILD_DOCS)
+ add_subdirectory(third-party/doxyconfig docs)
+endif()
+
# Generate 'compile_commands.json' for clang_complete
set(CMAKE_COLOR_MAKEFILE ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
diff --git a/README.md b/README.md
index 7359deb..5bf571f 100644
--- a/README.md
+++ b/README.md
@@ -1,56 +1,90 @@
-# Cross-platform Linux/macOS/Windows Tray
+# Overview
-[](https://codecov.io/gh/LizardByte/tray)
+[](https://github.com/LizardByte/tray/actions/workflows/ci.yml?query=branch%3Amaster)
+[](https://codecov.io/gh/LizardByte/tray)
+[](https://github.com/LizardByte/tray)
-
+## About
-
+Cross-platform, super tiny C99 implementation of a system tray icon with a popup menu and notifications.
-
+The code is C++ friendly and will compile fine in C++98 and up. This is a fork of
+[dmikushin/tray](https://github.com/dmikushin/tray) and is intended to add additional features required for our own
+[Sunshine](https://github.com/LizardByte/Sunshine) project.
-Cross-platform, super tiny C99 implementation of a system tray icon with a popup menu.
+This fork adds the following features:
-Works well on:
+- system tray notifications
+- support for both linux appindicator versions
+- unit tests
+- code coverage
+- refactored code, e.g. moved source code into the `src` directory
+- doxygen documentation, and readthedocs configuration
+
+## Screenshots
+
+
+
+- Linux
+- macOS
+- Windows
+
+
+
+## Supported platforms
* Linux/Gtk (libayatana-appindicator3 or libappindicator3)
* Windows XP or newer (shellapi.h)
* MacOS (Cocoa/AppKit)
-The code is C++ friendly and will compile fine in C++98 and up.
+## Prerequisites
-This fork is intended to bring together the [original work of Serge Zaitsev](https://github.com/zserge/tray) and the most interesting forks and PRs of respectable contributors:
+* CMake
+* [Ninja](https://ninja-build.org/), in order to have the same build commands on all platforms
-* [Only process messages coming from the tray window on Windows](https://github.com/zserge/tray/pull/18)
-* [Become C++-friendly](https://github.com/zserge/tray/pull/16)
-* [Fix all menu items have a check box](https://github.com/zserge/tray/pull/11)
-* [Add support for tooltip](https://github.com/zserge/tray/pull/11)
-* Darwin implementation translated from C to Objective C adapted from [@trevex fork](https://github.com/trevex/tray)
+### Linux Dependencies
-## Prerequisites
+
-* CMake
-* [Ninja](https://ninja-build.org/), in order to have the same build commands on all platforms
-* AppIndicator on Linux:
+- Arch
+ ```bash
+ sudo pacman -S libayatana-appindicator
+ ```
-```
-sudo apt install libappindicator3-dev
-```
+- Debian/Ubuntu
+ ```bash
+ sudo apt install libappindicator3-dev
+ ```
+
+- Fedora
+ ```bash
+ sudo dnf install libappindicator-gtk3-devel
+ ```
+
+
## Building
-```
-mkdir build
-cd build
-cmake -G Ninja ..
-ninja
+```bash
+mkdir -p build
+cmake -G Ninja -B build -S .
+ninja -C build
```
## Demo
Execute the `tray_example` application:
+```bash
+./build/tray_example
```
-./tray_example
+
+## Tests
+
+Execute the `tests` application:
+
+```bash
+./build/tests/test_tray
```
## API
@@ -91,4 +125,4 @@ array must have text field set to NULL.
## 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.
+so feel free to integrate it in your commercial products.
diff --git a/docs/Doxyfile b/docs/Doxyfile
new file mode 100644
index 0000000..77aaefb
--- /dev/null
+++ b/docs/Doxyfile
@@ -0,0 +1,39 @@
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+#
+# Note:
+#
+# Use doxygen to compare the used configuration file with the template
+# configuration file:
+# doxygen -x [configFile]
+# Use doxygen to compare the used configuration file with the template
+# configuration file without replacing the environment variables or CMake type
+# replacement variables:
+# doxygen -x_noenv [configFile]
+
+# 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_NAME = tray
+
+# project specific settings
+DOT_GRAPH_MAX_NODES = 50
+IMAGE_PATH = ../docs/images
+INCLUDE_PATH =
+
+# files and directories to process
+USE_MDFILE_AS_MAINPAGE = ../README.md
+INPUT = ../README.md \
+ ../third-party/doxyconfig/docs/source_code.md \
+ ../src
diff --git a/docs/images/screenshot_macosx.png b/docs/images/screenshot_macos.png
similarity index 100%
rename from docs/images/screenshot_macosx.png
rename to docs/images/screenshot_macos.png
diff --git a/src/example.c b/src/example.c
index 9d7958d..7b3f81a 100644
--- a/src/example.c
+++ b/src/example.c
@@ -1,8 +1,12 @@
+/**
+ * @file src/example.c
+ * @brief Example usage of the tray library.
+ */
#include
#include
#if defined (_WIN32) || defined (_WIN64)
-#define TRAY_WINAPI 1
+#define TRAY_WINAPI 1 ///< Use WinAPI.
#elif defined (__linux__) || defined (linux) || defined (__linux)
#define TRAY_APPINDICATOR 1
#elif defined (__APPLE__) || defined (__MACH__)
@@ -18,8 +22,8 @@
#define TRAY_ICON1 "icon.png"
#define TRAY_ICON2 "icon.png"
#elif TRAY_WINAPI
-#define TRAY_ICON1 "icon.ico"
-#define TRAY_ICON2 "icon.ico"
+#define TRAY_ICON1 "icon.ico" ///< Path to first icon.
+#define TRAY_ICON2 "icon.ico" ///< Path to second icon.
#endif
static struct tray tray;
@@ -92,6 +96,10 @@ static struct tray tray = {
{.text = NULL}},
};
+/**
+ * @brief Main entry point.
+ * @return 0 on success, 1 on error.
+ */
int main() {
if (tray_init(&tray) < 0) {
printf("failed to create tray\n");
diff --git a/src/tray.h b/src/tray.h
index c94854e..fb535f0 100644
--- a/src/tray.h
+++ b/src/tray.h
@@ -1,3 +1,7 @@
+/**
+ * @file src/tray.h
+ * @brief Definition of the tray API.
+ */
#ifndef TRAY_H
#define TRAY_H
@@ -5,41 +9,67 @@
extern "C" {
#endif
+/**
+ * @brief Tray menu item.
+ */
struct tray_menu;
+/**
+ * @brief Tray icon.
+ */
struct tray {
- const char *icon;
- const char *tooltip;
- const char *notification_icon;
- const char *notification_text;
- const char *notification_title;
- void (*notification_cb)();
- struct tray_menu *menu;
- const int iconPathCount;
- const char *allIconPaths[];
+ const char *icon; ///< Icon to display.
+ const char *tooltip; ///< Tooltip to display.
+ const char *notification_icon; ///< Icon to display in the notification.
+ const char *notification_text; ///< Text to display in the notification.
+ const char *notification_title; ///< Title to display in the notification.
+ void (*notification_cb)(); ///< Callback to invoke when the notification is clicked.
+ struct tray_menu *menu; ///< Menu items.
+ const int iconPathCount; ///< Number of icon paths.
+ const char *allIconPaths[]; ///< Array of icon paths.
};
+/**
+ * @brief Tray menu item.
+ */
struct tray_menu {
- const char *text;
- int disabled;
- int checked;
- int checkbox;
+ const char *text; ///< Text to display.
+ int disabled; ///< Whether the item is disabled.
+ int checked; ///< Whether the item is checked.
+ int checkbox; ///< Whether the item is a checkbox.
- void (*cb)(struct tray_menu *);
- void *context;
+ void (*cb)(struct tray_menu *); ///< Callback to invoke when the item is clicked.
+ void *context; ///< Context to pass to the callback.
- struct tray_menu *submenu;
+ struct tray_menu *submenu; ///< Submenu items.
};
+/**
+ * @brief Create tray icon.
+ * @param tray The tray to initialize.
+ * @return 0 on success, -1 on error.
+ */
int
tray_init(struct tray *tray);
+/**
+ * @brief Run one iteration of the UI loop.
+ * @param blocking Whether to block the call or not.
+ * @return 0 on success, -1 if tray_exit() was called.
+ */
int
tray_loop(int blocking);
+/**
+ * @brief Update the tray icon and menu.
+ * @param tray The tray to update.
+ */
void
tray_update(struct tray *tray);
+/**
+ * @brief Terminate UI loop.
+ */
void
tray_exit(void);
diff --git a/src/tray_darwin.m b/src/tray_darwin.m
index 9865bd6..b510e39 100644
--- a/src/tray_darwin.m
+++ b/src/tray_darwin.m
@@ -1,10 +1,29 @@
+/**
+ * @file src/tray_darwin.m
+ * @brief System tray implementation for macOS.
+ */
+// header include
#include "tray.h"
-#include
+
+// system includes
#include
+// lib includes
+#include
+
+/**
+ * @class AppDelegate
+ * @brief The application delegate that handles menu actions.
+ */
@interface AppDelegate: NSObject
+/**
+ * @brief Callback function for menu item actions.
+ * @param sender The object that sent the action message.
+ * @return void
+ */
- (IBAction)menuCallback:(id)sender;
@end
+
@implementation AppDelegate {
}
- (IBAction)menuCallback:(id)sender {
diff --git a/src/tray_linux.c b/src/tray_linux.c
index 7faa3a2..a85a6d5 100644
--- a/src/tray_linux.c
+++ b/src/tray_linux.c
@@ -1,18 +1,26 @@
+/**
+ * @file src/tray_linux.c
+ * @brief System tray implementation for Linux.
+ */
+// header include
#include "tray.h"
+
+// system includes
#include
#include
#include
+
+// lib includes
#ifdef TRAY_AYATANA_APPINDICATOR
#include
#elif TRAY_LEGACY_APPINDICATOR
#include
#endif
#ifndef IS_APP_INDICATOR
-#define IS_APP_INDICATOR APP_IS_INDICATOR
+#define IS_APP_INDICATOR APP_IS_INDICATOR ///< Define IS_APP_INDICATOR for app-indicator compatibility.
#endif
-
#include
-#define TRAY_APPINDICATOR_ID "tray-id"
+#define TRAY_APPINDICATOR_ID "tray-id" ///< Tray appindicator ID.
static bool async_update_pending = false;
static pthread_cond_t async_update_cv = PTHREAD_COND_INITIALIZER;
diff --git a/src/tray_windows.c b/src/tray_windows.c
index cf92da7..cbea316 100644
--- a/src/tray_windows.c
+++ b/src/tray_windows.c
@@ -1,22 +1,35 @@
+/**
+ * @file src/tray_windows.c
+ * @brief System tray implementation for Windows.
+ */
+// header include
+#include "tray.h"
+
+// system includes
#include
#include
-#include "tray.h"
-#define WM_TRAY_CALLBACK_MESSAGE (WM_USER + 1)
-#define WC_TRAY_CLASS_NAME "TRAY"
-#define ID_TRAY_FIRST 1000
+#define WM_TRAY_CALLBACK_MESSAGE (WM_USER + 1) ///< Tray callback message.
+#define WC_TRAY_CLASS_NAME "TRAY" ///< Tray window class name.
+#define ID_TRAY_FIRST 1000 ///< First tray identifier.
+/**
+ * @brief Icon information.
+ */
struct icon_info {
- const char *path;
- HICON icon;
- HICON large_icon;
- HICON notification_icon;
+ const char *path; ///< Path to the icon
+ HICON icon; ///< Regular icon
+ HICON large_icon; ///< Large icon
+ HICON notification_icon; ///< Notification icon
};
+/**
+ * @brief Icon type.
+ */
enum IconType {
- REGULAR = 1,
- LARGE,
- NOTIFICATION
+ REGULAR = 1, ///< Regular icon
+ LARGE, ///< Large icon
+ NOTIFICATION ///< Notification icon
};
static WNDCLASSEX wc;
@@ -108,6 +121,11 @@ static HMENU _tray_menu(struct tray_menu *m, UINT *id) {
return hmenu;
}
+/**
+ * @brief Create icon information.
+ * @param path Path to the icon.
+ * @return Icon information.
+ */
struct icon_info _create_icon_info(const char * path) {
struct icon_info info;
info.path = strdup(path);
@@ -121,6 +139,11 @@ struct icon_info _create_icon_info(const char * path) {
return info;
}
+/**
+ * @brief Initialize icon cache.
+ * @param paths Paths to the icons.
+ * @param count Number of paths.
+ */
void _init_icon_cache(const char ** paths, int count) {
icon_info_count = count;
icon_infos = malloc(sizeof(struct icon_info) * icon_info_count);
@@ -130,6 +153,9 @@ void _init_icon_cache(const char ** paths, int count) {
}
}
+/**
+ * @brief Destroy icon cache.
+ */
void _destroy_icon_cache() {
for (int i = 0; i < icon_info_count; ++i) {
DestroyIcon(icon_infos[i].icon);
@@ -143,6 +169,12 @@ void _destroy_icon_cache() {
icon_info_count = 0;
}
+/**
+ * @brief Fetch cached icon.
+ * @param icon_record Icon record.
+ * @param icon_type Icon type.
+ * @return Icon.
+ */
HICON _fetch_cached_icon(struct icon_info *icon_record, enum IconType icon_type) {
switch (icon_type) {
case REGULAR:
@@ -154,6 +186,12 @@ HICON _fetch_cached_icon(struct icon_info *icon_record, enum IconType icon_type)
}
}
+/**
+ * @brief Fetch icon.
+ * @param path Path to the icon.
+ * @param icon_type Icon type.
+ * @return Icon.
+ */
HICON _fetch_icon(const char * path, enum IconType icon_type) {
// Find a cached icon by path
for (int i = 0; i < icon_info_count; ++i) {
diff --git a/third-party/doxyconfig b/third-party/doxyconfig
new file mode 160000
index 0000000..671b494
--- /dev/null
+++ b/third-party/doxyconfig
@@ -0,0 +1 @@
+Subproject commit 671b494f3cbe8597a36d81869a864dc9fff497f4