Discussion:
How to handle file open requests on MacOS
Mike Hearn
2015-01-05 17:07:23 UTC
Permalink
8u40 has javapackager improvements to help you do file associations in your
installers. I'm looking forward to this, though for now I have to do it
manually with 8u20.

But one problem I've found is that just setting up the file associations is
not enough. You must handle the "openFile" messages sent by the OS as well.
On Linux/Windows the interface is simple: the OS starts another copy of
your app with the command line parameter. On MacOS you get a message posted
to your message queue because apps are single instance by default. To
handle this in JFX we must use internal APIs, like in the code snippet
below.

Hopefully 8u40 will contain a proper cross platform file open request API
that exposes this in a better way, otherwise the new javapackager support
will be rather hard to use.

private void handleFileOpenRequests() {
// This is only for MacOS, where the OS single instances us by
default and sends us a message at startup to ask
// us to open a file. It requires internal APIs.
if (!System.getProperty("os.name").toLowerCase().contains("mac")) return;
com.sun.glass.ui.Application app =
com.sun.glass.ui.Application.GetApplication();
com.sun.glass.ui.Application.EventHandler old = app.getEventHandler();
app.setEventHandler(new com.sun.glass.ui.Application.EventHandler() {
@Override public void
handleQuitAction(com.sun.glass.ui.Application app, long time) {
old.handleQuitAction(app, time);
}
@Override public boolean handleThemeChanged(String themeName) {
return old.handleThemeChanged(themeName);
}

@Override
public void handleOpenFilesAction(com.sun.glass.ui.Application
app, long time, String[] files) {
for (String strPath : files) {
if
(strPath.equals("com.intellij.rt.execution.application.AppMain"))
continue; // Only happens in dev environment.
log.info("OS is requesting that we open " + strPath);
Platform.runLater(() -> {
Main.instance.mainWindow.handleOpenedFile(new
File(strPath));
});
}
}
});
}
Danno Ferrin
2015-01-05 17:23:27 UTC
Permalink
Post by Mike Hearn
8u40 has javapackager improvements to help you do file associations in your
installers. I'm looking forward to this, though for now I have to do it
manually with 8u20.
But one problem I've found is that just setting up the file associations is
not enough. You must handle the "openFile" messages sent by the OS as well.
On Linux/Windows the interface is simple: the OS starts another copy of
your app with the command line parameter. On MacOS you get a message posted
to your message queue because apps are single instance by default. To
handle this in JFX we must use internal APIs, like in the code snippet
below.
Hopefully 8u40 will contain a proper cross platform file open request API
that exposes this in a better way, otherwise the new javapackager support
will be rather hard to use.
8u40 will not contain such an API, what is in 8u40 is what is visible in the EA releases. Only major bugs will be fixed at this point.

For a demo proof of concept application I settled on a "two main" solution. One main is for the Mac and one is for Win/Linux. The win/linux main was the principal main class:

(warning: the demo was swing based, avert your eyes)

public class MainApplication {

/* <Snip> */

void loadFile(File f) {
/* <Snip applicaiton logic> */
}

/* <More Snip> */

public static void main(String [] args) {
SwingUtilities.invokeLater(() -> { // My Eyes! The Goggles, they do nothing!
MainApplication app = new MainApplication();
app.createGUI();
if (args.length > 0) {
File f = new File(args[0]);
if (f.isFile()) {
app.loadFile(f);
}
}
});
}
}

The gist is that the main delegates to a load file method in the normal main, and the app has a stable state outside of the loaded file.

For Mac I wrote a MainApplicationMac that did mac-isms (not masochisms) in a different main method. It extended strictly for method accessibility.

public class MainApplicationMac extends MainApplication {

public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
MainApplication app = new MainApplication();
app.createGUI();
if (args.length > 0) {
File f = new File(args[0]);
if (f.isFile()) {
app.loadFile(f);
}
}

Application.getApplication().setOpenFileHandler((AppEvent.OpenFilesEvent ofe) -> {
List<File> files = ofe.getFiles();
if (files != null && files.size() > 0) {
app.loadFile(files.get(0));
}
});

});
}
}

Really the only new code is the second block.

Also since I segregated out the mac stuff I did build gymnastics to ensure this is only built and packaged on a mac, so the introspection isn't done. But I did have to add "-XDignore.symbol.file=true" to the javac arguments since the mac specific classes are not added to the symbol file for some odd reason.

Another thing you will notice that is different from mac and win/linux is that the same application receives all file open requests, whereas in win/linux you will see a new application opened for each request.
Mike Hearn
2015-01-05 17:43:59 UTC
Permalink
What is Application.getApplication() here? The JavaFX Application class
does not have a setOpenFileHandler method. Is that a Mac-specific API?

