Click here to Skip to main content
15,885,366 members
Articles / Programming Languages / Java

Setting the Mac Menubar & Dock in a Cross Platform Java Swing Application

Rate me:
Please Sign up or sign in to vote.
4.00/5 (2 votes)
16 Feb 2020CPOL4 min read 11.5K   157   2
A class to easily set the Mac menu bar & dock icon programmatically on application launch
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:

Java
private static final String APPLICATION_NAME = "Menu bar & dock demo";
private static final String APPLICATION_ICON = "/resources/app-icon.png";

/**
 * Launch the application.
 */
public static void main(String[] args) {
     if(OSXHelper.IS_MAC){
          // These calls must come before any AWT or Swing code is called,
          // otherwise the Mac menu bar will use the class name as the application name.
          System.setProperty("apple.laf.useScreenMenuBar", "true");
          OSXHelper.setMacMenuAboutNameAndDockIcon(args, APPLICATION_NAME, APPLICATION_ICON);
     }
     
     // Ok proceed with launch
     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.

Java
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 { // XdocSet so remove temporary copy of icon.            
         // We land here after the Jar has been relaunched 
         // with our new command line arguments.  
         // One would think that an attempt to delete the temporary icon should be safe here, 
         // however the call to "Runtime.getRuntime().exec(commands)" above 
         // does not return until this section of code (run in another process) is completed. 
         // Therefore, the temporary icon is deleted before it can be loaded.
         // The call to invokeLater() allows the JVM to finish loading this instance before
         // the deletion of the temporary icon.
         SwingUtilities.invokeLater(new Runnable() { 
            public void run() {
               if(null != applicationIcon){
                  try {
                     // exportResource checks to see if the resource 
                     // (in this case the temporary icon) exists and will
                     // not overwrite it if present.  It will however 
                     // return the path to the existing resource.
                     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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionSetting the Mac Menubar & Dock in a Cross Platform Java Swing Application causing NULL pointer exception Pin
Mike Retondo9-Jul-21 16:23
Mike Retondo9-Jul-21 16:23 
AnswerRe: Setting the Mac Menubar & Dock in a Cross Platform Java Swing Application causing NULL pointer exception Pin
David MacDermot19-Jul-21 5:58
David MacDermot19-Jul-21 5:58 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.