-
Enhancement
-
Resolution: Unresolved
-
P4
-
8, 9, 10
-
x86
-
os_x
FULL PRODUCT VERSION :
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
10.12.6
A DESCRIPTION OF THE PROBLEM :
Having just ported an application from Swing to JavaFX I'm finding I have to use com.sun.* code to be able to received file-open events. The problem was described well in this post on the openJDK mailing list back in November of 2105:
http://mail.openjdk.java.net/pipermail/openjfx-dev/2015-November/018135.html. I've been unable to track down a bug filled for this, so I'm doing that now.
Copied from the mailing list:
Short version: I believe JavaFX is lacking some Mac-related APIs which
cannot be worked around without an internal com.sun.* call, and would
like to request that the APIs be added/made public for JDK 9, perhaps as
part of Jonathan Giles' work around JEP 253.
I stand to be corrected on any of this...
Long version: On Windows and Linux, files are generally opened via
command-line arguments. On Mac, there is a file-open event issued to an
application, e.g. you double-click a .txt file, TextEdit gets a
file-open event. If TextEdit is not open, it will be launched, then a
file-open event will be sent to it.
Pre-JavaFX, the Java solution to receive these events was to use the
com.apple.eawt.* packages, which Oracle added in JDK 7. However, it
seems that these APIs don't play nice with FX.
I've put some source code at the end of the email for a simple
application which I bundled with infinitekind's appbundler fork for
testing (https://bitbucket.org/infinitekind/appbundler/). No matter
where you uncomment the setEAWTFileHandler call (search AAA, or BBB in
the source), the .app always misses the initial load event which caused
the application to open. The net result is if the user double-clicks an
associated file, the JavaFX application will launch, but not load the
file in question unless the user double-clicks it again. Needless to
say, this is problematic.
If you look at the FX source code, you can see that FX does set a
file-open event handler with an empty implementation
(com.sun.glass.ui.Application, line 83 in 8u60). Grepping the source
code shows this method is not implemented anywhere in the source code,
so FX is simply discarding the event. The only Java way I've found to
let an FX application listen to the open files event is using a com.sun
call in the Application constructor (see CCC in the source, and
setFXFileHandler). Suggestions for a better work-around very welcome!
The second API is a similar problem. JavaFX installs an application
menu with standard options, including "Quit" (bound to Cmd-Q). If you
leave Platform.setImplicitExit to its default value of true, all is
well. Cmd-Q quits the application. However, if you
setImplicitExit(false) (see YYY in the source), as our actual
application must, problems arise. The quit handler now is a no-op, with
no way of overriding it. Pressing Cmd-Q or right-clicking the dock and
selecting quit is now totally ignored by the application, and from the
user's perspective the app is unquittable by these normal means. The
only work-around I've found is to set a quit handler in the same spot we
set the file open handler (see ZZZ in the source: I've used
Platform.exit for this example, in reality we have a nicer custom
shutdown routine).
My proposal is fairly straight-forward: the
com.sun.glass.ui.Application.EventHandler class (and associated
setEventHandler call) needs to become a public API in JDK 9, or else
these issues will be impossible to work around post-modularisation.
Initial file open and quit handler are the only ones biting us at the
moment, but I suspect that whole handler class should be public, either
as-is or by repurposing it into an EventHandler with some sort of
AppEvent extends Event class.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the attache project, then open with a file.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
See description.
ACTUAL -
See description.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
package example;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class OpenApp extends Application
{
private static ObservableList<File> requestedFiles =
FXCollections.observableArrayList();
public static void main(String[] args)
{
// AAA: Uncommenting here, we miss initial load events:
//setEAWTFileHandler();
launch(args);
}
@Override
public void start(Stage stage) throws Exception
{
// YYY: this causes problems.
//Platform.setImplicitExit(false);
// BBB: Here also misses initial load events:
//setEAWTFileHandler();
BorderPane bp = new BorderPane();
Label filename = new Label("");
bp.setCenter(filename);
Runnable updateFiles = () ->
filename.setText(requestedFiles.stream().map(File::getAbsolutePath).collect(Collectors.joining("\n")));
requestedFiles.addListener((ListChangeListener<? super File>)c
-> updateFiles.run());
updateFiles.run();
bp.setBottom(new Label("Example Application"));
MenuBar menuBar = new MenuBar(new Menu("My Custom Menu"));
menuBar.setUseSystemMenuBar(true);
bp.setTop(menuBar);
stage.setScene(new Scene(bp));
stage.show();
}
public OpenApp()
{
// CCC: Constructor is the only time we are at the right point in
// FX control flow.
//setFXFileHandler();
}
private static void setEAWTFileHandler()
{
com.apple.eawt.Application appleApp =
com.apple.eawt.Application.getApplication();
appleApp.setOpenFileHandler(e -> {
List<File> files = new ArrayList(e.getFiles());
Platform.runLater(() -> requestedFiles.addAll(e.getFiles()));
});
}
private static void setFXFileHandler()
{
com.sun.glass.ui.Application glassApp =
com.sun.glass.ui.Application.GetApplication();
glassApp.setEventHandler(new
com.sun.glass.ui.Application.EventHandler() {
@Override
public void
handleOpenFilesAction(com.sun.glass.ui.Application app, long time,
String[] filenames)
{
List<File> files =
Arrays.stream(filenames).map(File::new).collect(Collectors.toList());
Platform.runLater(() -> requestedFiles.addAll(files));
super.handleOpenFilesAction(app, time, filenames);
}
// ZZZ: add our own quit handler, too
/*
@Override
public void handleQuitAction(com.sun.glass.ui.Application
app, long time)
{
Platform.exit();
super.handleQuitAction(app, time);
}
*/
});
}
}
======
build.xml (needs appbundler in ant lib, built from
https://bitbucket.org/infinitekind/appbundler/)
======
<?xml version="1.0" encoding="UTF-8"?>
<project name="machandler" default="all">
<taskdef name="bundleapp"
classpath="appbundler-1.0ea.jar"
classname="com.oracle.appbundler.AppBundlerTask"/>
<target name="all">
<mkdir dir="classes"/>
<javac srcdir="src" destdir="classes">
<compilerarg value="-XDignore.symbol.file=true"/>
</javac>
<jar destfile="openapp.jar" basedir="classes"></jar>
<bundleapp
jvmrequired="1.8"
outputdirectory="."
name="OpenApp"
displayname="OpenApp"
executableName="OpenApp"
identifier="example.OpenApp"
shortversion="1.0"
version="1.0"
mainclassname="example.OpenApp">
<classpath file="openapp.jar"/>
<bundledocument extensions="dummyfx"
name="Dummy document"
role="editor">
</bundledocument>
</bundleapp>
</target>
</project>
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
To use com.sun.* API's, as shown in the example, and used in this project:
https://github.com/bitgamma/fx-platform-utils
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
10.12.6
A DESCRIPTION OF THE PROBLEM :
Having just ported an application from Swing to JavaFX I'm finding I have to use com.sun.* code to be able to received file-open events. The problem was described well in this post on the openJDK mailing list back in November of 2105:
http://mail.openjdk.java.net/pipermail/openjfx-dev/2015-November/018135.html. I've been unable to track down a bug filled for this, so I'm doing that now.
Copied from the mailing list:
Short version: I believe JavaFX is lacking some Mac-related APIs which
cannot be worked around without an internal com.sun.* call, and would
like to request that the APIs be added/made public for JDK 9, perhaps as
part of Jonathan Giles' work around JEP 253.
I stand to be corrected on any of this...
Long version: On Windows and Linux, files are generally opened via
command-line arguments. On Mac, there is a file-open event issued to an
application, e.g. you double-click a .txt file, TextEdit gets a
file-open event. If TextEdit is not open, it will be launched, then a
file-open event will be sent to it.
Pre-JavaFX, the Java solution to receive these events was to use the
com.apple.eawt.* packages, which Oracle added in JDK 7. However, it
seems that these APIs don't play nice with FX.
I've put some source code at the end of the email for a simple
application which I bundled with infinitekind's appbundler fork for
testing (https://bitbucket.org/infinitekind/appbundler/). No matter
where you uncomment the setEAWTFileHandler call (search AAA, or BBB in
the source), the .app always misses the initial load event which caused
the application to open. The net result is if the user double-clicks an
associated file, the JavaFX application will launch, but not load the
file in question unless the user double-clicks it again. Needless to
say, this is problematic.
If you look at the FX source code, you can see that FX does set a
file-open event handler with an empty implementation
(com.sun.glass.ui.Application, line 83 in 8u60). Grepping the source
code shows this method is not implemented anywhere in the source code,
so FX is simply discarding the event. The only Java way I've found to
let an FX application listen to the open files event is using a com.sun
call in the Application constructor (see CCC in the source, and
setFXFileHandler). Suggestions for a better work-around very welcome!
The second API is a similar problem. JavaFX installs an application
menu with standard options, including "Quit" (bound to Cmd-Q). If you
leave Platform.setImplicitExit to its default value of true, all is
well. Cmd-Q quits the application. However, if you
setImplicitExit(false) (see YYY in the source), as our actual
application must, problems arise. The quit handler now is a no-op, with
no way of overriding it. Pressing Cmd-Q or right-clicking the dock and
selecting quit is now totally ignored by the application, and from the
user's perspective the app is unquittable by these normal means. The
only work-around I've found is to set a quit handler in the same spot we
set the file open handler (see ZZZ in the source: I've used
Platform.exit for this example, in reality we have a nicer custom
shutdown routine).
My proposal is fairly straight-forward: the
com.sun.glass.ui.Application.EventHandler class (and associated
setEventHandler call) needs to become a public API in JDK 9, or else
these issues will be impossible to work around post-modularisation.
Initial file open and quit handler are the only ones biting us at the
moment, but I suspect that whole handler class should be public, either
as-is or by repurposing it into an EventHandler with some sort of
AppEvent extends Event class.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the attache project, then open with a file.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
See description.
ACTUAL -
See description.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
package example;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class OpenApp extends Application
{
private static ObservableList<File> requestedFiles =
FXCollections.observableArrayList();
public static void main(String[] args)
{
// AAA: Uncommenting here, we miss initial load events:
//setEAWTFileHandler();
launch(args);
}
@Override
public void start(Stage stage) throws Exception
{
// YYY: this causes problems.
//Platform.setImplicitExit(false);
// BBB: Here also misses initial load events:
//setEAWTFileHandler();
BorderPane bp = new BorderPane();
Label filename = new Label("");
bp.setCenter(filename);
Runnable updateFiles = () ->
filename.setText(requestedFiles.stream().map(File::getAbsolutePath).collect(Collectors.joining("\n")));
requestedFiles.addListener((ListChangeListener<? super File>)c
-> updateFiles.run());
updateFiles.run();
bp.setBottom(new Label("Example Application"));
MenuBar menuBar = new MenuBar(new Menu("My Custom Menu"));
menuBar.setUseSystemMenuBar(true);
bp.setTop(menuBar);
stage.setScene(new Scene(bp));
stage.show();
}
public OpenApp()
{
// CCC: Constructor is the only time we are at the right point in
// FX control flow.
//setFXFileHandler();
}
private static void setEAWTFileHandler()
{
com.apple.eawt.Application appleApp =
com.apple.eawt.Application.getApplication();
appleApp.setOpenFileHandler(e -> {
List<File> files = new ArrayList(e.getFiles());
Platform.runLater(() -> requestedFiles.addAll(e.getFiles()));
});
}
private static void setFXFileHandler()
{
com.sun.glass.ui.Application glassApp =
com.sun.glass.ui.Application.GetApplication();
glassApp.setEventHandler(new
com.sun.glass.ui.Application.EventHandler() {
@Override
public void
handleOpenFilesAction(com.sun.glass.ui.Application app, long time,
String[] filenames)
{
List<File> files =
Arrays.stream(filenames).map(File::new).collect(Collectors.toList());
Platform.runLater(() -> requestedFiles.addAll(files));
super.handleOpenFilesAction(app, time, filenames);
}
// ZZZ: add our own quit handler, too
/*
@Override
public void handleQuitAction(com.sun.glass.ui.Application
app, long time)
{
Platform.exit();
super.handleQuitAction(app, time);
}
*/
});
}
}
======
build.xml (needs appbundler in ant lib, built from
https://bitbucket.org/infinitekind/appbundler/)
======
<?xml version="1.0" encoding="UTF-8"?>
<project name="machandler" default="all">
<taskdef name="bundleapp"
classpath="appbundler-1.0ea.jar"
classname="com.oracle.appbundler.AppBundlerTask"/>
<target name="all">
<mkdir dir="classes"/>
<javac srcdir="src" destdir="classes">
<compilerarg value="-XDignore.symbol.file=true"/>
</javac>
<jar destfile="openapp.jar" basedir="classes"></jar>
<bundleapp
jvmrequired="1.8"
outputdirectory="."
name="OpenApp"
displayname="OpenApp"
executableName="OpenApp"
identifier="example.OpenApp"
shortversion="1.0"
version="1.0"
mainclassname="example.OpenApp">
<classpath file="openapp.jar"/>
<bundledocument extensions="dummyfx"
name="Dummy document"
role="editor">
</bundledocument>
</bundleapp>
</target>
</project>
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
To use com.sun.* API's, as shown in the example, and used in this project:
https://github.com/bitgamma/fx-platform-utils
- relates to
-
JDK-8091107 Add java.awt.Desktop support to javafx
- Open
-
JDK-8091517 Implement jep272 APIs that make sense in JavaFX
- Open