Icon display in system tray in Java (Windows: Notification area / Mac: Menu bar)

Introduction

Recently (in my hobby area) I often write a lot of server-side code using the Java Spring Boot framework, but suddenly I wanted to make it resident by displaying an icon in the system tray. Considering distributing server applications to people who are unrelated to information systems, I thought that it was not very kind to display a console such as a terminal, and I thought it would be nice if I could perform the minimum operations with the icon in the system tray. Is the trigger.

The system tray referred to in this article is the red part in the image below.

システムトレイ

It is the "notification area" in Windows and the "menu bar" in Mac. I think there are similar ones in other OS. (Current Windows is no longer called the task tray ...)

environment

This time I will try it with two OSs.

STEP1: Put out the icon for the time being

In this article, Illustration of "A" by Irasutoya is used as the sample icon ʻicon.png`.

First, the code that just puts out an icon.

code

IconDemo.java



import java.awt.AWTException;
import java.awt.Image;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;

public class IconDemo {

    public static void main(String[] args) {
        IconDemo app = new IconDemo();
        app.run();
    }

    /**
     *Method to display icon in system tray
     */
    private void run() {
        Image image = Toolkit.getDefaultToolkit().createImage( ClassLoader.getSystemResource("icon.png ") ); //Prepare icon image
        TrayIcon icon = new TrayIcon(image, "Sample Java App"); //* 1 Generated as a tray icon
        icon.setImageAutoSize(true); //resize
        
        try {
			SystemTray.getSystemTray().add(icon); //* 2 Added to system tray
		} catch (AWTException e) {
			e.printStackTrace();
		}
    }
}

Execution result

実行結果

It was added like this. However, on a Mac, the Java icon was also displayed in the Dock. If you do not intend this, it seems that you can avoid it by adding the following option as a VM argument when starting the program.


-Dapple.awt.UIElement=true

In other words, if you are using plain java, start it as follows.


java -Dapple.awt.UIElement=true IconDemo

Also, hover your mouse cursor over the icon to see it.

ツールチップ

You see "Sample Java App". This is called a tooltip, and the above code instructs you to display the specified character string.

※1 TrayIcon

The TrayIcon class represents an icon that can be added to the system tray. The above code takes two arguments, but you can take up to three arguments.


/*
1st argument:image Icon image (required)
2nd argument:Character string displayed by hovering the mouse cursor over the tooltip icon (optional)
3rd argument:popup Pop-up menu (optional) Used in subsequent STEP
*/
TrayIcon icon = new TrayIcon(Image image, String tooltip, PopupMenu popup);

※2 SystemTray

The SystemTray class represents the system tray for each OS. Get the entity of the system tray with SystemTray.getSystemTray () and add the tray icon to it using the ʻadd ()` method.

You can also use the SystemTray.isSupported () method, like the code below, to see if java can manipulate the system tray in the execution environment in the first place.

IconDemo.java



import java.awt.SystemTray;

public class IconDemo {

    public static void main(String[] args) {
        IconDemo app = new IconDemo();
        app.check();
    }
    
    /**
    *Check if the execution environment supports the system tray
    */
    private void check() {
        Boolean check = SystemTray.isSupported();
        System.out.println("Support status: " + check);
    }

}

However, it seems that ** not necessarily the system tray is unavailable ** if this becomes false. The source is myself. When I tried to do the same in Spring Boot, I got a Headless Exception, which I'll discuss later, and the ʻisSupported ()method becamefalse. However, I was able to display it even if it was false` by taking the corrective action described later.

When a HeadlessException occurs

In my case, it happened when I was using Spring Boot. The cause is that AWT's headless mode is enabled, and when it is enabled, AWT-related features are rarely available.

It can be disabled by specifying the following in the VM argument.

-Djava.awt.headless=false

In other words, if you are using plain java, start it as follows.


java -Djava.awt.headless=false IconDemo

If you are using Spring Boot, you can also disable it by changing the main () method as follows.


/*****Before changing*****/
public static void main(String[] args) {
	SpringApplication.run(IconDemo.class, args);
}

/*****After changing*****/
public static void main(String[] args) {
	SpringApplicationBuilder builder = new SpringApplicationBuilder(IconDemo.class);
	builder.headless(false).run(args);		
}

