Application name javafx on gnome
I am helping develop an application with JavaFX for Linux and OSX. On Linux we cannot have the application name in the gnome top panel. We have an entry point for JavaFX. The window has a nice name, but on gnome we have something like "com.myApp.javaFXMainClass".
I have the same swing problem and I was able to fix it with this 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 do I do this with JavaFX?
source to share
This bug was already reported here, but I couldn't get it to work, so ultimately awt
mt was to create an app awt
as a bootstrap for JavaFx
and it worked like a charming
- Read about integrating JavaFX into Swing applications. Integrating JavaFX into Swing Applications.
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);
}
}
source to share
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);
}
}
source to share
Here is a workaround / solution that should be relatively easy to use for anyone packaging their JavaFX application in a jar using some build tool (like Maven, Gradle, Leiningen) that supports Maven dependency style coordinates:
- One little class fell into your project
- Two small dependencies
- Two values ββin your manifest
- One extra argument to your Java startup command
- One environment variable in your startup script
View / get source here: no.andante.george.Agent
(Detailed usage / help built into source.)
Hope this is helpful.
(Let me know if you were able to use it successfully in the comment below.)
Understanding the problem and this solution
The problem is the double hit!
Digging into the OpenJDK source code, 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 (through introspection) early enough (for example, at the beginning of your main
method), your value will be fetched and passed to the underlying GTK system when the Toolkit windowing system is initialized. This is what IntelliJ for example does in com.intellij.ui.AppUIUtil
However, if you dig deeper into the OpenJFX source code, you will find it at 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 it won't work because it's too late! The variable name
was set on an instance of the class and then passed to the underlying GTK system before your code is called.
So you're trying to use introspection to set the constant two DEFAULT_NAME
what you want and so that the instance of the variable gets used to name
. But that doesn't work either! What for?
Because of the keyword final
(which is the most significant difference between Toolkit and application implementation)!
Because it is final static and is assigned a value "java"
, that value is "inlined" at compile time. Those. it is inserted into the "persistent pool" and any constant DEFAULT_NAME
references are replaced with a persistent pool reference. And so, even if you set a constant at runtime, it will be ignored and any code (in this case, the constructor) will simply set the name
variable to the value that was compiled into the constant pool.
I don't think you can change the constant pool or constructor body through introspection. If you can, I would like to know how!
Enter ASM
But if you can rewrite the actual bytecode of the class before it is loaded by the JVM, then you can do pretty much anything!
So I decided to write a little "Java agent" (think of it as JVM middleware). All it does is look for the APPLICATION_NAME environment variable, and if found, it looks for the com.sun.glass.ui.Application class and slightly transforms it before passing it to the JVM and classloader:
First, the constant is DEFAULT_NAME
set to "APPLICATION_NAME". It then modifies the body of the application constructor so that the variable is name
assigned the value two DEFAULT_NAME
(instead of the value in the constant pool). Finally, it disables the method setName
(by deleting its body).
Hope this is interesting and maybe helpful for someone. Please see (and use) my solution linked at the top of this answer. Feedback is appreciated.
source to share