Open a file with java application on macOS

109 views Asked by At

I have a Java project that I compile both into an exe with launch4j and a mac app with universalJavaApplicationStub. On Windows I can open a file with the exe (right-clickopen with) and it will be passed as the first command line argument (in String[] args).

On macOS I even registered DocumentTypes in the Info.plist. This activates the ability to drop files onto the app icon which was not possible before – so I at least know this part is set up correctly. However when I drop a file onto the app icon, do right-clickopen with, or I run open myfile.txt -a myapp.app the main function receives an empty list of arguments.

The open --help command has the following line:

--args: All remaining arguments are passed in argv to the application's main() function instead of opened.

Using the parameter finally opens the file correctly. What does this description mean? How can I get the opened files instead of the arguments, so it works with right-click or dropping onto the app icon?

(I am using macOS Sonoma)

1

There are 1 answers

0
Hans On BEST ANSWER

Using the comment from @Sweeper, I managed to figure out a solution using the OpenFilesHandler API. This is the minimal working example:

MyApp.java

package myPackage;

import java.awt.Desktop;
import java.io.File;

import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.Timer;

import com.formdev.flatlaf.util.SystemInfo; // You probably need to do this differently

public class MyApp extends JFrame {

  /**
   * This is required since on macOS a file open handler function gets called <em>after</em> the end
   * of the main function.
   */
  private static boolean hasOpenedAnyFileYet = false;

  public static void main(String[] args) {
    if (SystemInfo.isMacOS) {
      // On macOS this function is called when a new file is opened.
      Desktop.getDesktop().setOpenFileHandler(new OpenMyAppFilesHandler());
    }
    System.out.println("Startup with arguments: " + Arrays.toString(args));
    if (args.length == 0) {
      // No file selected, ask user to open one. On macOS there is no argument, instead the opener's
      // event function gets called. Thus we wait for a short while to let that function trigger and
    // set a flag 
      Timer timer =
          new Timer(
              10,
              ae -> {
                if (!MyApp.hasOpenedAnyFileYet) MyApp.newFromFileDialogue();
              });
      timer.setRepeats(false);
      timer.start();
    } else {
      // File selected, open it.
      new MyApp (args[0].replace('/', File.separatorChar).replace('\\', File.separatorChar));
    }
  }

  /** Ask the user to open a file and return its path. Might return {@link null}. */
  private static String queryFilePath() {
    JFileChooser fileChooser = new JFileChooser();
    // If the user cancels, return null
    if (fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
      return fileChooser.getSelectedFile().getAbsolutePath();
    } else {
      return null;
    }
  }

  /**
   * Ask the user to open a file and display it. Return the MyApp object. {@link null} if the user
   * cancels the interaction.
   */
  public static MyApp newFromFileDialogue() {
    String path = queryFilePath();
    if (path != null) return new MyApp(path);
    return null;
  }


  public MyApp(String path) {
    MyApp.hasOpenedAnyFileYet = true;

    // All the setup happens here

    this.setVisible(true);
  }
}

OpenMyAppFilesHandler.java

package myPackage;

import java.awt.desktop.OpenFilesEvent;
import java.awt.desktop.OpenFilesHandler;
import java.io.File;

/** This is what gets notified when a file is opened on macOS. */
public class OpenMyAppFilesHandlerimplements OpenFilesHandler {

  @Override
  public void openFiles(OpenFilesEvent e) {
    for (File file : e.getFiles()) {
      new MyApp(file.getAbsolutePath());
    }
  }
}