System tray
Une icône de tray rendue par Compose et un menu contextuel natif sur macOS, Windows et Linux — réactif, compatible GraalVM, sans pipeline d'export d'icônes.
Construire une app de tray à la main, c'est jongler avec des fichiers .ico, des NSStatusItem AppKit, des menus XML AppIndicator, et trois sémantiques de clic différentes. Tray() ramène tout ça à un composable : le icône, c'est ce que Compose sait dessiner ; le menu, un DSL Kotlin ; le menu réagit à l'état.
Dépôt séparé
System Tray vient de NucleusFramework/ComposeNativeTray avec son propre cycle de release. L'artefact est dev.nucleusframework:composenativetray.
En bref
- Rends n'importe quel
@Composable,ImageVector,PainterouDrawableResourcecomme icône — net à tout DPI. - Réactif : change un
mutableStateOf, icône et menu suivent. - Menus natifs partout :
NSStatusItemsur macOS,Shell_NotifyIconsur Windows, StatusNotifierItem / AppIndicator sous Linux. - Compatible GraalVM Native Image.
Installation
dependencies {
implementation("dev.nucleusframework:composenativetray:<version>")
}Démarrage rapide
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import dev.nucleusframework.tray.*
Tray(
icon = Icons.Default.Favorite,
tooltip = "Mon App",
primaryAction = { showWindow() },
) {
Item(label = "Afficher la fenêtre") { showWindow() }
Divider()
Item(label = "Quitter") { exitProcess(0) }
}Fonctionnement
Tray() est un composable. Son icône est rastérisée depuis Compose à chaque changement d'état, puis remise à l'API native de tray de l'OS courant. La lambda du menu tourne dans un scope DSL réévalué à chaque recomposition — ajouter, retirer ou renommer un item ne coûte que le travail Compose habituel.
Résultat : le tray fait partie de le arbre d'état Compose. Pas d'appel rebuild ad hoc, pas de pipeline d'icônes manuel, pas de maintien triple .ico / .png / .icns.
Adossé à NSStatusItem dans la barre de menus. Les icônes vectorielles rendent au DPI natif, avec une teinte optionnelle via tint.
Adossé à Shell_NotifyIcon. Le vectoriel est rastérisé en .ico 32×32 par facteur d'échelle. Passe un .ico authoring via windowsIcon si l'on veut le contrôle exact.
Via StatusNotifierItem (KStatusNotifierItem). Sous GNOME vanilla, ça requiert l'extension AppIndicator ; sous Ubuntu, ça marche out-of-the-box.
StatusNotifierItem est natif à Plasma — tooltips au survol et badges là où Plasma le supporte, sans config.
Sources d'icônes
// ImageVector
Tray(icon = Icons.Default.Notifications, tint = Color.White, tooltip = "App") { /* … */ }
// Painter
Tray(icon = painterResource("icon.png"), tooltip = "App") { /* … */ }
// Compose Multiplatform DrawableResource
Tray(icon = Res.drawable.app_icon, tooltip = "App") { /* … */ }
// Tout composable
Tray(
iconContent = {
Box(Modifier.size(24.dp).background(Color.Red, CircleShape)) {
Text("3", color = Color.White, modifier = Modifier.align(Alignment.Center))
}
},
tooltip = "3 notifications",
) { /* … */ }Icônes spécifiques par plateforme
Quand on veut un .ico natif sous Windows et un vectoriel ailleurs :
Tray(
windowsIcon = painterResource("icon.ico"),
macLinuxIcon = Icons.Default.Notifications,
tint = Color.White,
tooltip = "Mon App",
) { /* menu */ }Action primaire
primaryAction se déclenche au clic gauche (macOS / Windows) ou clic simple (Linux, dépend du desktop). Sans elle, le menu s'ouvre à chaque clic.
Tray(
icon = Icons.Default.Favorite,
tooltip = "Mon App",
primaryAction = { showWindow() },
) {
Item(label = "Quitter") { exitProcess(0) }
}Pour continuer
- DSL menu du tray — items, cases, sous-menus, séparateurs, composition réactive.
- Tray apps — fais ouvrir une popup Compose par l'icône du tray.
Notifications sous Linux
Notifications FreeDesktop sur D-Bus — urgence, hints, sons, callbacks d'action, avec les particularités documentées par serveur.
DSL menu du tray
Construis le menu contextuel du tray comme un DSL Kotlin réactif à Compose — items, cases à cocher, sous-menus, séparateurs, contenu conditionnel.