(However, I do not understand the harmful effects of disabling what was originally enabled in Spring Boot etc.)

STEP2: Display the menu

On Windows, create a menu that appears when you right-click an icon, and on Mac, when you click the icon. It also describes what happens when you click each menu. In addition, the writing style is slightly different from STEP1.

code

IconDemo.java



import java.awt.AWTException;
import java.awt.Image;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class IconDemo {

    public static void main(String[] args) {
        IconDemo app = new IconDemo();
        app.run();
    }

    /**
     *Method to display icon in system tray
     */
    private void run() {
        SystemTray tray = SystemTray.getSystemTray(); //Get system tray
        PopupMenu popup = new PopupMenu(); //* 4 Generate pop-up menu
        Image image = Toolkit.getDefaultToolkit().createImage( ClassLoader.getSystemResource("icon.png ") ); //Prepare icon image
        TrayIcon icon = new TrayIcon(image, "Sample Java App", popup); //* 4 Generated as a tray icon
        icon.setImageAutoSize(true); //resize

        //* 3 Create the contents of the pop-up menu
        MenuItem item1 = new MenuItem("Hello");
        item1.addActionListener(new ActionListener(){
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("Hello world!!");
            }
        });

        MenuItem item2 = new MenuItem("Exit");
        item2.addActionListener(new ActionListener(){
            @Override
            public void actionPerformed(ActionEvent e) {
                tray.remove(icon);
                // System.exit(0);
            }
        });
        popup.add(item1); // ※4
        popup.add(item2); // ※4
        
        try {
            tray.add(icon); //Add to system tray
		} catch (AWTException e) {
			e.printStackTrace();
		}
    }
}

Execution result

Right-click the icon on Windows and click on Mac.

実行結果

Click "Hello" to display "Hello world !!" on the console, and click "Exit" to exit the program.

※3 MenuItem

Each item in the pop-up menu is created with the MenuItem class. In the argument of the constructor, specify the character string of the display name on the menu.

Also, the processing after clicking the menu is added using the ʻaddActionListener () method. Is this writing method familiar to anyone who has created GUI applications? The argument of the ʻaddActionListener () method is an instance that implements the ʻActionListener interface. The above code is implemented using an anonymous class. The ʻActionListener interface must always override the ʻactionPerformed (ActionEvent e)` method, in which you write what happens after the menu is clicked.

In the "Exit" menu, the process to terminate the program is written. You can use System.exit (0); to force it to exit, but it doesn't seem to be a good idea to use it too much. In this program, just write tray.remove (icon); and remove the tray icon from the system tray, and the program will end.

※4 PopupMenu

The PopupMenu class represents a single pop-up menu. By specifying this PopupMenu in the 3rd argument when generating TrayIcon, you can display the menu when you right-click or click the icon.

The MenuItem created earlier can be added by using the ʻadd ()` method of this PopupMenu.

STEP3: Display a message (notification)

In the STEP2 code above, when I clicked the "Hello" menu, "Hello world !!" was displayed on the console, but let's display something as a message in the system tray instead of the console.

code

Change the contents of the ʻactionPerformed () method of ʻitem1 in STEP2 as follows.


import java.awt.TrayIcon.MessageType; //Add to import

/**
*Before changing
*/
@Override
public void actionPerformed(ActionEvent e) {
    System.out.println("Hello world!!");
}

/**
*After changing
*/
@Override
public void actionPerformed(ActionEvent e) {
    // ※5
    icon.displayMessage("Sample program", "Hello World! This is a sample message.", MessageType.INFO);
}

Execution result

Try clicking the "Hello" menu.

実行結果

Unfortunately, it didn't show up on the Mac. In the case of Mac, it seems that the message is displayed using the notification center of Mac, but there seems to be no way to use it directly from Java. The person who answered here introduced the solution, but it has not been done yet. It is a verification.

Summary

I feel like it's not a very modern way.

If you know a better way, I would appreciate it if you could tell me.

The page that I used as a reference

Recommended Posts

Icon display in system tray in Java (Windows: Notification area / Mac: Menu bar)
Call the Windows Notification API in Java
Change java encoding in windows