10

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

Beele
  • 199
  • 3
  • 11
  • 1
    *"This I think has todo with javafx appearently running is headless mode on OSX"* Why on Earth would it do that? It is a GUI toolkit, and they're not much use without a screen. – Andrew Thompson Mar 10 '13 at 09:51
  • I don't know, I've added a check in the code on github checking for GraphicsEnvironment.isHeadless() and that returns true, so... – Beele Mar 10 '13 at 09:55

2 Answers2

7

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

Sergey Grinev
  • 34,078
  • 10
  • 128
  • 141
  • Option 1 hides the warnings I'm getting on startup, but now the entire javafx app won't show on the screen. Option 2 is not what I need I guess. It renders parts of the interface to an image. I need to capture the entire desktop, not a part of the application. I guess for now that there's no option to make it work on osx... – Beele Mar 10 '13 at 11:28
  • It is possible to take a screenshot on mac os, don't worry. The answer exists. – Alexander Kirov Mar 10 '13 at 12:04
  • @Beele what version of JavaFX do you use? – Sergey Grinev Mar 10 '13 at 12:27
  • @SergeyGrinev javafx.runtime.version: 2.2.7-b01 – Beele Mar 10 '13 at 12:40
  • Shortly, decision is to launch an AWT robot in a separate VM. – Alexander Kirov Mar 11 '13 at 08:02
  • 1
    Do these workarounds only apply to JavaFX 2.2-? I see for Java 8, [Mac: Headless environment issue, MacOSX](http://javafx-jira.kenai.com/browse/RT-20784), was resolved as fixed, possibly meaning the workarounds in this answer aren't required if the target platform is Java 8+? (or a new alternative workaround could be listed => "Use Java 8+"). – jewelsea Mar 13 '13 at 01:13
4

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);
    }

}
Rainer Schwarze
  • 4,725
  • 1
  • 27
  • 49