ldtray 0.1.0

Cross-platform tray icons with zero compile-time linkage to platform GUI libraries — every toolkit is loaded at runtime via libloading, so headless daemons degrade gracefully instead of failing to link.
Documentation
  • Coverage
  • 100%
    32 out of 32 items documented1 out of 1 items with examples
  • Size
  • Source code size: 101.44 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 1.38 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 4s Average build duration of successful builds.
  • all releases: 5s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • KarpelesLab/ldtray
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • MagicalTux

ldtray

CI crates.io docs.rs License: MIT

Cross-platform tray icons for Rust that are never linked against any GUI or platform library at compile time.

Every platform toolkit — libdbus on Linux, shell32/user32 on Windows, AppKit/objc on macOS — is resolved at runtime through libloading. The result: one daemon binary runs everywhere. On a headless server the only failure is a clean Err from Tray::new ("the tray library could not be loaded") — never a link error, never a crash. Ignore the error and your program keeps running without a tray.

Features

  • Tray icon from raw RGBA pixels
  • Click triggers: left / right / middle / double click
  • Context menu with buttons, checkboxes, separators, and submenus
  • Desktop notifications
  • Update icon, tooltip, and menu live from any thread via a Send + Sync handle
  • Blocking run() (main-thread-correct, works on macOS) or background spawn() (Linux/Windows)

Status

Platform Mechanism State
Linux StatusNotifierItem + dbusmenu over D-Bus ✅ icon, triggers, menu, notifications
Windows Shell_NotifyIcon + hidden window planned
macOS NSStatusItem via the Obj-C runtime planned

Usage

use ldtray::{Tray, TrayConfig, Icon, Menu, MenuItem, Event, Notification};

let icon = Icon::from_rgba(1, 1, vec![255, 0, 0, 255])?;
let menu = Menu::new()
    .item(MenuItem::button(1, "Say hi"))
    .item(MenuItem::separator())
    .item(MenuItem::button(2, "Quit"));

let tray = match Tray::new(TrayConfig::new(icon).tooltip("demo").menu(menu)) {
    Ok(tray) => tray,
    Err(err) => {
        eprintln!("no tray available ({err}); continuing headless");
        return Ok(());
    }
};

let handle = tray.handle();
tray.run(move |event| match event {
    Event::Menu(id) if id.0 == 1 => { let _ = handle.notify(Notification::new("demo", "hi")); }
    Event::Menu(id) if id.0 == 2 => { let _ = handle.quit(); }
    other => println!("event: {other:?}"),
})?;

Run the bundled example:

cargo run --example basic

Design

The whole crate compiles with a single non-platform dependency (libloading). Platform symbols are hand-bound FFI declarations resolved from a dlopen'd library the first time a Tray is created. See src/platform/ for the per-OS backends and docs/source comments for the wire-level details.

License

MIT © Karpelès Lab Inc.