#linux #systray #api-bindings #properties

ksni

A Rust implementation of the KDE/freedesktop StatusNotifierItem specification

8 releases

0.2.2 Apr 27, 2024
0.2.1 Jun 27, 2023
0.2.0 Jul 22, 2021
0.1.3 Mar 20, 2021
0.0.0 Jun 3, 2019

#72 in GUI

Download history 1305/week @ 2024-07-13 1271/week @ 2024-07-20 1722/week @ 2024-07-27 1011/week @ 2024-08-03 1493/week @ 2024-08-10 918/week @ 2024-08-17 1429/week @ 2024-08-24 1305/week @ 2024-08-31 1272/week @ 2024-09-07 596/week @ 2024-09-14 1011/week @ 2024-09-21 1175/week @ 2024-09-28 1580/week @ 2024-10-05 1104/week @ 2024-10-12 1188/week @ 2024-10-19 1739/week @ 2024-10-26

5,834 downloads per month
Used in 5 crates (3 directly)

Unlicense

89KB
2K SLoC

ksni

Build Status Crates Documentation MSRV

A Rust implementation of the KDE/freedesktop StatusNotifierItem specification

Example

use ksni;

#[derive(Debug)]
struct MyTray {
    selected_option: usize,
    checked: bool,
}

impl ksni::Tray for MyTray {
    fn icon_name(&self) -> String {
        "help-about".into()
    }
    fn title(&self) -> String {
        if self.checked { "CHECKED!" } else { "MyTray" }.into()
    }
    // NOTE: On some system trays, `id` is a required property to avoid unexpected behaviors
    fn id(&self) -> String {
        env!("CARGO_PKG_NAME").into()
    }
    fn menu(&self) -> Vec<ksni::MenuItem<Self>> {
        use ksni::menu::*;
        vec![
            SubMenu {
                label: "a".into(),
                submenu: vec![
                    SubMenu {
                        label: "a1".into(),
                        submenu: vec![
                            StandardItem {
                                label: "a1.1".into(),
                                ..Default::default()
                            }
                            .into(),
                            StandardItem {
                                label: "a1.2".into(),
                                ..Default::default()
                            }
                            .into(),
                        ],
                        ..Default::default()
                    }
                    .into(),
                    StandardItem {
                        label: "a2".into(),
                        ..Default::default()
                    }
                    .into(),
                ],
                ..Default::default()
            }
            .into(),
            MenuItem::Separator,
            RadioGroup {
                selected: self.selected_option,
                select: Box::new(|this: &mut Self, current| {
                    this.selected_option = current;
                }),
                options: vec![
                    RadioItem {
                        label: "Option 0".into(),
                        ..Default::default()
                    },
                    RadioItem {
                        label: "Option 1".into(),
                        ..Default::default()
                    },
                    RadioItem {
                        label: "Option 2".into(),
                        ..Default::default()
                    },
                ],
                ..Default::default()
            }
            .into(),
            CheckmarkItem {
                label: "Checkable".into(),
                checked: self.checked,
                activate: Box::new(|this: &mut Self| this.checked = !this.checked),
                ..Default::default()
            }
            .into(),
            StandardItem {
                label: "Exit".into(),
                icon_name: "application-exit".into(),
                activate: Box::new(|_| std::process::exit(0)),
                ..Default::default()
            }
            .into(),
        ]
    }
}

fn main() {
    let service = ksni::TrayService::new(MyTray {
        selected_option: 0,
        checked: false,
    });
    let handle = service.handle();
    service.spawn();

    std::thread::sleep(std::time::Duration::from_secs(5));
    // We can modify the tray
    handle.update(|tray: &mut MyTray| {
        tray.checked = true;
    });
    // Run forever
    loop {
        std::thread::park();
    }
}

Will create a system tray like this:

screenshot_of_example_in_gnome.png

(In GNOME with AppIndicator extension)

Todo

  • org.kde.StatusNotifierItem
  • com.canonical.dbusmenu
  • org.freedesktop.DBus.Introspectable
  • org.freedesktop.DBus.Properties
  • radio item
  • documents
  • async diwic/dbus-rs#166
  • mutable menu items

License

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.

Dependencies

~6MB
~135K SLoC