This article describes a Java class and methods designed to easily set the menu bar application name and doc icon on a Macintosh. Using this class, these elements may be set programmatically from a Swing application when it is launched.
Introduction
One of the attractive features of Java application development is the concept of write once run anywhere. Since I'm using Java for application development in my current capacity, I decided to see how my app would look on a Mac.
There are quite a few resources available online that detail the steps one should take to make a Java app look and behave like a native Mac app when running on Apple's OS. There was even a time, many years ago, when an app could be easily configured in the source code so that its name would appear in the menu bar. Sadly, this is not quite the case today.
One sure fire way to ensure that the application name appears in the menu bar and icon on the dock is to open the terminal and launch the jar using the following command:
java -Xdock:name= <applicationName> -Xdock:icon= <iconPath> -jar <jar file path>
Doing this however, defeats the purpose of making an executable jar in the first place and I want to keep things simple for my users as well as my lazy self. What if my app, when launched on a Mac, relaunched itself with these arguments before proceeding to fully load? Now that would be swell!
OSXHelper Class
I've put together a small class that makes it trivially easy to ensure that a Java application name appears in the Mac menu bar and it's icon appears in the dock. Just add the class to your project and use it as follows:
private static final String APPLICATION_NAME = "Menu bar & dock demo";
private static final String APPLICATION_ICON = "/resources/app-icon.png";
public static void main(String[] args) {
if(OSXHelper.IS_MAC){
System.setProperty("apple.laf.useScreenMenuBar", "true");
OSXHelper.setMacMenuAboutNameAndDockIcon(args, APPLICATION_NAME, APPLICATION_ICON);
}
System.setProperty("java.net.preferIPv4Stack", "true");
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Demo window = new Demo();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace()
}
}
});
}
How It Works
The class contains two static fields and three static methods. For the purpose of this article, I'll only focus on setMacMenuAboutNameAndDockIcon()
which is where the magic happens.
static protected void setMacMenuAboutNameAndDockIcon(final String[] applicationArgs,
final String applicationName, final String applicationIcon){
try {
String iconPath = null;
String[] launch = {""};
boolean XdockSet = false;
for(String arg : applicationArgs) if(XdockSet = arg.equals("-XdockSet")) break;
if(!XdockSet){
if(null != applicationIcon){
iconPath = exportResource(new Object(){}.getClass().getEnclosingClass(),
applicationIcon);
}
if(null != iconPath){
launch = new String[] { "java", "-Xdock:name=" + applicationName,
"-Xdock:icon=" + iconPath, "-jar",
THIS_JAR_FILE.getAbsolutePath(), "-XdockSet" };
} else {
launch = new String[] { "java","-Xdock:name=" + applicationName,
"-jar", THIS_JAR_FILE.getAbsolutePath(), "-XdockSet" };
}
String[] command = new String[launch.length + applicationArgs.length];
System.arraycopy(launch, 0, command, 0, launch.length);
System.arraycopy(applicationArgs, 0, command, launch.length, applicationArgs.length);
Runtime.getRuntime().exec(command);
Runtime.getRuntime().exit(0);
} else {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if(null != applicationIcon){
try {
String iconPath = exportResource(new Object(){}
.getClass().getEnclosingClass(), applicationIcon);
Thread.sleep(1000);
new File(iconPath).delete();
} catch (Exception e) { e.printStackTrace(); }
}
}
});
}
} catch (Exception e) {
e.printStackTrace();
}
}
While looking at this code; the first thing to note is that this method behaves as if it were a recursive method, and while it doesn't call itself directly, it performs a kind of inter-process recursion and as such needs a stop condition. This is achieved by means of a flag prepended to the jar file argument list upon completion of the first half of the method code. Checking for this flag is therefore the first step in the process.
The first time this method is called, the flag -XdockSet
will not be found (rather should not be found. Don't use this as an argument for your app if you use this class.) and so execution proceeds to the first half of the method where the relaunch command is built and executed.
In order to have some flexibility; setting the dock icon is optional. If a null is supplied to the applicationIcon
argument, then the Java launch command string will not include the -Xdock:icon=
argument and parameter. As a result, the Dock will display the default Java coffee cup. If however, you want to display your application's icon in the dock; it will be necessary to export the resource from the jar so that it might be read by the Java runtime during relaunch. The call to exportResource()
extracts a resource from the executable jar to the folder where the jar is located and returns the path of the extracted resource, in this case our application's icon. This then is included in the Java launch command string. Finally, we include the -XdockSet
flag as the first argument passed to our jar file.
Any arguments that were passed initially to the jar file must then be appended to the launch commands so they are not lost when we relaunch our application. The resulting launch commands are then used to launch our app in a new process before exiting the current process.
The second time this method is called, the flag -XdockSet
will be found and so execution moves to the second half of the method where cleanup takes place. In this case, I wanted to remove the temporary icon resource that was copied out of the jar after it had been loaded into the Dock by the JVM. The comments in the code explain the necessity of invoking the call to File.delete()
after the method completes.
Final Comments
The demo and source were created on a Windows PC and tested on a Mac.
History
- 16th February, 2020: Version 1.0.0.0