Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace libappindicator with ksni #201

Open
wants to merge 12 commits into
base: dev
Choose a base branch
from
2 changes: 1 addition & 1 deletion .changes/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"getPublishedVersion": "cargo search ${ pkg.pkg } --limit 1 | sed -nE 's/^[^\"]*\"//; s/\".*//1p' -",
"prepublish": [
"sudo apt-get update",
"sudo apt-get install -y libgtk-3-dev libxdo-dev libayatana-appindicator3-dev"
"sudo apt-get install -y libgtk-3-dev libayatana-appindicator3-dev libdbus-1-dev"
],
"publish": [
{
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/clippy-fmt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
if: matrix.platform == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libxdo-dev libayatana-appindicator3-dev
sudo apt-get install -y libgtk-3-dev libayatana-appindicator3-dev libdbus-1-dev

- uses: dtolnay/rust-toolchain@stable
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
if: matrix.platform == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libxdo-dev libayatana-appindicator3-dev
sudo apt-get install -y libgtk-3-dev libayatana-appindicator3-dev libdbus-1-dev

- uses: dtolnay/rust-toolchain@stable
- run: cargo test
9 changes: 7 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ license = "MIT OR Apache-2.0"
categories = ["gui"]

[features]
default = ["libxdo"]
libxdo = ["muda/libxdo"]
default = []
serde = ["muda/serde", "dep:serde"]
common-controls-v6 = ["muda/common-controls-v6"]
linux-ksni = ["dep:ksni", "dep:arc-swap", "muda/linux-ksni"]

[dependencies]
muda = { version = "0.15", default-features = false }
Expand All @@ -33,6 +33,8 @@ features = [

[target."cfg(target_os = \"linux\")".dependencies]
libappindicator = "0.9"
arc-swap = { version = "1.7.1", optional = true }
ksni = { version = "0.2.2", optional = true }
dirs = "5"

[target."cfg(target_os = \"linux\")".dev-dependencies]
Expand Down Expand Up @@ -78,3 +80,6 @@ tao = "0.30"
image = "0.25"
eframe = "0.27"
serde_json = "1"

[patch.crates-io]
muda = { git = "https://github.com/dfaust/muda", branch = "ksni" }
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,33 @@ tray-icon lets you create tray icons for desktop applications.

- Windows
- macOS
- Linux (gtk Only)
- Linux

## Platform-specific notes:

- On Windows and Linux, an event loop must be running on the thread, on Windows, a win32 event loop and on Linux, a gtk event loop. It doesn't need to be the main thread but you have to create the tray icon on the same thread as the event loop.
- On macOS, an event loop must be running on the main thread so you also need to create the tray icon on the main thread.

### Cargo Features
## Cargo Features

- `common-controls-v6`: Use `TaskDialogIndirect` API from `ComCtl32.dll` v6 on Windows for showing the predefined `About` menu item dialog.
- `libxdo`: Enables linking to `libxdo` which is used for the predfined `Copy`, `Cut`, `Paste` and `SelectAll` menu item, see https://github.com/tauri-apps/muda#cargo-features
- `serde`: Enables de/serializing derives.
- `linux-ksni`: Use ksni and the xdg standard to create and manage tray icons on Linux. (experimental)

## Dependencies (Linux Only)

On Linux, `gtk`, `libxdo` is used to make the predfined `Copy`, `Cut`, `Paste` and `SelectAll` menu items work and `libappindicator` or `libayatnat-appindicator` are used to create the tray icon, so make sure to install them on your system.
On Linux, `gtk`, `libappindicator` or `libayatana-appindicator` are used to create the tray icon. When using the `linux-ksni` feature, `libdbus-1-dev` is needed as well. So make sure to install these packages on your system.

#### Arch Linux / Manjaro:

```sh
pacman -S gtk3 xdotool libappindicator-gtk3 #or libayatana-appindicator
pacman -S gtk3 libappindicator-gtk3 # or `libayatana-appindicator` and optionally `dbus`
```

#### Debian / Ubuntu:

```sh
sudo apt install libgtk-3-dev libxdo-dev libappindicator3-dev #or libayatana-appindicator3-dev
sudo apt install libgtk-3-dev libappindicator3-dev # or `libayatana-appindicator3-dev` and optionally `libdbus-1-dev`
```

## Examples
Expand Down
86 changes: 86 additions & 0 deletions examples/counter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

#![allow(unused)]

use tao::event_loop::{ControlFlow, EventLoopBuilder};
use tray_icon::{
menu::{
AboutMetadata, CheckMenuItem, IconMenuItem, Menu, MenuEvent, MenuItem, PredefinedMenuItem,
Submenu,
},
TrayIconBuilder, TrayIconEvent,
};

fn main() {
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png");

let event_loop = EventLoopBuilder::new().build();

let mut counter = 0;
let tray_menu = Menu::new();

let counter_i = MenuItem::new(format!("Counter: {counter}"), true, None);
tray_menu.append_items(&[&counter_i]);

let mut tray_icon = None;

let menu_channel = MenuEvent::receiver();
let tray_channel = TrayIconEvent::receiver();

event_loop.run(move |event, _, control_flow| {
// We add delay of 16 ms (60fps) to event_loop to reduce cpu load.
// Alternatively, you can set ControlFlow::Wait or use TrayIconEvent::set_event_handler,
// see https://github.com/tauri-apps/tray-icon/issues/83#issuecomment-1697773065
*control_flow = ControlFlow::Poll;
std::thread::sleep(std::time::Duration::from_millis(16));

if let tao::event::Event::NewEvents(tao::event::StartCause::Init) = event {
let icon = load_tray_icon(std::path::Path::new(path));

// We create the icon once the event loop is actually running
// to prevent issues like https://github.com/tauri-apps/tray-icon/issues/90
tray_icon = Some(
TrayIconBuilder::new()
.with_menu(Box::new(tray_menu.clone()))
.with_tooltip("tao - awesome windowing lib")
.with_icon(icon)
.build()
.unwrap(),
);

// We have to request a redraw here to have the icon actually show up.
// Tao only exposes a redraw method on the Window so we use core-foundation directly.
#[cfg(target_os = "macos")]
unsafe {
use core_foundation::runloop::{CFRunLoopGetMain, CFRunLoopWakeUp};

let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
}
}

counter += 1;
counter_i.set_text(format!("Counter: {counter}"));
})
}

fn load_icon(path: &std::path::Path) -> (Vec<u8>, u32, u32) {
let image = image::open(path)
.expect("Failed to open icon path")
.into_rgba8();
let (width, height) = image.dimensions();
let rgba = image.into_raw();
(rgba, width, height)
}

fn load_tray_icon(path: &std::path::Path) -> tray_icon::Icon {
let (icon_rgba, icon_width, icon_height) = load_icon(path);
tray_icon::Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")
}

fn load_menu_icon(path: &std::path::Path) -> muda::Icon {
let (icon_rgba, icon_width, icon_height) = load_icon(path);
muda::Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")
}
45 changes: 34 additions & 11 deletions examples/tao.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ use tao::{
event_loop::{ControlFlow, EventLoopBuilder},
};
use tray_icon::{
menu::{AboutMetadata, Menu, MenuEvent, MenuItem, PredefinedMenuItem},
menu::{
AboutMetadata, CheckMenuItem, IconMenuItem, Menu, MenuEvent, MenuItem, PredefinedMenuItem,
Submenu,
},
TrayIconBuilder, TrayIconEvent,
};

Expand Down Expand Up @@ -37,6 +40,16 @@ fn main() {

let tray_menu = Menu::new();

let icon_i = IconMenuItem::new(
"Icon",
true,
Some(load_menu_icon(std::path::Path::new(path))),
None,
);
let check_i = CheckMenuItem::new("Check", true, false, None);
let subitem_i = MenuItem::new("Subitem", true, None);
let submenu_i = Submenu::new("Submenu", true);
submenu_i.append(&subitem_i);
let quit_i = MenuItem::new("Quit", true, None);
tray_menu.append_items(&[
&PredefinedMenuItem::about(
Expand All @@ -48,6 +61,9 @@ fn main() {
}),
),
&PredefinedMenuItem::separator(),
&icon_i,
&check_i,
&submenu_i,
&quit_i,
]);

Expand All @@ -61,7 +77,7 @@ fn main() {

match event {
Event::NewEvents(tao::event::StartCause::Init) => {
let icon = load_icon(std::path::Path::new(path));
let icon = load_tray_icon(std::path::Path::new(path));

// We create the icon once the event loop is actually running
// to prevent issues like https://github.com/tauri-apps/tray-icon/issues/90
Expand Down Expand Up @@ -103,14 +119,21 @@ fn main() {
})
}

fn load_icon(path: &std::path::Path) -> tray_icon::Icon {
let (icon_rgba, icon_width, icon_height) = {
let image = image::open(path)
.expect("Failed to open icon path")
.into_rgba8();
let (width, height) = image.dimensions();
let rgba = image.into_raw();
(rgba, width, height)
};
fn load_icon(path: &std::path::Path) -> (Vec<u8>, u32, u32) {
let image = image::open(path)
.expect("Failed to open icon path")
.into_rgba8();
let (width, height) = image.dimensions();
let rgba = image.into_raw();
(rgba, width, height)
}

fn load_tray_icon(path: &std::path::Path) -> tray_icon::Icon {
let (icon_rgba, icon_width, icon_height) = load_icon(path);
tray_icon::Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")
}

fn load_menu_icon(path: &std::path::Path) -> muda::Icon {
let (icon_rgba, icon_width, icon_height) = load_icon(path);
muda::Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")
}
Loading
Loading