百科.dev
全部条目AI 编程趋势榜开源项目技术资讯提交条目
登录
< 返回工具列表
C

ComposeNativeTray

> 编程语言
开源

ComposeTray是一个克特林文库,为Mac,Linux和Windows提供了简单的创建系统托盘应用程序的方法. 此库允许

398 stars0 点赞0 次浏览
访问官网GitHub

工具介绍

ComposeTray是一个克特林文库,为Mac,Linux和Windows提供了简单的创建系统托盘应用程序的方法. 此库允许

️ Compose Native Tray

Introduction

Compose Native Tray is a modern Kotlin library for creating applications with system tray icons, offering native support for Linux, Windows, and macOS. It uses an intuitive Kotlin DSL syntax and fixes issues with the standard Compose for Desktop solution.

✨ Features

  • Cross-platform support for Linux, Windows, and macOS.
  • DSL-style syntax to define tray menus with ease.
  • Supports standard items, submenus, dividers, and checkable items.
  • Ability to enable/disable menu items dynamically.
  • Corrects issues with the Compose for Desktop tray, particularly HDPI support on Windows and Linux.
  • Improves the appearance of the tray on Linux, which previously resembled Windows 95.
  • Adds support for checkable items, dividers, and submenus, including nested submenus.
  • Supports primary action for Windows, macOS, and Linux.
    • On Windows and macOS, the primary action is triggered by a left-click on the tray icon.
    • On Linux, on GNOME the primary action is triggered by a double left-click on the tray icon, while on the majority of other environments, primarily KDE Plasma, it is triggered by a single left-click, similar to Windows and macOS.
  • Single Instance Management: Ensures that only one instance of the application can run at a time and allows restoring focus to the running instance when another instance is attempted.
  • Tray Position Detection: Allows determining the position of the system tray, which helps in positioning related windows appropriately.
  • Compose Recomposition Support: The tray supports Compose recomposition, making it possible to dynamically show or hide the tray icon, for example:

Table of Contents

  • Introduction
  • Why Compose Native Tray?
  • Preview
  • ⚡ Installation
  • Quick Start
  • Usage Guide
    • Creating the System Tray Icon
    • ️ Primary Action
    • Building the Menu
    • Icons with painterResource
    • New: Icons with DrawableResource
  • Advanced Features
    • Fully Reactive System Menu
    • Single Instance Management
    • Position Detection
    • Dark Mode Detection
    • Icon Rendering Customization
  • ⚠️ Platform-Specific Notes
    • Icon Limitations
    • Theme Behavior
    • ProGuard / R8
  • TrayApp (Alpha)
  • Apps Using Compose Native Tray
  • License
  • Contribution
  • ‍ Author

Why Compose Native Tray?

This library was created to solve several limitations of the standard Compose for Desktop solution:

  • ✅ Improved HDPI support on Windows and Linux
  • ✅ Modern appearance on Linux (no more Windows 95 look!)
  • ✅ Extended features: checkable items, nested submenus, separators
  • ✅ Native primary action: left-click on Windows/macOS, single-click (KDE) or double-click (GNOME) on Linux
  • ✅ Full Compose recomposition support: fully reactive icon and menu, allowing dynamic updates of items, their states, and visibility

Preview

Windows

macOS

Ubuntu GNOME

Ubuntu KDE

⚡ Installation

Add the dependency to your build.gradle.kts:

dependencies {
  // System tray icon + menu (lightweight — no windowing backend pulled in)
  implementation("dev.nucleusframework:composenativetray:")

  // Only if you use TrayApp (the tray + anchored popup window API).
  // Pulls in the Nucleus application / decorated-window-tao backend.
  implementation("dev.nucleusframework:composenativetray-app:")
}

Since 2.1.0 the library is split in two so apps that only need a tray icon don't pull in the heavier decorated-window-tao windowing backend (see #418). Tray and the menu DSL live in composenativetray and work in any Compose Desktop application { } — no Nucleus application scope required. TrayApp lives in composenativetray-app and needs nucleusApplication { }. Add the second artifact only when you use TrayApp.

Quick Start

Minimal example to create a system tray icon with menu. Tray is a regular composable — it works inside Compose Desktop's application { … } or Nucleus' nucleusApplication { … }:

application {
  Tray(
    icon = Icons.Default.Favorite,
    tooltip = "My Application"
  ) {
    Item(label = "Settings") {
      println("Settings opened")
    }
    
    Divider()
    
    Item(label = "Exit") {
      exitProcess(0)
    }
  }
}

** Recommendation**: It is highly recommended to check out the demo examples in the project's demo directory. These examples showcase various implementation patterns and features that will help you better understand how to use the library effectively.

Notable demos:

  • DemoWithDrawableResources.kt – shows using DrawableResource directly for Tray and menu icons
  • DemoWithPainter.kt – demonstrates using a painterResource icon
  • DemoWithoutContextMenu.kt – minimalist tray with primary action only
  • TrayAppDemo.kt – the full TrayApp (tray + popup window) example

Usage Guide

Creating the System Tray Icon

New: Using a DrawableResource directly

Tray(
  icon = Res.drawable.myIcon,  // org.jetbrains.compose.resources.DrawableResource
  tooltip = "My Application"
) { /* menu */ }

Requires compose.components.resources in your project. In this library it's already included; in your app add: implementation(compose.components.resources)

Option 1: Using an ImageVector

