Nucleus
OS integration

Notifications cross-plateforme

Envoyez des notifications desktop sur macOS, Windows et Linux depuis une seule API Kotlin.

notification-common vous permet d'envoyer une notification desktop depuis Kotlin sans écrire de code par plateforme. Le module expose une fonction notification(...) et un NotificationManager qui détecte l'OS courant à l'exécution et délègue au backend natif adéquat sur macOS, Windows ou Linux.

L'API commune couvre le sous-ensemble partagé : titre, corps de texte, grande image, petite icône, jusqu'à cinq boutons d'action et callbacks de cycle de vie. Pour les fonctionnalités propres à une plateforme (planification, catégories, barres de progression, images héros), utilisez directement le module par OS.

Ajouter la dépendance

build.gradle.kts
dependencies {
    implementation("dev.nucleusframework:nucleus.notification-common:2.4.3")
}

Cette dépendance tire les trois modules par plateforme de façon transitive. Seul le module correspondant à l'OS hôte est chargé à l'exécution.

Envoyer une notification

import dev.nucleusframework.notification.common.NotificationManager
import dev.nucleusframework.notification.common.notification

NotificationManager.initialize()

val n = notification(
    title = "Téléchargement terminé",
    message = "report.pdf a été enregistré",
    onActivated = { openFile() },
) {
    button("Ouvrir") { openFile() }
    button("Afficher dans le dossier") { showInFolder() }
}

n.send()

title et message, tout comme les callbacks, sont des paramètres de la fonction notification(...). Le bloc lambda final déclare les boutons d'action via button(title, onClick) et, depuis 2.3, des blocs d'options optionnels par plateforme.

Options spécifiques à la plateforme

Les sémantiques qui diffèrent entre OS restent dans les blocs linux { }, macos { } et windows { } du builder. Chaque bloc n'est appliqué que lorsque la notification est livrée sur cet OS ; sur les autres, c'est un no-op. Les champs partagés (title, message, image, buttons, callbacks) restent la base.

import dev.nucleusframework.notification.InterruptionLevel
import dev.nucleusframework.notification.common.notification
import dev.nucleusframework.notification.linux.Urgency
import dev.nucleusframework.notification.windows.ToastDuration
import dev.nucleusframework.notification.windows.ToastScenario

val n = notification(
    title = "Build failed",
    message = "see logs",
    onActivated = { openLogs() },
) {
    button("Open") { openLogs() }

    linux {
        urgency = Urgency.CRITICAL
        transient = true
        category = "device.error"
    }
    macos {
        interruptionLevel = InterruptionLevel.TIME_SENSITIVE
        relevanceScore = 0.9
        subtitle = "CI"
    }
    windows {
        scenario = ToastScenario.URGENT
        duration = ToastDuration.LONG
    }
}

n.send()
BlocType de scopePropriétés
linux { }LinuxNotificationScopeurgency (Urgency), category, transient, resident, expireTimeout (ms ; 0 = jamais, non défini/-1 = défaut serveur)
macos { }MacNotificationScopeinterruptionLevel (InterruptionLevel), relevanceScore (0.0..1.0), subtitle
windows { }WindowsNotificationScopescenario (ToastScenario), duration (ToastDuration)

TIME_SENSITIVE et CRITICAL sous macOS exigent l'entitlement Apple correspondant ; sans cela le système retombe sur ACTIVE. ToastScenario.URGENT nécessite Windows 11+ pour traverser Focus Assist. Pour les API plateforme plus profondes (planification, barres de progression, catégories), utilisez directement les modules macOS, Windows ou Linux.

Fonctionnement

NotificationManager détecte la plateforme active à la première utilisation et délègue au pont natif adéquat. Appeler NotificationManager.initialize() initialise le sous-système immédiatement ; sinon l'initialisation se fait paresseusement au premier send().

La fonction notification(...) construit une valeur Notification. Appeler send() dessus route vers NotificationManager.send(notification) et renvoie un NotificationResult. La même instance Notification peut être envoyée plusieurs fois ; chaque appel produit une nouvelle notification OS.

Les callbacks de cycle de vie ne s'exécutent pas forcément sur un thread UI : revenez sur votre dispatcher UI avant de toucher à l'état Compose.

Délègue à notification-macos. Requiert l'autorisation utilisateur et une app signée et bundlée. Testez sur le binaire packagé (./gradlew runDistributable).

Délègue à notification-windows, qui affiche des toasts WinRT.

Délègue à notification-linux, qui utilise org.freedesktop.Notifications sur D-Bus. Fonctionne sur tout bureau exécutant un daemon de notifications, dont GNOME, KDE et Cinnamon.

Référence de l'API

Construire une notification

Passez le contenu et les callbacks en paramètres de la fonction, et déclarez les boutons dans le bloc :

val n = notification(
    title = "Nouveau message d'Alice",
    message = "Vous avez vu le dernier build ?",
    onActivated = { openConversation() },
    onDismissed = { reason -> log.info("rejetée: $reason") },
    onFailed = { log.warn("échec de l'affichage de la notification") },
) {
    button("Répondre") { /* … */ }
    button("Muet") { /* … */ }
}
  • onActivated: (() -> Unit)? — l'utilisateur a cliqué sur le corps de la notification.
  • onDismissed: ((DismissReason) -> Unit)? — la notification a été rejetée. DismissReason vaut USER_DISMISSED, TIMED_OUT, APPLICATION ou UNKNOWN.
  • onFailed: (() -> Unit)? — l'affichage de la notification a échoué.

Vous pouvez ajouter jusqu'à cinq boutons ; au-delà, une exception est levée.

Envoyer

send() renvoie un NotificationResult :

when (val result = n.send()) {
    is NotificationResult.Success -> result.handle.dismiss()
    is NotificationResult.Failure -> log.warn(result.reason)
}

NotificationResult.Success porte un NotificationHandle ; appelez handle.dismiss() pour fermer la notification par programme tant qu'elle est visible. NotificationResult.Failure porte une chaîne reason.

Vérifier la disponibilité

if (!NotificationManager.isAvailable()) {
    // L'OS courant ou la sandbox interdit les notifications.
}

macOS ignore silencieusement les notifications des apps non bundlées. Testez toujours sur le binaire packagé.

Et ensuite