From d7ddc1d4928dd28ac17ee8465e91019ff1c46381 Mon Sep 17 00:00:00 2001 From: Kevin Rushforth Date: Mon, 1 Feb 2021 13:29:10 +0000 Subject: [PATCH 1/2] 8259680: Need API to query states of CAPS LOCK and NUM LOCK keys Reviewed-by: arapte, pbansal --- .../java/com/sun/glass/events/KeyEvent.java | 7 + .../java/com/sun/glass/ui/Application.java | 19 +++ .../com/sun/glass/ui/gtk/GtkApplication.java | 3 + .../com/sun/glass/ui/mac/MacApplication.java | 3 + .../com/sun/glass/ui/win/WinApplication.java | 3 + .../java/com/sun/javafx/tk/DummyToolkit.java | 6 + .../main/java/com/sun/javafx/tk/Toolkit.java | 8 + .../sun/javafx/tk/quantum/QuantumToolkit.java | 19 +++ .../java/javafx/application/Platform.java | 41 ++++- .../src/main/native-glass/gtk/glass_key.cpp | 60 +++++++ .../src/main/native-glass/mac/GlassKey.m | 22 +++ .../src/main/native-glass/win/KeyTable.cpp | 22 +++ .../com/sun/javafx/pgstub/StubToolkit.java | 5 + tests/manual/events/CapsLockTest.java | 79 +++++++++ .../javafx/application/KeyLockedTest.java | 150 ++++++++++++++++++ 15 files changed, 446 insertions(+), 1 deletion(-) create mode 100644 tests/manual/events/CapsLockTest.java create mode 100644 tests/system/src/test/java/test/robot/javafx/application/KeyLockedTest.java diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/events/KeyEvent.java b/modules/javafx.graphics/src/main/java/com/sun/glass/events/KeyEvent.java index f41111fa34..6e0463fba6 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/events/KeyEvent.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/events/KeyEvent.java @@ -66,6 +66,13 @@ public class KeyEvent { @Native public final static int MODIFIER_BUTTON_SECONDARY = 1 << 6; @Native public final static int MODIFIER_BUTTON_MIDDLE = 1 << 7; + /* + * Key lock state + */ + @Native public static final int KEY_LOCK_OFF = 0; + @Native public static final int KEY_LOCK_ON = 1; + @Native public static final int KEY_LOCK_UNKNOWN = -1; + /* * Key event key codes. */ diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Application.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Application.java index 5ee80ad3d3..37bd30735b 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Application.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Application.java @@ -36,6 +36,7 @@ import java.util.List; import java.util.Map; import java.util.LinkedList; +import java.util.Optional; public abstract class Application { @@ -744,4 +745,22 @@ public final boolean supportsSystemMenu() { public static int getKeyCodeForChar(char c) { return application._getKeyCodeForChar(c); } + + protected int _isKeyLocked(int keyCode) { + // Overridden in subclasses + return KeyEvent.KEY_LOCK_UNKNOWN; + } + + public final Optional isKeyLocked(int keyCode) { + checkEventThread(); + int lockState = _isKeyLocked(keyCode); + switch (lockState) { + case KeyEvent.KEY_LOCK_OFF: + return Optional.of(false); + case KeyEvent.KEY_LOCK_ON: + return Optional.of(true); + default: + return Optional.empty(); + } + } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkApplication.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkApplication.java index 0526b62ba4..e1d1a61fb7 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkApplication.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkApplication.java @@ -478,4 +478,7 @@ protected boolean _supportsInputMethods() { @Override protected native int _getKeyCodeForChar(char c); + @Override + protected native int _isKeyLocked(int keyCode); + } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacApplication.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacApplication.java index 8f49d60cb1..a93b4d77ef 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacApplication.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacApplication.java @@ -391,4 +391,7 @@ public String getDataDirectory() { @Override protected native int _getKeyCodeForChar(char c); + + @Override + protected native int _isKeyLocked(int keyCode); } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinApplication.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinApplication.java index 0538de5807..00acd2276d 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinApplication.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinApplication.java @@ -358,4 +358,7 @@ public String getDataDirectory() { @Override protected native int _getKeyCodeForChar(char c); + + @Override + protected native int _isKeyLocked(int keyCode); } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/DummyToolkit.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/DummyToolkit.java index 3936673e86..8ad7e2bdad 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/DummyToolkit.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/DummyToolkit.java @@ -67,6 +67,7 @@ import com.sun.scenario.animation.AbstractPrimaryTimer; import com.sun.scenario.effect.FilterContext; import com.sun.scenario.effect.Filterable; +import java.util.Optional; /** * A stubbed out Toolkit that provides no useful implementation. This is used @@ -371,6 +372,11 @@ public KeyCode getPlatformShortcutKey() { throw new UnsupportedOperationException("Not supported yet."); } + @Override + public Optional isKeyLocked(KeyCode keyCode) { + throw new UnsupportedOperationException("Not supported yet."); + } + @Override public FileChooserResult showFileChooser(TKStage ownerWindow, String title, diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/Toolkit.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/Toolkit.java index 185d63b957..20b69feac2 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/Toolkit.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/Toolkit.java @@ -94,6 +94,7 @@ import com.sun.scenario.effect.Color4f; import com.sun.scenario.effect.FilterContext; import com.sun.scenario.effect.Filterable; +import java.util.Optional; public abstract class Toolkit { @@ -873,6 +874,13 @@ public KeyCode getPlatformShortcutKey() { return PlatformUtil.isMac() ? KeyCode.META : KeyCode.CONTROL; } + /** + * Returns the lock state for the given keyCode. + * @param keyCode the keyCode to check + * @return the lock state for the given keyCode. + */ + public abstract Optional isKeyLocked(KeyCode keyCode); + public abstract FileChooserResult showFileChooser( TKStage ownerWindow, String title, diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java index 5474026f20..352d87c3f7 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java @@ -73,6 +73,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; +import java.util.Optional; import com.sun.glass.ui.Application; import com.sun.glass.ui.Clipboard; import com.sun.glass.ui.ClipboardAssistance; @@ -1192,6 +1193,24 @@ public boolean isMSAASupported() { return GraphicsPipeline.getPipeline().isMSAASupported(); } + // Returns the glass keycode for the given JavaFX KeyCode. + // This method only converts lock state KeyCode values + private int toGlassKeyCode(KeyCode keyCode) { + switch (keyCode) { + case CAPS: + return com.sun.glass.events.KeyEvent.VK_CAPS_LOCK; + case NUM_LOCK: + return com.sun.glass.events.KeyEvent.VK_NUM_LOCK; + default: + return com.sun.glass.events.KeyEvent.VK_UNDEFINED; + } + } + + @Override + public Optional isKeyLocked(KeyCode keyCode) { + return Application.GetApplication().isKeyLocked(toGlassKeyCode(keyCode)); + } + static TransferMode clipboardActionToTransferMode(final int action) { switch (action) { case Clipboard.ACTION_NONE: diff --git a/modules/javafx.graphics/src/main/java/javafx/application/Platform.java b/modules/javafx.graphics/src/main/java/javafx/application/Platform.java index 0a8b8a32d1..3fca5992d2 100644 --- a/modules/javafx.graphics/src/main/java/javafx/application/Platform.java +++ b/modules/javafx.graphics/src/main/java/javafx/application/Platform.java @@ -25,10 +25,12 @@ package javafx.application; +import com.sun.javafx.application.PlatformImpl; import com.sun.javafx.tk.Toolkit; +import java.util.Optional; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanWrapper; -import com.sun.javafx.application.PlatformImpl; +import javafx.scene.input.KeyCode; /** * Application platform support class. @@ -319,6 +321,43 @@ public static void exitNestedEventLoop(Object key, Object rval) { Toolkit.getToolkit().exitNestedEventLoop(key, rval); } + /** + * Returns a flag indicating whether the key corresponding to {@code keyCode} + * is in the locked (or "on") state. + * {@code keyCode} must be one of: {@link KeyCode#CAPS} or + * {@link KeyCode#NUM_LOCK}. + * If the underlying system is not able to determine the state of the + * specified {@code keyCode}, an empty {@code Optional} is returned. + * If the keyboard attached to the system doesn't have the specified key, + * an {@code Optional} containing {@code false} is returned. + * This method must be called on the JavaFX Application thread. + * + * @param keyCode the {@code KeyCode} of the lock state to query + * + * @return the lock state of the key corresponding to {@code keyCode}, + * or an empty {@code Optional} if the system cannot determine its state + * + * @throws IllegalArgumentException if {@code keyCode} is not one of the + * valid {@code KeyCode} values + * + * @throws IllegalStateException if this method is called on a thread + * other than the JavaFX Application Thread + * + * @since 17 + */ + public static Optional isKeyLocked(KeyCode keyCode) { + Toolkit.getToolkit().checkFxUserThread(); + + switch (keyCode) { + case CAPS: + case NUM_LOCK: + break; + default: + throw new IllegalArgumentException("Invalid KeyCode"); + } + return Toolkit.getToolkit().isKeyLocked(keyCode); + } + /** * Checks whether a nested event loop is running, returning true to indicate * that one is, and false if there are no nested event loops currently diff --git a/modules/javafx.graphics/src/main/native-glass/gtk/glass_key.cpp b/modules/javafx.graphics/src/main/native-glass/gtk/glass_key.cpp index 4ae67f1bf9..79be721587 100644 --- a/modules/javafx.graphics/src/main/native-glass/gtk/glass_key.cpp +++ b/modules/javafx.graphics/src/main/native-glass/gtk/glass_key.cpp @@ -29,6 +29,7 @@ #include #include "glass_general.h" #include +#include static gboolean key_initialized = FALSE; static GHashTable *keymap; @@ -343,4 +344,63 @@ JNIEXPORT jint JNICALL Java_com_sun_glass_ui_gtk_GtkApplication__1getKeyCodeForC return gdk_keyval_to_glass(keyval); } +/* + * Function to determine whether the Xkb extention is available. This is a + * precaution against X protocol errors, although it should be available on all + * Linux systems. + */ + +static Bool xkbInitialized = False; +static Bool xkbAvailable = False; + +static Bool isXkbAvailable(Display *display) { + if (!xkbInitialized) { + int xkbMajor = XkbMajorVersion; + int xkbMinor = XkbMinorVersion; + xkbAvailable = XkbQueryExtension(display, NULL, NULL, NULL, &xkbMajor, &xkbMinor); + xkbInitialized = True; + } + return xkbAvailable; +} + +/* + * Class: com_sun_glass_ui_gtk_GtkApplication + * Method: _isKeyLocked + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_com_sun_glass_ui_gtk_GtkApplication__1isKeyLocked + (JNIEnv * env, jobject obj, jint keyCode) +{ + Display* display = gdk_x11_display_get_xdisplay(gdk_display_get_default()); + if (!isXkbAvailable(display)) { + return com_sun_glass_events_KeyEvent_KEY_LOCK_UNKNOWN; + } + + Atom keyCodeAtom = None; + switch (keyCode) { + case com_sun_glass_events_KeyEvent_VK_CAPS_LOCK: + keyCodeAtom = XInternAtom(display, "Caps Lock", True); + break; + + case com_sun_glass_events_KeyEvent_VK_NUM_LOCK: + keyCodeAtom = XInternAtom(display, "Num Lock", True); + break; + } + + if (keyCodeAtom == None) { + return com_sun_glass_events_KeyEvent_KEY_LOCK_UNKNOWN; + } + + Bool isLocked = False; + if (XkbGetNamedIndicator(display, keyCodeAtom, NULL, &isLocked, NULL, NULL)) { + if (isLocked) { + return com_sun_glass_events_KeyEvent_KEY_LOCK_ON; + } else { + return com_sun_glass_events_KeyEvent_KEY_LOCK_OFF; + } + } + + return com_sun_glass_events_KeyEvent_KEY_LOCK_UNKNOWN; +} + } // extern "C" diff --git a/modules/javafx.graphics/src/main/native-glass/mac/GlassKey.m b/modules/javafx.graphics/src/main/native-glass/mac/GlassKey.m index f36bcb1ceb..afa38d6944 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/GlassKey.m +++ b/modules/javafx.graphics/src/main/native-glass/mac/GlassKey.m @@ -390,3 +390,25 @@ BOOL GetMacKey(jint javaKeyCode, unsigned short *outMacKeyCode) return [GlassApplication getKeyCodeForChar:c]; } +/* + * Class: com_sun_glass_ui_mac_MacApplication + * Method: _isKeyLocked + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_com_sun_glass_ui_mac_MacApplication__1isKeyLocked + (JNIEnv * env, jobject obj, jint keyCode) +{ + NSUInteger mask = 0; + switch (keyCode) { + case com_sun_glass_events_KeyEvent_VK_CAPS_LOCK: + mask = NSEventModifierFlagCapsLock; + break; + + // Caps lock is the only locking key supported on macOS + default: + return com_sun_glass_events_KeyEvent_KEY_LOCK_UNKNOWN; + } + NSUInteger modifierFlags = [NSEvent modifierFlags]; + return (modifierFlags & mask) ? com_sun_glass_events_KeyEvent_KEY_LOCK_ON + : com_sun_glass_events_KeyEvent_KEY_LOCK_OFF; +} diff --git a/modules/javafx.graphics/src/main/native-glass/win/KeyTable.cpp b/modules/javafx.graphics/src/main/native-glass/win/KeyTable.cpp index 1d4fba9b34..62338dab06 100644 --- a/modules/javafx.graphics/src/main/native-glass/win/KeyTable.cpp +++ b/modules/javafx.graphics/src/main/native-glass/win/KeyTable.cpp @@ -247,5 +247,27 @@ JNIEXPORT jint JNICALL Java_com_sun_glass_ui_win_WinApplication__1getKeyCodeForC return WindowsKeyToJavaKey(vkey); } +/* + * Class: com_sun_glass_ui_win_WinApplication + * Method: _isKeyLocked + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_com_sun_glass_ui_win_WinApplication__1isKeyLocked + (JNIEnv * env, jobject obj, jint keyCode) +{ + SHORT keyState = 0; + switch (keyCode) { + case com_sun_glass_events_KeyEvent_VK_CAPS_LOCK: + keyState = ::GetKeyState(VK_CAPITAL); + break; + case com_sun_glass_events_KeyEvent_VK_NUM_LOCK: + keyState = ::GetKeyState(VK_NUMLOCK); + break; + default: + return com_sun_glass_events_KeyEvent_KEY_LOCK_UNKNOWN; + } + return (keyState & 0x1) ? com_sun_glass_events_KeyEvent_KEY_LOCK_ON + : com_sun_glass_events_KeyEvent_KEY_LOCK_OFF; +} diff --git a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubToolkit.java b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubToolkit.java index 2626626943..3e6906a040 100644 --- a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubToolkit.java +++ b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubToolkit.java @@ -750,6 +750,11 @@ public KeyCode getPlatformShortcutKey() { return platformShortcutKey; } + @Override + public Optional isKeyLocked(KeyCode keyCode) { + return Optional.empty(); + } + private DndDelegate dndDelegate; public void setDndDelegate(DndDelegate dndDelegate) { this.dndDelegate = dndDelegate; diff --git a/tests/manual/events/CapsLockTest.java b/tests/manual/events/CapsLockTest.java new file mode 100644 index 0000000000..6414f7beba --- /dev/null +++ b/tests/manual/events/CapsLockTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2021, 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. + */ + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.Optional; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.scene.input.KeyCode; +import javafx.stage.Stage; + +public class CapsLockTest { + + private static BufferedReader reader; + + public static class App extends Application { + private void checkCapsLock(boolean expected) throws Exception { + Optional capsLock = Platform.isKeyLocked(KeyCode.CAPS); + if (capsLock.isPresent()) { + System.out.println("isKeyLocked(CAPS) is " + capsLock.get()); + if (capsLock.get() != expected) { + System.out.println("TEST FAILED"); + System.exit(1); + } + } else { + System.out.println("ERROR: isKeyLocked(CAPS) is empty"); + System.out.println("TEST FAILED"); + System.exit(1); + } + } + + @Override + public void start(Stage stage) throws Exception { + checkCapsLock(true); + System.out.println("Disable Caps Lock on your system then press ENTER"); + reader.readLine(); + checkCapsLock(false); + Platform.exit(); + } + + } + + public static void main(String[] args) { + System.out.println("Enable Caps Lock on your system then press ENTER"); + try { + reader = new BufferedReader(new InputStreamReader(System.in)); + reader.readLine(); + Application.launch(App.class, args); + } catch (Exception ex) { + ex.printStackTrace(System.out); + System.out.println("TEST FAILED"); + System.exit(1); + } + System.out.println(); + System.out.println("TEST PASSED"); + } +} diff --git a/tests/system/src/test/java/test/robot/javafx/application/KeyLockedTest.java b/tests/system/src/test/java/test/robot/javafx/application/KeyLockedTest.java new file mode 100644 index 0000000000..d2888946f8 --- /dev/null +++ b/tests/system/src/test/java/test/robot/javafx/application/KeyLockedTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2021, 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 test.robot.javafx.application; + +import com.sun.javafx.PlatformUtil; +import java.util.Optional; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import javafx.application.Platform; +import javafx.scene.input.KeyCode; +import javafx.scene.robot.Robot; +import test.util.Util; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import static org.junit.Assert.*; +import static org.junit.Assume.assumeTrue; + +/** + * Test program for Platform::isKeyLocked. + */ +public class KeyLockedTest { + + // Used to start the toolkit before running any test + private static final CountDownLatch startupLatch = new CountDownLatch(1); + private static Robot robot; + + @BeforeClass + public static void initFX() throws Exception { + Platform.setImplicitExit(false); + Platform.startup(startupLatch::countDown); + assertTrue("Timeout waiting for FX runtime to start", + startupLatch.await(15, TimeUnit.SECONDS)); + + if (PlatformUtil.isWindows()) { + Util.runAndWait(() -> robot = new Robot()); + } + } + + @AfterClass + public static void cleanupFX() { + if (robot != null) { + // Disable caps lock if it is set + Platform.runLater(() -> { + Optional capsLockState = Platform.isKeyLocked(KeyCode.CAPS); + capsLockState.ifPresent(state -> { + if (state) { + robot.keyPress(KeyCode.CAPS); + robot.keyRelease(KeyCode.CAPS); + } + }); + }); + } + Platform.exit(); + } + + @Test(expected = IllegalStateException.class) + public void testCallOnTestThread() { + // This should throw an exception + Optional capsLockState = Platform.isKeyLocked(KeyCode.CAPS); + } + + @Test(expected = IllegalArgumentException.class) + public void testIllegalKeyCode() { + Util.runAndWait(() -> { + // This should throw an exception + Optional capsLockState = Platform.isKeyLocked(KeyCode.A); + }); + } + + @Test + public void testCanReadCapsLockState() { + Util.runAndWait(() -> { + // Check that we don't get an exception or a null optional. + Optional capsLockState = Platform.isKeyLocked(KeyCode.CAPS); + assertNotNull(capsLockState); + // A result should always be present + assertTrue(capsLockState.isPresent()); + }); + } + + @Test + public void testCanReadNumLockState() { + Util.runAndWait(() -> { + // Check that we don't get an exception or a null optional. + Optional numLockState = Platform.isKeyLocked(KeyCode.NUM_LOCK); + assertNotNull(numLockState); + // A result should always be present on Windows and Linux + if (PlatformUtil.isWindows() || PlatformUtil.isLinux()) { + assertTrue(numLockState.isPresent()); + } + // A result should never be present on Mac + if (PlatformUtil.isMac()) { + assertFalse(numLockState.isPresent()); + } + }); + } + + @Test + public void testCapsLockState() { + // We can set caps lock via robot only on Windows + assumeTrue(PlatformUtil.isWindows()); + + final AtomicBoolean initialCapsLock = new AtomicBoolean(false); + Util.runAndWait(() -> { + Optional capsLockState = Platform.isKeyLocked(KeyCode.CAPS); + assertNotNull(capsLockState); + assertTrue(capsLockState.isPresent()); + + // Read the initial state of the caps lock key and then toggle it + initialCapsLock.set(capsLockState.get()); + robot.keyPress(KeyCode.CAPS); + robot.keyRelease(KeyCode.CAPS); + }); + // Wait for 1/2 second to make sure the state has toggled + Util.sleep(500); + Util.runAndWait(() -> { + Optional capsLockState = Platform.isKeyLocked(KeyCode.CAPS); + assertNotNull(capsLockState); + assertTrue(capsLockState.isPresent()); + assertTrue(initialCapsLock.get() != capsLockState.get()); + }); + } +} From 948abb04d99e20cd55f326bc30d98e01a9fc1e9a Mon Sep 17 00:00:00 2001 From: Kevin Rushforth Date: Wed, 5 May 2021 06:56:47 -0700 Subject: [PATCH 2/2] Update @since tag to 11.0.12 --- .../src/main/java/javafx/application/Platform.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/application/Platform.java b/modules/javafx.graphics/src/main/java/javafx/application/Platform.java index 3fca5992d2..72bcd1566d 100644 --- a/modules/javafx.graphics/src/main/java/javafx/application/Platform.java +++ b/modules/javafx.graphics/src/main/java/javafx/application/Platform.java @@ -343,7 +343,7 @@ public static void exitNestedEventLoop(Object key, Object rval) { * @throws IllegalStateException if this method is called on a thread * other than the JavaFX Application Thread * - * @since 17 + * @since 11.0.12 */ public static Optional isKeyLocked(KeyCode keyCode) { Toolkit.getToolkit().checkFxUserThread();