Tray(
  icon = Icons.Default.Favorite,
  tint = null,  // Optional: if null, the tint automatically adapts (white in dark mode, black in light mode) according to the isMenuBarInDarkMode() API
  tooltip = "My Application"
) { /* menu */ }

Option 2: Using a Painter

Tray(
  icon = painterResource(Res.drawable.myIcon),
  tooltip = "My Application"
) { /* menu */ }

Option 3: Using a Custom Composable

Tray(
  iconContent = {
    Canvas(modifier = Modifier.fillMaxSize()) { // Important to use fillMaxSize()!
      // A simple red circle as an icon
      drawCircle(
        color = Color.Red,
        radius = size.minDimension / 2,
        center = center
      )
    }
  },
  tooltip = "My Application"
) { /* menu */ }

⚠️ Important: Always use Modifier.fillMaxSize() with iconContent for proper icon rendering.

Option 4: Platform-Specific Icons

This approach allows respecting the design conventions of each platform:

  • Windows: Traditionally uses colored icons in the system tray
  • macOS/Linux: Prefer monochrome icons that automatically adapt to the theme
val windowsIcon = painterResource(Res.drawable.myIcon)
val macLinuxIcon = Icons.Default.Favorite

Tray(
  windowsIcon = windowsIcon,      // Windows: full colored icon
  macLinuxIcon = macLinuxIcon,    // macOS/Linux: adaptive icon
  tooltip = "My Application"
) { /* menu */ }

** Note**: If no tint is specified, ImageVectors are automatically tinted white (dark mode) or black (light mode) based on the theme.

️ Primary Action

Define an action for clicking on the icon. The behavior varies by platform:

  • Windows/macOS: Left-click on the icon (native implementation for macOS)
  • Linux: Single-click on KDE or double-click on GNOME (implementation via DBus)
Tray(
  icon = Icons.Default.Favorite,
  tooltip = "My Application",
  primaryAction = {
    println("Icon clicked!")
    // Open a window, display a menu, etc.
  }
) { /* menu */ }

Building the Menu

Important note: It's not mandatory to create a context menu. You can use only an icon in the tray with a primary action (left-click) to restore your application, as shown in the DemoWithoutContextMenu.kt example. This minimalist approach is perfect for simple applications that only need a restore function.

The menu uses an intuitive DSL syntax with several types of elements:

…

Icons with painterResource

New: Icons with DrawableResource in menu items

You can now pass DrawableResource directly to menu builders:

Tray(icon = Res.drawable.app_icon, tooltip = "App") {
  SubMenu(label = "With icons", icon = Res.drawable.gear) {
    Item(label = "Action 1", icon = Res.drawable.star) { /* ... */ }
    Item(label = "Action 2", icon = Res.drawable.star) { /* ... */ }
  }

  Divider()

  CheckableItem(
    label = "Enabled",
    icon = Res.drawable.check,
    checked = true,
    onCheckedChange = { /* ... */ }
  )
}

See demo/DemoWithDrawableResources.kt for a complete example. When using painterResource with menu items, declare it in the composable context:

application {
  val advancedIcon = painterResource(Res.drawable.advanced) // ✅ Correct
  
  Tray(/* config */) {
    SubMenu(
      label = "Advanced",
      icon = advancedIcon  // Use the variable
    ) { /* items */ }
  }
}

Advanced Features

Fully Reactive System Menu

The library supports Compose recomposition for all aspects of the system menu:

…

All menu properties (icon, labels, states, item visibility) are reactive and update automatically when application states change, without requiring manual recreation of the menu.

Single Instance Management

Prevent multiple instances of your application:

The single instance manager combined with the primary action (left-click) is particularly useful for restoring a minimized application in the tray rather than opening a new instance. This improves the user experience by:

  • Avoiding resource duplication and confusion with multiple windows
  • Preserving the current state of the application during restoration
  • Offering behavior similar to native system applications

Single instance is enabled by default in nucleusApplication; pass enableSingleInstance = false to opt out. When a second launch is detected, the already-running instance is notified through SingleInstanceRestoreEffect — restore your window (or re-open the tray popup) there instead of starting a new process:

import dev.nucleusframework.application.SingleInstanceRestoreEffect

nucleusApplication(enableSingleInstance = true) { // true is the default
  var isWindowVisible by remember { mutableStateOf(true) }

  // Runs in the already-running instance each time the app is launched again.
  SingleInstanceRestoreEffect {
    isWindowVisible = true // bring the existing window / tray popup back
  }

  // ... Tray / TrayApp / windows
}

See TrayAppDemo.kt for a working example (a second launch re-opens the tray popup).

Deep links

To react to a deep link the OS hands to the app (including one that arrives on a second launch while single instance is enabled), register a handler on the application scope:

nucleusApplication {
  onDeepLink { uri ->
    // handle the incoming URI (navigate, restore state, …)
  }

  // ... Tray / TrayApp / windows
}

Position Detection

Tray positioning needs screen geometry (the Tao backend), so these helpers live in the composenativetray-app artifact (package dev.nucleusframework.composenativetray.trayapp) — the same one that provides TrayApp, which uses them internally.

getTrayPosition() tells

Issues· 10 开放

查看全部 Issues在 GitHub 打开

暂无开放 Issues,或尚未同步最近议题。

> 标签

C

暂无评论,来聊聊你的看法吧

> 工具信息

发布日期2026年8月1日
最后更新2026年9月17日
分类编程语言
定价开源

> 相关工具

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言