It's too bad that 8u40 won't have this. Being able to easily open double
clicked files is pretty basic. Perhaps post 8u40 the JFX team could go
through Scene Builder and identify everywhere it relies on internal APIs or
custom magic and suck it into the core API, as then you'd have confidence
that an app of real complexity can be done entirely with documented stuff.
Danno Ferrin
2015-01-05 17:50:18 UTC
Permalink
Oh, yes. Mac has it's own Applicaiton class. Here's the imports for the second file...

import com.apple.eawt.AppEvent;
import com.apple.eawt.Application;
import java.io.File;
import java.util.List;
import javax.swing.SwingUtilities;

The com.apple.eawt stuff is shipped with Oracle JRE and JDK, but like I said in my previous mail you need to pass a flag to the compiler to turn off it's index and read the jar.

The JavaDoc has gone MIA though -- https://bugs.openjdk.java.net/browse/JDK-8027638 but you can peruse the source code to see the details, it's all OpenJDK - http://hg.openjdk.java.net/jdk8u/jdk8u40/jdk/file/564bca490631/src/macosx/classes/com/apple/eawt
What is Application.getApplication() here? The JavaFX Application class does not have a setOpenFileHandler method. Is that a Mac-specific API?
It's too bad that 8u40 won't have this. Being able to easily open double clicked files is pretty basic. Perhaps post 8u40 the JFX team could go through Scene Builder and identify everywhere it relies on internal APIs or custom magic and suck it into the core API, as then you'd have confidence that an app of real complexity can be done entirely with documented stuff.
Mike Hearn
2015-01-05 18:04:33 UTC
Permalink
Scene Builder doesn't do it this way - AFAICT you're only allowed to have
one such event handler registered with the OS and JavaFX already registers
one ... it just doesn't expose the resulting Java events via public API.
Post by Danno Ferrin
Oh, yes. Mac has it's own Applicaiton class. Here's the imports for the second file...
import com.apple.eawt.AppEvent;
import com.apple.eawt.Application;
import java.io.File;
import java.util.List;
import javax.swing.SwingUtilities;
The com.apple.eawt stuff is shipped with Oracle JRE and JDK, but like I
said in my previous mail you need to pass a flag to the compiler to turn
off it's index and read the jar.
The JavaDoc has gone MIA though --
https://bugs.openjdk.java.net/browse/JDK-8027638 but you can peruse the
source code to see the details, it's all OpenJDK -
http://hg.openjdk.java.net/jdk8u/jdk8u40/jdk/file/564bca490631/src/macosx/classes/com/apple/eawt
Post by Mike Hearn
What is Application.getApplication() here? The JavaFX Application class
does not have a setOpenFileHandler method. Is that a Mac-specific API?
Post by Mike Hearn
It's too bad that 8u40 won't have this. Being able to easily open double
clicked files is pretty basic. Perhaps post 8u40 the JFX team could go
through Scene Builder and identify everywhere it relies on internal APIs or
custom magic and suck it into the core API, as then you'd have confidence
that an app of real complexity can be done entirely with documented stuff.
Danno Ferrin
2015-01-05 19:59:40 UTC
Permalink
This code works inside of a JavaFX application too, just tried it locally.

You may be thinking of an older iteration of the apple application listener classes or perhaps the native level of the code.
Scene Builder doesn't do it this way - AFAICT you're only allowed to have one such event handler registered with the OS and JavaFX already registers one ... it just doesn't expose the resulting Java events via public API.
Oh, yes. Mac has it's own Applicaiton class. Here's the imports for the second file...
import com.apple.eawt.AppEvent;
import com.apple.eawt.Application;
import java.io.File;
import java.util.List;
import javax.swing.SwingUtilities;
The com.apple.eawt stuff is shipped with Oracle JRE and JDK, but like I said in my previous mail you need to pass a flag to the compiler to turn off it's index and read the jar.
The JavaDoc has gone MIA though -- https://bugs.openjdk.java.net/browse/JDK-8027638 but you can peruse the source code to see the details, it's all OpenJDK - http://hg.openjdk.java.net/jdk8u/jdk8u40/jdk/file/564bca490631/src/macosx/classes/com/apple/eawt
What is Application.getApplication() here? The JavaFX Application class does not have a setOpenFileHandler method. Is that a Mac-specific API?
It's too bad that 8u40 won't have this. Being able to easily open double clicked files is pretty basic. Perhaps post 8u40 the JFX team could go through Scene Builder and identify everywhere it relies on internal APIs or custom magic and suck it into the core API, as then you'd have confidence that an app of real complexity can be done entirely with documented stuff.
Mike Hearn
2015-01-05 20:09:47 UTC
Permalink
OK, then I might switch (or maybe not, as my current solution works ...)

BTW I noticed that the javapacker ISS changes put the file association
entries into HKEY_CLASSES_ROOT. Doing this results in an error half way
through setup if you don't run the installer with admin privs. The current
installer setup runs just fine with lowest privs, so it'd be perhaps nicer
to put the registry entries under HKEY_CURRENT_USER\SOFTWARE\Classes
instead:

http://support.microsoft.com/kb/257592
Post by Danno Ferrin
This code works inside of a JavaFX application too, just tried it locally.
You may be thinking of an older iteration of the apple application
listener classes or perhaps the native level of the code.
Scene Builder doesn't do it this way - AFAICT you're only allowed to have
one such event handler registered with the OS and JavaFX already registers
one ... it just doesn't expose the resulting Java events via public API.
Post by Danno Ferrin
Oh, yes. Mac has it's own Applicaiton class. Here's the imports for the second file...
import com.apple.eawt.AppEvent;
import com.apple.eawt.Application;
import java.io.File;
import java.util.List;
import javax.swing.SwingUtilities;
The com.apple.eawt stuff is shipped with Oracle JRE and JDK, but like I
said in my previous mail you need to pass a flag to the compiler to turn
off it's index and read the jar.
The JavaDoc has gone MIA though --
https://bugs.openjdk.java.net/browse/JDK-8027638 but you can peruse the
source code to see the details, it's all OpenJDK -
http://hg.openjdk.java.net/jdk8u/jdk8u40/jdk/file/564bca490631/src/macosx/classes/com/apple/eawt
Post by Mike Hearn
What is Application.getApplication() here? The JavaFX Application class
does not have a setOpenFileHandler method. Is that a Mac-specific API?
Post by Mike Hearn
It's too bad that 8u40 won't have this. Being able to easily open
double clicked files is pretty basic. Perhaps post 8u40 the JFX team could
go through Scene Builder and identify everywhere it relies on internal APIs
or custom magic and suck it into the core API, as then you'd have
confidence that an app of real complexity can be done entirely with
documented stuff.
Danno Ferrin
2015-01-05 20:25:03 UTC
Permalink
set the exe to install "System Wide" .. -Bwin.exe.systemWide=true via the CLI. This will cause the application to be installed with admin privleges.

Can you open a bug for this (just cut and paste the e-mail) so it will work for local installs?
Post by Mike Hearn
OK, then I might switch (or maybe not, as my current solution works ...)
http://support.microsoft.com/kb/257592
This code works inside of a JavaFX application too, just tried it locally.
You may be thinking of an older iteration of the apple application listener classes or perhaps the native level of the code.
Scene Builder doesn't do it this way - AFAICT you're only allowed to have one such event handler registered with the OS and JavaFX already registers one ... it just doesn't expose the resulting Java events via public API.
Oh, yes. Mac has it's own Applicaiton class. Here's the imports for the second file...
import com.apple.eawt.AppEvent;
import com.apple.eawt.Application;
import java.io.File;
import java.util.List;
import javax.swing.SwingUtilities;
The com.apple.eawt stuff is shipped with Oracle JRE and JDK, but like I said in my previous mail you need to pass a flag to the compiler to turn off it's index and read the jar.
The JavaDoc has gone MIA though -- https://bugs.openjdk.java.net/browse/JDK-8027638 but you can peruse the source code to see the details, it's all OpenJDK - http://hg.openjdk.java.net/jdk8u/jdk8u40/jdk/file/564bca490631/src/macosx/classes/com/apple/eawt
What is Application.getApplication() here? The JavaFX Application class does not have a setOpenFileHandler method. Is that a Mac-specific API?
It's too bad that 8u40 won't have this. Being able to easily open double clicked files is pretty basic. Perhaps post 8u40 the JFX team could go through Scene Builder and identify everywhere it relies on internal APIs or custom magic and suck it into the core API, as then you'd have confidence that an app of real complexity can be done entirely with documented stuff.
Mike Hearn
2015-01-05 21:30:33 UTC
Permalink
Done: https://javafx-jira.kenai.com/browse/RT-39763

