Skip to content

Instantly share code, notes, and snippets.

@wiverson
Last active July 7, 2024 03:30
Show Gist options
  • Save wiverson/d2edf0d66ad195c96793d0d25290753b to your computer and use it in GitHub Desktop.
Save wiverson/d2edf0d66ad195c96793d0d25290753b to your computer and use it in GitHub Desktop.
Starting point for implementing system native notifications with JavaFX.
package com.doublerobot;
import javafx.application.Platform;
import javafx.scene.control.Label;
import java.awt.*;
import java.awt.desktop.AppForegroundEvent;
import java.awt.desktop.AppForegroundListener;
import java.awt.event.ActionEvent;
/*
As of this writing, the underlying Java implementation has to add a SystemTray icon in order
for the notification to work.
In addition, at least on Big Sur, clicking on the notification doesn't actually post an event back.
As a workaround, if you only post notifications if the application is running in the background, you
can listen for application focus events. In this scenario, if the app is running in the foreground,
present the user with an application appropriate notification INSTEAD of using a system notification.
Then, if something happens in the background, the user will get a system tray icon AND a system native
notification. The user can then click on a) the app icon to bring it to the foreground, b) the system
native notification, or c) the system tray icon, all of which should then bring the desktop app to
the foreground.
One you are in the foreground, you can then present the application-specific notification and clear
the system tray icon and the notification.
For in-application notifications, check out
https://github.com/controlsfx/controlsfx/wiki/ControlsFX-Features#notifications
Between this snippet as a starter for native OS notifications and in-app notifications via ControlsFX
notifications, you have a reasonable starter.
It would be really, really nice for JavaFX to just create their own implementation of the current
java.awt.* desktop integration - hopefully that'll come at some point in the future.
Also, not shown here but important for cleanup, add:
stage.setOnCloseRequest(x -> notificationExamples.close());
*/
public class NotificationExample {
private Label target;
private ThreadLocal<TrayIcon> trayIcon;
NotificationExample(Label label) {
target = label;
Desktop.getDesktop().addAppEventListener(new AppFocusListener());
}
public void NotificationClicked(ActionEvent actionEvent) {
System.out.println(actionEvent.getActionCommand());
System.out.println("Notification clicked");
Platform.runLater(() -> target.setText("Notification clicked!"));
SystemTray tray = SystemTray.getSystemTray();
tray.remove(trayIcon.get());
}
public void ShowSystemNotification() {
javax.swing.SwingUtilities.invokeLater(() ->
{
if (trayIcon == null || trayIcon.get() == null) {
init();
}
try {
SystemTray tray = SystemTray.getSystemTray();
tray.add(trayIcon.get());
} catch (AWTException e) {
e.printStackTrace();
}
trayIcon.get().displayMessage("Hello, World", "Notification Example",
TrayIcon.MessageType.INFO);
});
}
private void init() {
if (!SystemTray.isSupported())
return;
// By default, we'll just use the standard app icon.
Image image = Taskbar.getTaskbar().getIconImage();
//Alternative (if the icon is on the classpath):
//Image image = Toolkit.getDefaultToolkit().createImage(getClass().getResource("icon.png"));
trayIcon = new ThreadLocal<>();
trayIcon.set(new TrayIcon(image, "Tray Demo"));
//Let the system resize the image if needed
trayIcon.get().setImageAutoSize(true);
//Set tooltip text for the tray icon
trayIcon.get().setToolTip("System tray icon demo");
trayIcon.get().setActionCommand("notification-clicked");
trayIcon.get().addActionListener(this::NotificationClicked);
PopupMenu popupMenu = new PopupMenu();
MenuItem showNotification = new MenuItem("Show notification");
popupMenu.add(showNotification);
showNotification.addActionListener(this::NotificationClicked);
trayIcon.get().setPopupMenu(popupMenu);
}
public void close() {
System.out.println("closing");
javax.swing.SwingUtilities.invokeLater(() ->
{
if (trayIcon.get() != null) {
SystemTray tray = SystemTray.getSystemTray();
tray.remove(trayIcon.get());
System.out.println(("Removed tray!"));
}
});
}
private class AppFocusListener implements AppForegroundListener {
@Override
public void appRaisedToForeground(AppForegroundEvent e) {
javax.swing.SwingUtilities.invokeLater(() ->
{
if (SystemTray.getSystemTray().getTrayIcons().length > 0)
SystemTray.getSystemTray().remove(trayIcon.get());
});
}
@Override
public void appMovedToBackground(AppForegroundEvent e) {
System.out.println("App moved to background");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment