Open your own links and files - fakeivchenko/lwjwae GitHub Wiki

An application can own a scheme of links, such as notes://today, and the types of files that it opens, such as .note. A browser, a chat, or the file manager then hands them to the application. In this guide, a notes application hears of a link and of a file, both in the window that already runs.

A link and a file that reached the running window, on Linux and on Windows 11

Register the scheme and the type

The Gradle plugin registers both in every package that it builds:

lwjwae {
    displayName = "Notes"
    urlScheme("notes")
    fileType("note")
    packaging.all()
}
Line What it registers
urlScheme("notes") Links that start with notes:. A scheme is a lowercase letter, then letters, digits, +, -, or .. The schemes of the system, such as https and mailto, are refused.
fileType("note") Files that end with .note, as the media type application/x-IMAGE-note, where IMAGE is the name of the executable. The file manager calls the type by the display name and "document", such as "Notes document".
fileType("md", "text/markdown") The same, with a media type of your own.
fileType("note", "application/x-notes", "Note") The same, with what the file manager calls the type.
Package Where the registration goes
.deb, Arch package, AppImage The MimeType line of the desktop entry, with x-scheme-handler/notes for the scheme, and a shared-mime-info type in /usr/share/mime/packages for each type of file.
.app and .dmg CFBundleURLTypes and CFBundleDocumentTypes in Info.plist.
.msi The classes of the user in the registry, under HKCU\Software\Classes, without administrator rights.

The registration comes with the installation, so ./gradlew run doesn't register anything. To try the code under run, pass a link or a file as an argument, as the system would.

Hear what the system opens

application.onOpen hears every request. With a single instance, a link that the user opens while the application runs reaches the running window instead of a second one:

public class OpenLinks {
  public static void main(String[] args) {
    ApplicationParameters parameters = ApplicationParameters.builder().name("Notes").build();
    Optional<Application> instance = Application.createSingleInstance(parameters, args);
    if (instance.isEmpty()) {
      return; // The running instance hears of the links and the files.
    }
    try (Application application = instance.get()) {
      Window window =
          application.show(
              WindowParameters.builder()
                  .title("Notes")
                  .size(new WindowSize(560, 360))
                  .resource("open/index.html")
                  .build());
      application.onOpen(
          event -> {
            event.urls().forEach(url -> window.emit("link", url.toString()));
            event.files().forEach(file -> window.emit("file", file.getFileName().toString()));
          });
      application.run();
    }
  }
}

An OpenEvent has two lists: urls, the links as URIs, and files, the files and folders as absolute Paths. The page shows what arrives:

lwjwae.listen("link", ({ payload }) => add(`Link: ${payload}`));
lwjwae.listen("file", ({ payload }) => add(`File: ${payload}`));

What counts as a link or a file

On Windows and Linux, the system starts the executable with the link or the file as an argument. The arguments also carry what the program needs for itself, so only what is clearly a link or a file counts:

  • A URI with a scheme of two letters or more is a link. A file: URI is a file. A Windows drive, such as C:, isn't a scheme.
  • A path that exists, relative to the working directory, is a file.
  • A flag, which starts with -, and anything else are left out.

Application.create(parameters, args) and Application.createSingleInstance(parameters, args) read the arguments of the first start. A request that comes before any listener waits for the first one, so onOpen after show misses nothing.

Platforms

Platform How a request arrives
Windows A new process with the link or the file as an argument. A running application hears of it only as a single instance.
macOS AppKit hands the links and the files to the running process through application:openURLs:, and starts it first when none runs. No second process starts, with or without a single instance.
Linux As on Windows, through the Exec line of the desktop entry.

Good to know

  • Treat a link like any input from outside: a page of the web can open notes:// with whatever follows. Check it before you act on it.
  • The name of the application is what the processes of a single instance meet under, so keep it the same from one version to the next. See Run a single instance.