Tutul
Tutul

Reputation: 129

javafx application name on gnome

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

Answers (3)

Terje Dahl
Terje Dahl

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:

  • One small class dropped into your project
  • Two small dependencies
  • Two values in your manifest
  • One additional arg to your java startup command
  • One environment variable in your startup-script

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

Eugene Smirnov
Eugene Smirnov

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

abd3lraouf
abd3lraouf

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

Related Questions