Skip to content
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e609d95
Add systemtray to builtins
expenses Mar 30, 2026
4c31394
Cargo fmt
expenses Apr 2, 2026
7c35e6c
[autofix.ci] apply automated fixes
autofix-ci[bot] Apr 2, 2026
4a593aa
Example
expenses Mar 30, 2026
3c31a7f
rename to system_tray, use more sensible icon
expenses Apr 2, 2026
1bdbc3f
[autofix.ci] apply automated fixes
autofix-ci[bot] Apr 2, 2026
4f9328d
Add favicon-white to reuse
expenses Apr 2, 2026
78f4e4b
Add SystemTray slint type
expenses Apr 2, 2026
b580236
Rename to inherits_window_or_system_tray
expenses Apr 7, 2026
18d816a
Change example to use SystemTray type
expenses Apr 2, 2026
1e7f58b
Restore some stuff, use with_event_loop_proxy, add init impl
expenses Apr 7, 2026
11b0720
Init via change tracking
expenses Apr 7, 2026
e680743
Update example
expenses Apr 7, 2026
98a5b82
Clean up
expenses Apr 7, 2026
b88cc1a
[autofix.ci] apply automated fixes
autofix-ci[bot] Apr 7, 2026
a9e2d33
Add title property
expenses Apr 7, 2026
4c9441b
Ensure that tray-icon compiles
expenses Apr 7, 2026
f25cacc
[autofix.ci] apply automated fixes
autofix-ci[bot] Apr 7, 2026
5874d3c
Reduce crates required
expenses Apr 7, 2026
72bbde8
Add libpango to CI
expenses Apr 8, 2026
8486653
Revert "Add libpango to CI"
expenses Apr 8, 2026
8017a2e
Gate system tray around OS
expenses Apr 8, 2026
b8fb662
Fix
expenses Apr 8, 2026
5bea4eb
Clean up return statements
expenses Apr 8, 2026
c61a027
Clean up features
expenses Apr 9, 2026
2f6fd28
copy FlickableDataBox
expenses Apr 9, 2026
a066df6
Add SystemTray to inherits_window
expenses Apr 9, 2026
36a7258
Change to systemtraydatabox
expenses Apr 9, 2026
f98c448
Mark SystemTray as experimental
expenses Apr 9, 2026
3b6102b
Get example working
expenses Apr 9, 2026
d038250
Switch to my tray-icon branch
expenses Apr 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
624 changes: 573 additions & 51 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ members = [
'examples/mcu-embassy',
'examples/uefi-demo',
'examples/async-io',
'examples/system_tray',
'demos/weather-demo',
'demos/usecases/rust',
'helper_crates/const-field-offset',
Expand Down Expand Up @@ -92,6 +93,7 @@ default-members = [
'examples/todo/rust',
'examples/virtual_keyboard/rust',
'examples/carousel/rust',
'examples/system_tray',
'demos/energy-monitor',
'internal/backends/winit',
'internal/backends/qt',
Expand Down
1 change: 1 addition & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ path = [
"examples/orbit-animation/images/**.svg",
"examples/sprite-sheet/images/**.png",
"examples/speedometer/needle.png",
"examples/system_tray/favicon-white.png",

"demos/printerdemo/ui/images/action-button-frame.png",
"demos/printerdemo/ui/images/mcu/combo-active-background.png",
Expand Down
1 change: 1 addition & 0 deletions api/cpp/cbindgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ fn gen_corelib(
"Layer",
"ContextMenu",
"MenuItem",
"SystemTray",
];

config.export.include = [
Expand Down
21 changes: 21 additions & 0 deletions examples/system_tray/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright © SixtyFPS GmbH <info@slint.dev>
# SPDX-License-Identifier: MIT

[package]
name = "system_tray"
version = "1.16.0"
authors = ["Slint Developers <info@slint.dev>"]
edition.workspace = true
publish = false
license = "MIT"

[[bin]]
path = "main.rs"
name = "system_tray"

[dependencies]
slint = { path = "../../api/rs/slint", default-features = false, features = ["renderer-femtovg", "backend-winit-x11", "compat-1-2"] }
i-slint-core = { workspace = true, features = ["system-tray"] }

[build-dependencies]
slint-build = { path = "../../api/rs/build" }
6 changes: 6 additions & 0 deletions examples/system_tray/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT

fn main() {
println!("cargo:rustc-env=SLINT_ENABLE_EXPERIMENTAL_FEATURES=1");
}
Binary file added examples/system_tray/favicon-white.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions examples/system_tray/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT

slint::slint! {
export component ExampleTray inherits SystemTray {
icon: @image-url("favicon-white.png");
}
}

fn main() {
let _tray = ExampleTray::new().unwrap();
slint::run_event_loop().unwrap();
}
5 changes: 5 additions & 0 deletions internal/compiler/builtins.slint
Original file line number Diff line number Diff line change
Expand Up @@ -829,3 +829,8 @@ export global NativePalette {
//-is_non_item_type
//-is_internal
}

export component SystemTray {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make it as experimental so it is only available when using SLINT_ENABLE_EXPERIMENTAL_FEATURES=1 ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Where does that leave the example? Should I remove it now?

in property <image> icon;
in property <string> title;
}
2 changes: 1 addition & 1 deletion internal/compiler/passes/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ pub fn ensure_window(

pub fn inherits_window(component: &Rc<Component>) -> bool {
component.root_element.borrow().builtin_type().is_none_or(|b| {
matches!(b.name.as_str(), "Window" | "Dialog" | "WindowItem" | "PopupWindow")
matches!(b.name.as_str(), "Window" | "Dialog" | "WindowItem" | "PopupWindow" | "SystemTray")
})
}

Expand Down
2 changes: 2 additions & 0 deletions internal/compiler/typeregister.rs
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,8 @@ impl TypeRegister {
register.elements.remove("DropArea").unwrap();
register.types.remove("DropEvent").unwrap(); // Also removed in xtask/src/slintdocs.rs

register.elements.remove("SystemTray").unwrap();

match register.elements.get_mut("Window").unwrap() {
ElementType::Builtin(b) => {
Rc::get_mut(b)
Expand Down
7 changes: 7 additions & 0 deletions internal/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ std = [
"i-slint-common/markdown",
"taffy/std",
]
system-tray = ["dep:ksni", "dep:tray-icon"]
# Unsafe feature meaning that there is only one core running and all thread_local are static.
# You can only enable this feature if you are sure that any API of this crate is only called
# from a single core, and not in a interrupt or signal handler.
Expand Down Expand Up @@ -146,6 +147,12 @@ taffy = { version = "0.9", default-features = false, features = ["flexbox", "taf
[target.'cfg(target_family = "unix")'.dependencies]
gettext-rs = { version = "0.7.1", optional = true, features = ["gettext-system"] }

[target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies]
tray-icon = { version = "0.21", optional = true }

[target.'cfg(not(any(target_os = "macos", target_os = "windows")))'.dependencies]
ksni = { version = "0.3.3", optional = true, features = ["blocking"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
web-time = { version = "1.0", optional = true }
wasm-bindgen = { version = "0.2" }
Expand Down
163 changes: 163 additions & 0 deletions internal/core/items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub use crate::menus::MenuItem;
use crate::rtti::*;
use crate::window::{WindowAdapter, WindowAdapterRc, WindowInner};
use crate::{Callback, Coord, Property, SharedString};
use alloc::boxed::Box;
use alloc::rc::Rc;
use const_field_offset::FieldOffsets;
use core::cell::Cell;
Expand Down Expand Up @@ -1774,6 +1775,168 @@ declare_item_vtable! {
fn slint_get_BoxShadowVTable() -> BoxShadowVTable for BoxShadow
}

#[repr(C)]
/// Wraps the internal data structure for the SystemTray
pub struct SystemTrayDataBox(core::ptr::NonNull<SystemTrayData>);

impl Default for SystemTrayDataBox {
fn default() -> Self {
SystemTrayDataBox(Box::leak(Box::<SystemTrayData>::default()).into())
}
}
impl Drop for SystemTrayDataBox {
fn drop(&mut self) {
// Safety: the self.0 was constructed from a Box::leak in SystemTrayDataBox::default
drop(unsafe { Box::from_raw(self.0.as_ptr()) });
}
}

impl core::ops::Deref for SystemTrayDataBox {
type Target = SystemTrayData;
fn deref(&self) -> &Self::Target {
// Safety: initialized in SystemTrayDataBox::default
unsafe { self.0.as_ref() }
}
}

#[derive(Default)]
pub struct SystemTrayData {
#[cfg(feature = "system-tray")]
inner: std::cell::OnceCell<crate::system_tray::SystemTray>,
#[cfg_attr(not(feature = "system-tray"), allow(unused))]
change_tracker: crate::properties::ChangeTracker,
}

#[repr(C)]
#[derive(FieldOffsets, Default, SlintElement)]
#[pin]
pub struct SystemTray {
pub icon: Property<crate::graphics::Image>,
pub title: Property<SharedString>,
pub cached_rendering_data: CachedRenderingData,
data: SystemTrayDataBox,
}

impl Item for SystemTray {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we really need the SystemTray to be an Item
the Compiler would probably need to generate some special code of it.
But thats fine to have it temporarily as an experiment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would the alternative be?

#[cfg_attr(not(feature = "system-tray"), allow(unused))]
fn init(self: Pin<&Self>, self_rc: &ItemRc) {
#[cfg(feature = "system-tray")]
self.data.change_tracker.init_delayed(
self_rc.downgrade(),
|_| true,
|self_weak, has_icon| {
let Some(tray_rc) = self_weak.upgrade() else {
return;
};
let Some(tray) = tray_rc.downcast::<SystemTray>() else {
return;
};
if !*has_icon {
return;
}
let tray = tray.as_pin_ref();
let system_tray =
match crate::system_tray::SystemTray::new(crate::system_tray::Params {
icon: &tray.icon(),
title: &tray.title(),
}) {
Ok(system_tray) => system_tray,
Err(err) => panic!("{}", err),
};

let _ = tray.data.inner.set(system_tray);
},
);
}

fn layout_info(
self: Pin<&Self>,
_orientation: Orientation,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> LayoutInfo {
LayoutInfo::default()
}

fn input_event_filter_before_children(
self: Pin<&Self>,
_: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursor,
) -> InputEventFilterResult {
InputEventFilterResult::ForwardAndIgnore
}

fn input_event(
self: Pin<&Self>,
_: &MouseEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
_: &mut MouseCursor,
) -> InputEventResult {
InputEventResult::EventIgnored
}

fn capture_key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}

fn key_event(
self: Pin<&Self>,
_: &InternalKeyEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> KeyEventResult {
KeyEventResult::EventIgnored
}

fn focus_event(
self: Pin<&Self>,
_: &FocusEvent,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
) -> FocusEventResult {
FocusEventResult::FocusIgnored
}

fn render(
self: Pin<&Self>,
_backend: &mut ItemRendererRef,
_self_rc: &ItemRc,
_size: LogicalSize,
) -> RenderingResult {
RenderingResult::ContinueRenderingChildren
}

fn bounding_rect(
self: core::pin::Pin<&Self>,
_window_adapter: &Rc<dyn WindowAdapter>,
_self_rc: &ItemRc,
geometry: LogicalRect,
) -> LogicalRect {
geometry
}

fn clips_children(self: core::pin::Pin<&Self>) -> bool {
false
}
}

impl ItemConsts for SystemTray {
const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
Self::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
}

declare_item_vtable! {
fn slint_get_SystemTrayVTable() -> SystemTrayVTable for SystemTray
}

declare_item_vtable! {
fn slint_get_ComponentContainerVTable() -> ComponentContainerVTable for ComponentContainer
}
Expand Down
2 changes: 2 additions & 0 deletions internal/core/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ pub mod sharedvector;
pub mod slice;
pub mod string;
pub mod styled_text;
#[cfg(feature = "system-tray")]
pub mod system_tray;
pub mod tests;
pub mod textlayout;
pub mod timers;
Expand Down
Loading
Loading