/* * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javafx.embed.swing; import javafx.beans.InvalidationListener; import javafx.beans.value.ChangeListener; import javafx.event.Event; import javafx.event.EventHandler; import javafx.event.EventType; import javafx.geometry.Point2D; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; import javafx.scene.input.ScrollEvent; import javafx.stage.Window; import javax.swing.JComponent; import javax.swing.Timer; import java.awt.AWTEvent; import java.awt.Component; import java.awt.Cursor; import java.awt.EventQueue; import java.awt.Toolkit; import java.lang.ref.WeakReference; import java.awt.dnd.DragGestureEvent; import java.awt.dnd.DragGestureListener; import java.awt.dnd.DragGestureRecognizer; import java.awt.dnd.DragSource; import java.awt.dnd.DropTarget; import java.awt.dnd.InvalidDnDOperationException; import java.awt.event.InputEvent; import java.awt.event.MouseWheelEvent; import java.awt.event.WindowEvent; import java.awt.event.WindowFocusListener; import java.lang.reflect.Method; import java.nio.IntBuffer; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.locks.ReentrantLock; import com.sun.javafx.embed.swing.Disposer; import com.sun.javafx.embed.swing.DisposerRecord; import com.sun.javafx.geom.BaseBounds; import com.sun.javafx.geom.transform.BaseTransform; import com.sun.javafx.scene.DirtyBits; import com.sun.javafx.sg.prism.NGExternalNode; import com.sun.javafx.sg.prism.NGNode; import com.sun.javafx.stage.FocusUngrabEvent; import com.sun.javafx.stage.WindowHelper; import com.sun.javafx.tk.TKStage; import com.sun.javafx.PlatformUtil; import com.sun.javafx.embed.swing.SwingNodeHelper; import com.sun.javafx.scene.NodeHelper; import com.sun.javafx.embed.swing.SwingEvents; import com.sun.javafx.embed.swing.SwingCursors; import static javafx.stage.WindowEvent.WINDOW_HIDDEN; import com.sun.javafx.embed.swing.InteropFactory; import com.sun.javafx.embed.swing.SwingNodeInterop; /** * This class is used to embed a Swing content into a JavaFX application. * The content to be displayed is specified with the {@link #setContent} method * that accepts an instance of Swing {@code JComponent}. The hierarchy of components * contained in the {@code JComponent} instance should not contain any heavyweight * components, otherwise {@code SwingNode} may fail to paint it. The content gets * repainted automatically. All the input and focus events are forwarded to the * {@code JComponent} instance transparently to the developer. *

* Here is a typical pattern which demonstrates how {@code SwingNode} can be used: *

 *     public class SwingFx extends Application {
 *
 *         @Override
 *         public void start(Stage stage) {
 *             final SwingNode swingNode = new SwingNode();
 *             createAndSetSwingContent(swingNode);
 *
 *             StackPane pane = new StackPane();
 *             pane.getChildren().add(swingNode);
 *
 *             stage.setScene(new Scene(pane, 100, 50));
 *             stage.show();
 *         }
 *
 *         private void createAndSetSwingContent(final SwingNode swingNode) {
 *             SwingUtilities.invokeLater(new Runnable() {
 *                 @Override
 *                 public void run() {
 *                     swingNode.setContent(new JButton("Click me!"));
 *                 }
 *             });
 *         }
 *
 *         public static void main(String[] args) {
 *             launch(args);
 *         }
 *     }
 * 
* @since JavaFX 8.0 */ public class SwingNode extends Node { private static boolean isThreadMerged; private static SwingNodeInterop swiop; static { InteropFactory instance = null; try { instance = InteropFactory.getInstance(); } catch (Exception e) { throw new ExceptionInInitializerError(e); } swiop = instance.createSwingNodeImpl(); AccessController.doPrivileged(new PrivilegedAction() { public Object run() { isThreadMerged = Boolean.valueOf( System.getProperty("javafx.embed.singleThread")); return null; } }); // This is used by classes in different packages to get access to // private and package private methods. SwingNodeHelper.setSwingNodeAccessor(new SwingNodeHelper.SwingNodeAccessor() { @Override public NGNode doCreatePeer(Node node) { return ((SwingNode) node).doCreatePeer(); } @Override public void doUpdatePeer(Node node) { ((SwingNode) node).doUpdatePeer(); } @Override public BaseBounds doComputeGeomBounds(Node node, BaseBounds bounds, BaseTransform tx) { return ((SwingNode) node).doComputeGeomBounds(bounds, tx); } @Override public boolean doComputeContains(Node node, double localX, double localY) { return ((SwingNode) node).doComputeContains(localX, localY); } }); } private double fxWidth; private double fxHeight; public int swingPrefWidth; public int swingPrefHeight; public int swingMaxWidth; public int swingMaxHeight; public int swingMinWidth; public int swingMinHeight; private volatile JComponent content; private volatile Object lwFrame; public final Object getLightweightFrame() { return lwFrame; } private NGExternalNode peer; public final ReentrantLock paintLock = new ReentrantLock(); private boolean skipBackwardUnrgabNotification; public boolean grabbed; // lwframe initiated grab { // To initialize the class helper at the begining each constructor of this class SwingNodeHelper.initHelper(this); } /** * Constructs a new instance of {@code SwingNode}. */ public SwingNode() { setFocusTraversable(true); setEventHandler(MouseEvent.ANY, new SwingMouseEventHandler()); setEventHandler(KeyEvent.ANY, new SwingKeyEventHandler()); setEventHandler(ScrollEvent.SCROLL, new SwingScrollEventHandler()); focusedProperty().addListener((observable, oldValue, newValue) -> { swiop.activateLwFrame(lwFrame, newValue); }); //Workaround for RT-34170 javafx.scene.text.Font.getFamilies(); } private EventHandler windowHiddenHandler = (Event event) -> { if (lwFrame != null && event.getTarget() instanceof Window) { final Window w = (Window) event.getTarget(); TKStage tk = WindowHelper.getPeer(w); if (tk != null) { if (isThreadMerged) { swiop.overrideNativeWindowHandle(0L, null); } else { // Postpone actual window closing to ensure that // a native window handler is valid on a Swing side tk.postponeClose(); SwingFXUtils.runOnEDT(() -> { swiop.overrideNativeWindowHandle(0L, (Runnable) () -> SwingFXUtils.runOnFxThread( () -> tk.closePostponed())); }); } } } }; private Window hWindow = null; private void notifyNativeHandle(Window window) { if (hWindow != window) { if (hWindow != null) { hWindow.removeEventHandler(WINDOW_HIDDEN, windowHiddenHandler); } if (window != null) { window.addEventHandler(WINDOW_HIDDEN, windowHiddenHandler); } hWindow = window; } if (lwFrame != null) { long rawHandle = 0L; if (window != null) { TKStage tkStage = WindowHelper.getPeer(window); if (tkStage != null) { rawHandle = tkStage.getRawHandle(); } } swiop.overrideNativeWindowHandle(rawHandle, null); } } /** * Attaches a {@code JComponent} instance to display in this {@code SwingNode}. *

* The method can be called either on the JavaFX Application thread or the Event Dispatch thread. * Note however, that access to a Swing component must occur from the Event Dispatch thread * according to the Swing threading restrictions. * * @param content a Swing component to display in this {@code SwingNode} * * @see java.awt.EventQueue#isDispatchThread() * @see javafx.application.Platform#isFxApplicationThread() */ public void setContent(final JComponent content) { this.content = content; SwingFXUtils.runOnEDT(() -> setContentImpl(content)); } /** * Returns the {@code JComponent} instance attached to this {@code SwingNode}. *

* The method can be called either on the JavaFX Application thread or the Event Dispatch thread. * Note however, that access to a Swing component must occur from the Event Dispatch thread * according to the Swing threading restrictions. * * @see java.awt.EventQueue#isDispatchThread() * @see javafx.application.Platform#isFxApplicationThread() * * @return the Swing component attached to this {@code SwingNode} */ public JComponent getContent() { return content; } private static final class OptionalMethod { private final Method method; private final boolean isIntegerAPI; OptionalMethod(Class cls, String name, Class... args) { Method m; try { m = cls.getMethod(name, args); } catch (NoSuchMethodException ignored) { // This means we're running with older JDK, simply skip the call m = null; } catch (Throwable ex) { throw new RuntimeException("Error when calling " + cls.getName() + ".getMethod('" + name + "').", ex); } method = m; isIntegerAPI = args != null && args.length > 0 && args[0] == Integer.TYPE; } public boolean isSupported() { return method != null; } public boolean isIntegerApi() { return isIntegerAPI; } public Object invoke(T object, Object... args) { if (method != null) { try { return method.invoke(object, args); } catch (Throwable ex) { throw new RuntimeException("Error when calling " + object.getClass().getName() + "." + method.getName() + "().", ex); } } else { return null; } } } /* * Called on EDT */ private void setContentImpl(JComponent content) { if (lwFrame != null) { swiop.disposeLwFrame(); lwFrame = null; } if (content != null) { lwFrame = swiop.createLightweightFrame(); SwingNodeWindowFocusListener snfListener = new SwingNodeWindowFocusListener(this); swiop.addWindowFocusListener(lwFrame, snfListener); if (getScene() != null) { Window window = getScene().getWindow(); if (window != null) { swiop.notifyDisplayChanged(lwFrame, window); } } swiop.setContent(lwFrame, swiop.createSwingNodeContent(content, this)); swiop.setVisible(lwFrame, true); Disposer.addRecord(this, swiop.createSwingNodeDisposer(lwFrame)); if (getScene() != null) { notifyNativeHandle(getScene().getWindow()); } SwingFXUtils.runOnFxThread(() -> { locateLwFrame();// initialize location if (focusedProperty().get()) { swiop.activateLwFrame(lwFrame, true); } }); } } private List peerRequests = new ArrayList<>(); /* * Called on EDT */ public void setImageBuffer(final int[] data, final int x, final int y, final int w, final int h, final int linestride, final double scaleX, final double scaleY) { Runnable r = () -> peer.setImageBuffer(IntBuffer.wrap(data), x, y, w, h, w, h, linestride, scaleX, scaleY); SwingFXUtils.runOnFxThread(() -> { if (peer != null) { r.run(); } else { peerRequests.clear(); peerRequests.add(r); } }); } /* * Called on EDT */ public void setImageBounds(final int x, final int y, final int w, final int h) { Runnable r = () -> peer.setImageBounds(x, y, w, h, w, h); SwingFXUtils.runOnFxThread(() -> { if (peer != null) { r.run(); } else { peerRequests.add(r); } }); } /* * Called on EDT */ public void repaintDirtyRegion(final int dirtyX, final int dirtyY, final int dirtyWidth, final int dirtyHeight) { Runnable r = () -> { peer.repaintDirtyRegion(dirtyX, dirtyY, dirtyWidth, dirtyHeight); NodeHelper.markDirty(this, DirtyBits.NODE_CONTENTS); }; SwingFXUtils.runOnFxThread(() -> { if (peer != null) { r.run(); } else { peerRequests.add(r); } }); } @Override public boolean isResizable() { return true; } /** * Invoked by the {@code SwingNode}'s parent during layout to set the {@code SwingNode}'s * width and height. Applications should not invoke this method directly. * If an application needs to directly set the size of the {@code SwingNode}, it should * set the Swing component's minimum/preferred/maximum size constraints which will * be propagated correspondingly to the {@code SwingNode} and it's parent will honor those * settings during layout. * * @param width the target layout bounds width * @param height the target layout bounds height */ @Override public void resize(final double width, final double height) { super.resize(width, height); if (width != this.fxWidth || height != this.fxHeight) { this.fxWidth = width; this.fxHeight = height; NodeHelper.geomChanged(this); NodeHelper.markDirty(this, DirtyBits.NODE_GEOMETRY); SwingFXUtils.runOnEDT(() -> { if (lwFrame != null) { locateLwFrame(); } }); } } /** * Returns the {@code SwingNode}'s preferred width for use in layout calculations. * This value corresponds to the preferred width of the Swing component. * * @return the preferred width that the node should be resized to during layout */ @Override public double prefWidth(double height) { return swingPrefWidth; } /** * Returns the {@code SwingNode}'s preferred height for use in layout calculations. * This value corresponds to the preferred height of the Swing component. * * @return the preferred height that the node should be resized to during layout */ @Override public double prefHeight(double width) { return swingPrefHeight; } /** * Returns the {@code SwingNode}'s maximum width for use in layout calculations. * This value corresponds to the maximum width of the Swing component. * * @return the maximum width that the node should be resized to during layout */ @Override public double maxWidth(double height) { return swingMaxWidth; } /** * Returns the {@code SwingNode}'s maximum height for use in layout calculations. * This value corresponds to the maximum height of the Swing component. * * @return the maximum height that the node should be resized to during layout */ @Override public double maxHeight(double width) { return swingMaxHeight; } /** * Returns the {@code SwingNode}'s minimum width for use in layout calculations. * This value corresponds to the minimum width of the Swing component. * * @return the minimum width that the node should be resized to during layout */ @Override public double minWidth(double height) { return swingMinWidth; } /** * Returns the {@code SwingNode}'s minimum height for use in layout calculations. * This value corresponds to the minimum height of the Swing component. * * @return the minimum height that the node should be resized to during layout */ @Override public double minHeight(double width) { return swingMinHeight; } /* * Note: This method MUST only be called via its accessor method. */ private boolean doComputeContains(double localX, double localY) { return true; } private final InvalidationListener locationListener = observable -> { locateLwFrame(); }; private final EventHandler ungrabHandler = event -> { if (!skipBackwardUnrgabNotification) { if (lwFrame != null) { AccessController.doPrivileged(new PostEventAction( swiop.createUngrabEvent(lwFrame))); } } }; private final ChangeListener windowVisibleListener = (observable, oldValue, newValue) -> { if (!newValue) { swiop.disposeLwFrame(); } else { setContent(content); } }; private final ChangeListener sceneWindowListener = (observable, oldValue, newValue) -> { if (oldValue != null) { removeWindowListeners(oldValue); } notifyNativeHandle(newValue); if (newValue != null) { addWindowListeners(newValue); } }; private void removeSceneListeners(Scene scene) { Window window = scene.getWindow(); if (window != null) { removeWindowListeners(window); } scene.windowProperty().removeListener(sceneWindowListener); } private void addSceneListeners(final Scene scene) { Window window = scene.getWindow(); if (window != null) { addWindowListeners(window); notifyNativeHandle(window); } scene.windowProperty().addListener(sceneWindowListener); } private void addWindowListeners(final Window window) { window.xProperty().addListener(locationListener); window.yProperty().addListener(locationListener); window.widthProperty().addListener(locationListener); window.heightProperty().addListener(locationListener); window.renderScaleXProperty().addListener(locationListener); window.addEventHandler(FocusUngrabEvent.FOCUS_UNGRAB, ungrabHandler); window.showingProperty().addListener(windowVisibleListener); swiop.setLwFrameScale(window.getRenderScaleX(), window.getRenderScaleY()); } private void removeWindowListeners(final Window window) { window.xProperty().removeListener(locationListener); window.yProperty().removeListener(locationListener); window.widthProperty().removeListener(locationListener); window.heightProperty().removeListener(locationListener); window.renderScaleXProperty().removeListener(locationListener); window.removeEventHandler(FocusUngrabEvent.FOCUS_UNGRAB, ungrabHandler); window.showingProperty().removeListener(windowVisibleListener); } /* * Note: This method MUST only be called via its accessor method. */ private NGNode doCreatePeer() { peer = new NGExternalNode(); peer.setLock(paintLock); for (Runnable request : peerRequests) { request.run(); } peerRequests = null; if (getScene() != null) { addSceneListeners(getScene()); } sceneProperty().addListener((observable, oldValue, newValue) -> { if (oldValue != null) { // Removed from scene removeSceneListeners(oldValue); swiop.disposeLwFrame(); } if (newValue != null) { // Added to another scene if (content != null && lwFrame == null) { setContent(content); } addSceneListeners(newValue); } }); NodeHelper.treeVisibleProperty(this).addListener((observable, oldValue, newValue) -> { swiop.setLwFrameVisible(newValue); }); return peer; } /* * Note: This method MUST only be called via its accessor method. */ private void doUpdatePeer() { if (NodeHelper.isDirty(this, DirtyBits.NODE_VISIBLE) || NodeHelper.isDirty(this, DirtyBits.NODE_BOUNDS)) { locateLwFrame(); // initialize location } if (NodeHelper.isDirty(this, DirtyBits.NODE_CONTENTS)) { peer.markContentDirty(); } } private void locateLwFrame() { if (getScene() == null || lwFrame == null || getScene().getWindow() == null || !getScene().getWindow().isShowing()) { // Not initialized yet. Skip the update to set the real values later return; } Window w = getScene().getWindow(); double renderScaleX = w.getRenderScaleX(); double renderScaleY = w.getRenderScaleY(); final Point2D loc = localToScene(0, 0); final int windowX = (int) (w.getX()); final int windowY = (int) (w.getY()); final int windowW = (int) (w.getWidth()); final int windowH = (int) (w.getHeight()); final int frameX = (int) Math.round(w.getX() + getScene().getX() + loc.getX()); final int frameY = (int) Math.round(w.getY() + getScene().getY() + loc.getY()); final int frameW = (int) (fxWidth); final int frameH = (int) (fxHeight); SwingFXUtils.runOnEDT(() -> { if (lwFrame != null) { swiop.notifyDisplayChanged(lwFrame, w); swiop.setBounds(lwFrame, frameX, frameY, frameW, frameH); swiop.setHostBounds(lwFrame, w); } }); } /* * Note: This method MUST only be called via its accessor method. */ private BaseBounds doComputeGeomBounds(BaseBounds bounds, BaseTransform tx) { bounds.deriveWithNewBounds(0, 0, 0, (float)fxWidth, (float)fxHeight, 0); tx.transform(bounds, bounds); return bounds; } private static class SwingNodeWindowFocusListener implements WindowFocusListener { private WeakReference swingNodeRef; SwingNodeWindowFocusListener(SwingNode swingNode) { this.swingNodeRef = new WeakReference(swingNode); } @Override public void windowGainedFocus(WindowEvent e) { SwingFXUtils.runOnFxThread(() -> { SwingNode swingNode = swingNodeRef.get(); if (swingNode != null) { swingNode.requestFocus(); } }); } @Override public void windowLostFocus(WindowEvent e) { SwingFXUtils.runOnFxThread(() -> { SwingNode swingNode = swingNodeRef.get(); if (swingNode != null) { swingNode.ungrabFocus(true); } }); } } public void ungrabFocus(boolean postUngrabEvent) { // On X11 grab is limited to a single XDisplay connection, // so we can't delegate it to another GUI toolkit. if (PlatformUtil.isLinux()) return; if (grabbed && getScene() != null && getScene().getWindow() != null && WindowHelper.getPeer(getScene().getWindow()) != null) { skipBackwardUnrgabNotification = !postUngrabEvent; WindowHelper.getPeer(getScene().getWindow()).ungrabFocus(); skipBackwardUnrgabNotification = false; grabbed = false; } } private class PostEventAction implements PrivilegedAction { private AWTEvent event; PostEventAction(AWTEvent event) { this.event = event; } @Override public Void run() { EventQueue eq = Toolkit.getDefaultToolkit().getSystemEventQueue(); eq.postEvent(event); return null; } } private class SwingMouseEventHandler implements EventHandler { private final Set mouseClickedAllowed = new HashSet<>(); @Override public void handle(MouseEvent event) { Object frame = swiop.getLightweightFrame(); if (frame == null) { return; } int swingID = SwingEvents.fxMouseEventTypeToMouseID(event); if (swingID < 0) { return; } // Prevent ancestors of the SwingNode from stealing the focus event.consume(); final EventType type = event.getEventType(); if (type == MouseEvent.MOUSE_PRESSED) { mouseClickedAllowed.add(event.getButton()); } else if (type == MouseEvent.MOUSE_RELEASED) { // RELEASED comes before CLICKED, so we don't remove the button from the set //mouseClickedAllowed.remove(event.getButton()); } else if (type == MouseEvent.MOUSE_DRAGGED) { // This is what AWT/Swing do mouseClickedAllowed.clear(); } else if (type == MouseEvent.MOUSE_CLICKED) { if (event.getClickCount() == 1 && !mouseClickedAllowed.contains(event.getButton())) { // RT-34610: In FX, CLICKED events are generated even after dragging the mouse pointer // Note that this is only relevant for single clicks. Double clicks use a smudge factor. return; } mouseClickedAllowed.remove(event.getButton()); } int swingModifiers = SwingEvents.fxMouseModsToMouseMods(event); boolean swingPopupTrigger = event.isPopupTrigger(); int swingButton = SwingEvents.fxMouseButtonToMouseButton(event); long swingWhen = System.currentTimeMillis(); int relX = (int) Math.round(event.getX()); int relY = (int) Math.round(event.getY()); int absX = (int) Math.round(event.getScreenX()); int absY = (int) Math.round(event.getScreenY()); java.awt.event.MouseEvent mouseEvent = swiop.createMouseEvent( frame, swingID, swingWhen, swingModifiers, relX, relY, absX, absY, event.getClickCount(), swingPopupTrigger, swingButton); AccessController.doPrivileged(new PostEventAction(mouseEvent)); } } private class SwingScrollEventHandler implements EventHandler { @Override public void handle(ScrollEvent event) { Object frame = swiop.getLightweightFrame(); if (frame == null) { return; } int swingModifiers = SwingEvents.fxScrollModsToMouseWheelMods(event); final boolean isShift = (swingModifiers & InputEvent.SHIFT_DOWN_MASK) != 0; // Vertical scroll. if (!isShift && event.getDeltaY() != 0.0) { sendMouseWheelEvent(frame, event.getX(), event.getY(), swingModifiers, event.getDeltaY() / event.getMultiplierY()); } // Horizontal scroll or shirt+vertical scroll. final double delta = isShift && event.getDeltaY() != 0.0 ? event.getDeltaY() / event.getMultiplierY() : event.getDeltaX() / event.getMultiplierX(); if (delta != 0.0) { swingModifiers |= InputEvent.SHIFT_DOWN_MASK; sendMouseWheelEvent(frame, event.getX(), event.getY(), swingModifiers, delta); } } private void sendMouseWheelEvent(Object source, double fxX, double fxY, int swingModifiers, double delta) { int wheelRotation = (int) delta; int signum = (int) Math.signum(delta); if (signum * delta < 1) { wheelRotation = signum; } int x = (int) Math.round(fxX); int y = (int) Math.round(fxY); MouseWheelEvent mouseWheelEvent = swiop.createMouseWheelEvent(source, swingModifiers, x, y, -wheelRotation); AccessController.doPrivileged(new PostEventAction(mouseWheelEvent)); } } private class SwingKeyEventHandler implements EventHandler { @Override public void handle(KeyEvent event) { Object frame = swiop.getLightweightFrame(); if (frame == null) { return; } if (event.getCharacter().isEmpty()) { // TODO: should we post an "empty" character? return; } // Don't let Arrows, Tab, Shift+Tab traverse focus out. if (event.getCode() == KeyCode.LEFT || event.getCode() == KeyCode.RIGHT || event.getCode() == KeyCode.UP || event.getCode() == KeyCode.DOWN || event.getCode() == KeyCode.TAB) { event.consume(); } int swingID = SwingEvents.fxKeyEventTypeToKeyID(event); if (swingID < 0) { return; } int swingModifiers = SwingEvents.fxKeyModsToKeyMods(event); int swingKeyCode = event.getCode().getCode(); char swingChar = event.getCharacter().charAt(0); // A workaround. Some swing L&F's process mnemonics on KEY_PRESSED, // for which swing provides a keychar. Extracting it from the text. if (event.getEventType() == javafx.scene.input.KeyEvent.KEY_PRESSED) { String text = event.getText(); if (text.length() == 1) { swingChar = text.charAt(0); } } long swingWhen = System.currentTimeMillis(); java.awt.event.KeyEvent keyEvent = swiop.createKeyEvent(frame, swingID, swingWhen, swingModifiers, swingKeyCode, swingChar); AccessController.doPrivileged(new PostEventAction(keyEvent)); } } }