Reputation: 129
I help to develop an application with JavaFX for linux and OSX. On Linux, we cannot have the application name on the gnome top bar. We have the entry point for JavaFX. The window have the good name but on gnome we have something like "com.myApp.javaFXMainClass".
I have the same problem with swing and I was able to correct it with these code :
// Set name in system menubar for Gnome (and Linux)
if (System.getProperty("os.name").toLowerCase().contains("linux")) {
try {
Toolkit xToolkit = Toolkit.getDefaultToolkit();
Field awtAppClassNameField = xToolkit.getClass().getDeclaredField("awtAppClassName");
awtAppClassNameField.setAccessible(true);
awtAppClassNameField.set(xToolkit, "MyApp");
} catch (Exception e) {
// TODO
}
}
How to do that with JavaFX ?
Upvotes: 9
Views: 1903
Reputation: 982
Here is a workaround/solution which should be relatively easy to use for anyone packaging their JavaFX application in a jar using some sort of build-tool (e.g. Maven, Gradle, Leiningen) which supports Maven dependency style coordinates:
See/get the source here: no.andante.george.Agent
(Detailed usage/help embedded in the source.)
Hope it is useful.
(Do let me know if you were able to use it successfully in a comment bellow.)
Understanding the problem and this solution
The problem is a double whammy!
Digging into the OpenJDK source you will find in 'sun.awt.X11.XToolkit' (which extends 'sun.awt.UnixToolkit' which extends 'sun.awt.SunToolkit' which extends 'java.awt.Toolkit'):
private static String awtAppClassName = null;
If you set this variable to whatever you want (with introspection) early enough (e.g. at the beginning of your main
method), your value will get picked up and passed on to the underlying GTK system when the Toolkit windowing system is initialized. This is what e.g. IntelliJ does in com.intellij.ui.AppUIUtil
However, digging into the OpenJFX source you will find in 'com.sun.class.ui.Application':
private final static String DEFAULT_NAME = "java";
protected String name = DEFAULT_NAME;
Looking at this, your first idea might be to do
com.sun.javafx.application.PlatformImpl.setApplicationName(..)
.
But that won't work because it is too late! The name
variable was set at class instantiation and then passed on to the underlying GTK system before your code got called.
So then you try to use introspection to set the constant DEFAULT_NAME
to what you want, and so that will get used to instantiate the variable name
. But that also doesn't work! Why?
Because of the keyword final
(which is the most significant difference between the Toolkit and the Application implementation)!
Because it is final static and is assigned the value "java"
, the value is "inlined" at compile time. I.e. it is inserted into the "constant pool", and all references to the constant DEFAULT_NAME
are replaced with a reference to the constant pool. And so, even if you set the constant at runtime, it will be ignored, and any code (in this case the constructor) will simply set the variable name
to the value which was compiled into the constant pool.
I don't think you can alter the constant pool or the body of the constructor with introspection. If you can, I would like to know how!
Enter ASM
But if you can rewrite the actual byte-code of the class before it gets loaded by the JVM, then you can do pretty much anything!
So I chose to write a small "Java Agent" (think of it as JVM middleware). All it does is look for the environment variable 'APPLICATION_NAME', and if found it will look for the class 'com.sun.glass.ui.Application' and transform it slightly before it is passed on to the JVM and the class loader:
First, it sets the constant DEFAULT_NAME
to the value of 'APPLICATION_NAME'. Then, it alters the body of the Application constructor such that the variable name
is assigned the value of DEFAULT_NAME
(instead of the value in the constant pool). Finally, it disables the method setName
(by removing its body).
Hope this is interesting and possibly useful to someone. Please do see (and use) my solution linked at the top of this answer. Feedback is welcomed.
Upvotes: 1
Reputation: 91
package test;
import javafx.application.Preloader;
import javafx.stage.Stage;
public class TestPre extends Preloader {
@Override
public void start(Stage stage) throws Exception {
com.sun.glass.ui.Application.GetApplication().setName("app test");
}
}
package test;
import java.io.ByteArrayInputStream;
import java.util.Base64;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Test extends Application {
@Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
Test __app = new Test();
Stage __stage = new Stage();
__app.start(__stage);
}
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 300, 250);
String __simage = "iVBORw0K.....";
ByteArrayInputStream __imgstream = new ByteArrayInputStream(Base64.getDecoder().decode(__simage));
javafx.scene.image.Image __image = new javafx.scene.image.Image(__imgstream);
primaryStage.getIcons().add(__image);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
com.sun.javafx.application.LauncherImpl.launchApplication(Test.class, TestPre.class, args);
}
}
Upvotes: 9
Reputation: 1658
This bug has already been reported here but I couldn't get it working so mt final resort was to create an awt
application as a bootstrap to JavaFx
and it's working like charm
Code snippet @gitlab
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javax.swing.*;
import java.awt.*;
import java.lang.reflect.Field;
public class CustomJavaFxAppName {
private void display() {
JFrame f = new JFrame("CustomJavaFxAppName");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JFXPanel jfxPanel = new JFXPanel() {
@Override
public Dimension getPreferredSize() {
return new Dimension(320, 240);
}
};
initJFXPanel(jfxPanel);
f.add(jfxPanel);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private void initJFXPanel(JFXPanel jfxPanel) {
Platform.runLater(() -> {
javafx.scene.control.Label label = new javafx.scene.control.Label(
System.getProperty("os.name") + " v"
+ System.getProperty("os.version") + "; Java v"
+ System.getProperty("java.version"));
StackPane root = new StackPane(label);
Scene scene = new Scene(root);
jfxPanel.setScene(scene);
});
if (System.getProperty("os.name").toLowerCase().contains("linux")) {
try {
Toolkit xToolkit = Toolkit.getDefaultToolkit();
Field awtAppClassNameField = xToolkit.getClass().getDeclaredField("awtAppClassName");
awtAppClassNameField.setAccessible(true);
awtAppClassNameField.set(xToolkit, "MyApp");
} catch (Exception ignored) { }
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new CustomJavaFxAppName()::display);
}
}
Upvotes: 1