JavaFX screencapture headless exception on OSX

I'm converting my old java app from swing to javafx and I'm running into a problem.

I'm using the following code to capture screenshots:

 public ScreenCapper() {
    ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    gs = ge.getScreenDevices();

    try {
        robot = new Robot(gs[gs.length-1]);
    } catch (AWTException e) {
        LOGGER.getInstance().ERROR("Error creating screenshot robot instance!");
    }
}

public Color capture() {
    Rectangle bounds;

    mode = gs[0].getDisplayMode();
    bounds = new Rectangle(0, 0, mode.getWidth(), mode.getHeight());
    //......
}

This works fine when running the application under Windows. However when running under OSX in get the following exception:

Exception in Application start method
Exception in thread "main" java.lang.RuntimeException: Exception in Application start method
at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:403)
at com.sun.javafx.application.LauncherImpl.access$000(LauncherImpl.java:47)
at com.sun.javafx.application.LauncherImpl$1.run(LauncherImpl.java:115)
at java.lang.Thread.run(Thread.java:722)
Caused by: java.awt.HeadlessException
at sun.java2d.HeadlessGraphicsEnvironment.getScreenDevices(HeadlessGraphicsEnvironment.java:72)
at be.beeles_place.roggbiv.utils.ScreenCapper.<init>(ScreenCapper.java:33)
at be.beeles_place.roggbiv.modes.AverageColorMode.start(AverageColorMode.java:31)
at be.beeles_place.roggbiv.modes.ColorModeContext.startCurrentColorMode(ColorModeContext.java:28)
at be.beeles_place.roggbiv.controller.RoggbivController.<init>(RoggbivController.java:42)
at be.beeles_place.roggbiv.RoggbivMain.start(RoggbivMain.java:67)
at com.sun.javafx.application.LauncherImpl$5.run(LauncherImpl.java:319)
at com.sun.javafx.application.PlatformImpl$5.run(PlatformImpl.java:215)
at com.sun.javafx.application.PlatformImpl$4$1.run(PlatformImpl.java:179)
at com.sun.javafx.application.PlatformImpl$4$1.run(PlatformImpl.java:176)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl$4.run(PlatformImpl.java:176)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:76)

This I think has todo with javafx appearently running is headless mode on OSX, as the following debug warnings suggest:

013-03-10 10:44:03.795 java[1912:5903] *** WARNING: Method userSpaceScaleFactor in class NSView is deprecated on 10.7 and later. It should not be used in new applications. Use convertRectToBacking: instead. 
2013-03-10 10:44:05.472 java[1912:707] [JRSAppKitAWT markAppIsDaemon]: Process manager already initialized: can't fully enable headless mode.

Is there any way to get this to work? Or another way to capture screenshots that does not conflict with OSX?

full code @ https://github.com/beele/Roggbiv


JavaFX doesn't use AWT stack, so it's not being started in pure JavaFX application. Due to threads handling specifics AWT is being run in headless mode on Mac then requested from JavaFX.

There are next options to solve that:

  1. Use some voodoo magic to initialize AWT -- in static initialization run java.awt.Toolkit.getDefaultToolkit(); EDIT this worked only in older JavaFX, sorry

  2. Better options would be to opt out of using AWT from JavaFX. You can use next functionality to make screenshots: http://docs.oracle.com/javafx/2/api/javafx/scene/Node.html#snapshot%28javafx.util.Callback,%20javafx.scene.SnapshotParameters,%20javafx.scene.image.WritableImage%29

  3. EDIT As Alexander pointed out another way is to run AWT code in a separate VM. To achieve that you can refactor your screenshot functionality to a separate class and call it from JavaFX app by:

        new ProcessBuilder(
              System.getProperty("java.home") + "/bin/java", 
              "-cp", "classpath", 
              "my.apps.DoScreenshot"
        ).start();
    

    This app can store screenshot to a filesystem. If you need to do screenshots often and met performance issues you can run that separate app once and communicate with it through socket.

  4. Use com.sun.glass.ui.Robot instead of AWTRobot


I see the following things which may need attention:

  1. In addition to Sergey Grinev's point 1. set javafx.macosx.embedded:

    System.setProperty("javafx.macosx.embedded", "true");
    java.awt.Toolkit.getDefaultToolkit();
    
  2. Take care that AWT stuff is done in the EDT and JavaFX stuff is done in JavaFX Application thread.

I was dealing with JavaFX/Swing issues on a Mac recently, so this got me interested. If you try the code below, does it work for you? (It should put a scaled screen shot as the background of the app window.)

import java.awt.AWTException;
import java.awt.DisplayMode;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

import javax.swing.SwingUtilities;

public class SO extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        final Pane pane = new StackPane();
        Scene scene = new Scene(pane, 600, 300);
        stage.setScene(scene);
        Button b = new Button("Snap");
        final ImageView iv = new ImageView();
        iv.fitWidthProperty().bind(pane.widthProperty());
        iv.fitHeightProperty().bind(pane.heightProperty());
        pane.getChildren().add(iv);
        pane.getChildren().add(b);
        b.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        doSnap(iv);
                    }
                });
            }
        });
        stage.show();
    }

    protected void doSnap(final ImageView iv) {
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice[] gs = ge.getScreenDevices();

        Robot robot = null;
        try {
            robot = new Robot(gs[gs.length-1]);
        } catch (AWTException e) {
            e.printStackTrace();
            return;
        }
        DisplayMode mode = gs[0].getDisplayMode();
        Rectangle bounds = new Rectangle(0, 0, mode.getWidth(), mode.getHeight());
        final BufferedImage bi = robot.createScreenCapture(bounds);
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                Image im = SwingFXUtils.toFXImage(bi, null);
                iv.setImage(im);
            }
        });
    }

    public static void main(String[] args) {
        System.setProperty("javafx.macosx.embedded", "true");
        java.awt.Toolkit.getDefaultToolkit();
        Application.launch(args);
    }

}