Cross-platform, single header, super tiny C99 implementation of a system tray icon with a popup menu.
Works well on:
- Windows XP or newer (shellapi.h) ✅ Verified
- Linux/Gtk (libappindicator)
⚠️ Not verified - MacOS (Cocoa/AppKit)
⚠️ Not verified
There is also a stub implementation that returns errors on attempt to create a tray menu.
The library now includes automatic platform detection! You no longer need to manually define platform macros in most cases.
Simply include the header - the library will automatically detect your platform:
#include <iostream>
#include "tray.hpp"
// That's it! The library automatically detects:
// - Windows -> TRAY_WINAPI
// - macOS -> TRAY_APPKIT
// - Linux -> TRAY_APPINDICATORIf automatic detection doesn't work, you can manually define the platform:
For Windows:
#define TRAY_WINAPI
#include "tray.hpp"For Linux:
#define TRAY_APPINDICATOR
#include "tray.hpp"For Mac:
#define TRAY_APPKIT
#include "tray.hpp"#include <iostream>
#include <atomic>
#include "tray.hpp"
using namespace std;
// Global exit flag
static atomic<bool> should_exit{false};
// Forward declaration
void quit_cb(struct tray_menu* item);
// Menu items (must be static or global to persist)
static struct tray_menu menu_items[] = {
{"Toggle me", 0, 0, toggle_cb, nullptr, nullptr},
{"-", 0, 0, nullptr, nullptr, nullptr}, // Separator
{"Quit", 0, 0, quit_cb, nullptr, nullptr},
{nullptr, 0, 0, nullptr, nullptr, nullptr} // Terminator
};
// Tray structure
static struct tray tray = {
"icon.ico", // Windows: .ico file recommended
menu_items
};
void toggle_cb(struct tray_menu* item) {
item->checked = !item->checked;
tray_update(&tray);
cout << "Toggle: " << (item->checked ? "ON" : "OFF") << endl;
}
void quit_cb(struct tray_menu* item) {
(void)item; // Suppress unused parameter warning
should_exit = true;
cout << "Ending processing..." << endl;
tray_exit();
}
int main() {
// Initialize tray icon
if (tray_init(&tray) < 0) {
cerr << "Failed to initialize tray icon" << endl;
return -1;
}
cout << "Tray icon created. Right-click to access menu." << endl;
// Main event loop
while (!should_exit && tray_loop(1) == 0) {
// Continue running
}
cout << "Application terminated." << endl;
return 0;
}Tray structure defines an icon and a menu. Menu is a NULL-terminated array of items.
struct tray {
const char* icon; // Path to icon file or system icon
struct tray_menu* menu; // Pointer to menu items array
};
struct tray_menu {
const char* text; // Menu item text (NULL for terminator)
int disabled; // 1 = grayed out, 0 = enabled
int checked; // 1 = checkmark, 0 = no checkmark
void (*cb)(struct tray_menu*); // Callback function (can be NULL)
void* context; // Optional user data
struct tray_menu* submenu; // Submenu items (can be NULL)
};All functions must be called from the UI thread only.
int tray_init(struct tray* tray)- Creates tray icon. Returns -1 if tray icon/menu can't be created.void tray_update(struct tray* tray)- Updates tray icon and menu.int tray_loop(int blocking)- Runs one iteration of the UI loop. Returns -1 iftray_exit()has been called.void tray_exit()- Terminates UI loop and cleans up resources.
- Menu arrays must be terminated with a NULL item (text field set to NULL)
- Menu arrays must remain valid throughout the tray's lifetime (use static/global)
- Call
tray_exit()to properly clean up before program termination
- Best:
.icofiles (16x16, 32x32 pixels) - System icons:
"shell32.dll"for default system icon - Example:
"C:\\path\\to\\icon.ico"or"./icon.ico"
- Recommended:
.pngfiles - System icons: Standard icon theme names
- Example:
"/usr/share/pixmaps/myapp.png"
- Recommended:
.icnsfiles - System icons: NSImage names like
"NSImageNameInfo" - Example:
"icon.icns"or system icon names
- Check icon file exists and is accessible
- Try system icon: Use
"shell32.dll"on Windows - Run as administrator (Windows only, if needed)
- Verify system tray is enabled in your OS
- Missing libraries: Install platform-specific development packages
- Wrong platform detection: Manually define
TRAY_WINAPI,TRAY_APPINDICATOR, orTRAY_APPKIT
This software is distributed under MIT license, so feel free to integrate it in your commercial products.