My app works fine without admin privs so I just changed the registry keys
in the ISS to be the local user reg keys.
Post by Danno Ferrin
set the exe to install "System Wide" .. -Bwin.exe.systemWide=true via the
CLI. This will cause the application to be installed with admin privleges.
Can you open a bug for this (just cut and paste the e-mail) so it will
work for local installs?
OK, then I might switch (or maybe not, as my current solution works ...)
BTW I noticed that the javapacker ISS changes put the file association
entries into HKEY_CLASSES_ROOT. Doing this results in an error half way
through setup if you don't run the installer with admin privs. The current
installer setup runs just fine with lowest privs, so it'd be perhaps nicer
to put the registry entries under HKEY_CURRENT_USER\SOFTWARE\Classes
http://support.microsoft.com/kb/257592
Post by Danno Ferrin
This code works inside of a JavaFX application too, just tried it locally.
You may be thinking of an older iteration of the apple application
listener classes or perhaps the native level of the code.
Scene Builder doesn't do it this way - AFAICT you're only allowed to have
one such event handler registered with the OS and JavaFX already registers
one ... it just doesn't expose the resulting Java events via public API.
Post by Danno Ferrin
Oh, yes. Mac has it's own Applicaiton class. Here's the imports for
the second file...
import com.apple.eawt.AppEvent;
import com.apple.eawt.Application;
import java.io.File;
import java.util.List;
import javax.swing.SwingUtilities;
The com.apple.eawt stuff is shipped with Oracle JRE and JDK, but like I
said in my previous mail you need to pass a flag to the compiler to turn
off it's index and read the jar.
The JavaDoc has gone MIA though --
https://bugs.openjdk.java.net/browse/JDK-8027638 but you can peruse the
source code to see the details, it's all OpenJDK -
http://hg.openjdk.java.net/jdk8u/jdk8u40/jdk/file/564bca490631/src/macosx/classes/com/apple/eawt
Post by Mike Hearn
What is Application.getApplication() here? The JavaFX Application
class does not have a setOpenFileHandler method. Is that a Mac-specific API?
Post by Mike Hearn
It's too bad that 8u40 won't have this. Being able to easily open
double clicked files is pretty basic. Perhaps post 8u40 the JFX team could
go through Scene Builder and identify everywhere it relies on internal APIs
or custom magic and suck it into the core API, as then you'd have
confidence that an app of real complexity can be done entirely with
documented stuff.
David DeHaven
2015-01-05 21:52:42 UTC
Permalink
I went ahead and reassigned that to you Danno.

-DrD-
Post by Mike Hearn
Done: https://javafx-jira.kenai.com/browse/RT-39763
My app works fine without admin privs so I just changed the registry keys
in the ISS to be the local user reg keys.
Post by Danno Ferrin
set the exe to install "System Wide" .. -Bwin.exe.systemWide=true via the
CLI. This will cause the application to be installed with admin privleges.
Can you open a bug for this (just cut and paste the e-mail) so it will
work for local installs?
OK, then I might switch (or maybe not, as my current solution works ...)
BTW I noticed that the javapacker ISS changes put the file association
entries into HKEY_CLASSES_ROOT. Doing this results in an error half way
through setup if you don't run the installer with admin privs. The current
installer setup runs just fine with lowest privs, so it'd be perhaps nicer
to put the registry entries under HKEY_CURRENT_USER\SOFTWARE\Classes
http://support.microsoft.com/kb/257592
Post by Danno Ferrin
This code works inside of a JavaFX application too, just tried it locally.
You may be thinking of an older iteration of the apple application
listener classes or perhaps the native level of the code.
Scene Builder doesn't do it this way - AFAICT you're only allowed to have
one such event handler registered with the OS and JavaFX already registers
one ... it just doesn't expose the resulting Java events via public API.
Post by Danno Ferrin
Oh, yes. Mac has it's own Applicaiton class. Here's the imports for
the second file...
import com.apple.eawt.AppEvent;
import com.apple.eawt.Application;
import java.io.File;
import java.util.List;
import javax.swing.SwingUtilities;
The com.apple.eawt stuff is shipped with Oracle JRE and JDK, but like I
said in my previous mail you need to pass a flag to the compiler to turn
off it's index and read the jar.
The JavaDoc has gone MIA though --
https://bugs.openjdk.java.net/browse/JDK-8027638 but you can peruse the
source code to see the details, it's all OpenJDK -
http://hg.openjdk.java.net/jdk8u/jdk8u40/jdk/file/564bca490631/src/macosx/classes/com/apple/eawt
Post by Mike Hearn
What is Application.getApplication() here? The JavaFX Application
class does not have a setOpenFileHandler method. Is that a Mac-specific API?
Post by Mike Hearn
It's too bad that 8u40 won't have this. Being able to easily open
double clicked files is pretty basic. Perhaps post 8u40 the JFX team could
go through Scene Builder and identify everywhere it relies on internal APIs
or custom magic and suck it into the core API, as then you'd have
confidence that an app of real complexity can be done entirely with
documented stuff.
Loading...