| // Licensed to the Software Freedom Conservancy (SFC) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The SFC licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| package org.openqa.selenium; |
| |
| import java.util.List; |
| import org.jspecify.annotations.Nullable; |
| |
| /** |
| * Representations of pressable keys that aren't text. These are stored in the Unicode PUA (Private |
| * Use Area) code points, 0xE000–0xF8FF. These values are used internally by WebDriver to simulate |
| * keyboard input where standard Unicode characters are insufficient, such as modifier and control |
| * keys. |
| * |
| * <p>The codes follow conventions partially established by the W3C WebDriver specification and the |
| * Selenium project. Some values (e.g., RIGHT_SHIFT, RIGHT_COMMAND) are used in ChromeDriver but are |
| * not currently part of the W3C spec. Others (e.g., OPTION, FN) are symbolic and reserved for |
| * possible future mapping. |
| * |
| * <p>For consistency across platforms and drivers, values should be verified before assuming native |
| * support. |
| * |
| * @see <a href="https://www.w3.org/TR/webdriver/#keyboard-actions">W3C WebDriver Keyboard |
| * Actions</a> |
| * @see <a href="http://www.google.com.au/search?&q=unicode+pua&btnK=Search">Unicode PUA |
| * Overview</a> |
| */ |
| public enum Keys implements CharSequence { |
| // Basic control characters |
| NULL('\uE000'), |
| CANCEL('\uE001'), // ^break |
| HELP('\uE002'), |
| BACK_SPACE('\uE003'), |
| TAB('\uE004'), |
| CLEAR('\uE005'), |
| RETURN('\uE006'), |
| ENTER('\uE007'), |
| SHIFT('\uE008'), |
| LEFT_SHIFT(Keys.SHIFT), |
| CONTROL('\uE009'), |
| LEFT_CONTROL(Keys.CONTROL), |
| ALT('\uE00A'), |
| LEFT_ALT(Keys.ALT), |
| PAUSE('\uE00B'), |
| ESCAPE('\uE00C'), |
| SPACE('\uE00D'), |
| PAGE_UP('\uE00E'), |
| PAGE_DOWN('\uE00F'), |
| END('\uE010'), |
| HOME('\uE011'), |
| LEFT('\uE012'), |
| ARROW_LEFT(Keys.LEFT), |
| UP('\uE013'), |
| ARROW_UP(Keys.UP), |
| RIGHT('\uE014'), |
| ARROW_RIGHT(Keys.RIGHT), |
| DOWN('\uE015'), |
| ARROW_DOWN(Keys.DOWN), |
| INSERT('\uE016'), |
| DELETE('\uE017'), |
| SEMICOLON('\uE018'), |
| EQUALS('\uE019'), |
| |
| // Number pad keys |
| NUMPAD0('\uE01A'), |
| NUMPAD1('\uE01B'), |
| NUMPAD2('\uE01C'), |
| NUMPAD3('\uE01D'), |
| NUMPAD4('\uE01E'), |
| NUMPAD5('\uE01F'), |
| NUMPAD6('\uE020'), |
| NUMPAD7('\uE021'), |
| NUMPAD8('\uE022'), |
| NUMPAD9('\uE023'), |
| MULTIPLY('\uE024'), |
| ADD('\uE025'), |
| SEPARATOR('\uE026'), |
| SUBTRACT('\uE027'), |
| DECIMAL('\uE028'), |
| DIVIDE('\uE029'), |
| |
| // Function keys |
| F1('\uE031'), |
| F2('\uE032'), |
| F3('\uE033'), |
| F4('\uE034'), |
| F5('\uE035'), |
| F6('\uE036'), |
| F7('\uE037'), |
| F8('\uE038'), |
| F9('\uE039'), |
| F10('\uE03A'), |
| F11('\uE03B'), |
| F12('\uE03C'), |
| |
| META('\uE03D'), |
| COMMAND(Keys.META), |
| |
| // Extended macOS/ChromeDriver keys (based on observed Chrome usage) |
| RIGHT_SHIFT('\uE050'), // aligns with ChromeDriver usage |
| RIGHT_CONTROL('\uE051'), |
| RIGHT_ALT('\uE052'), |
| RIGHT_COMMAND('\uE053'), |
| |
| // Symbolic macOS keys not yet standardized |
| OPTION('\uE052'), |
| FN('\uE051'), // TODO: symbolic only; confirm or remove in future |
| |
| ZENKAKU_HANKAKU('\uE040'); |
| |
| private final char keyCode; |
| private final int codePoint; |
| |
| Keys(Keys key) { |
| this(key.charAt(0)); |
| } |
| |
| Keys(char keyCode) { |
| this.keyCode = keyCode; |
| this.codePoint = String.valueOf(keyCode).codePoints().findFirst().getAsInt(); |
| } |
| |
| public int getCodePoint() { |
| return codePoint; |
| } |
| |
| @Override |
| public char charAt(int index) { |
| if (index == 0) { |
| return keyCode; |
| } |
| return 0; |
| } |
| |
| @Override |
| public int length() { |
| return 1; |
| } |
| |
| @Override |
| public CharSequence subSequence(int start, int end) { |
| if (start == 0 && end == 1) { |
| return String.valueOf(keyCode); |
| } |
| throw new IndexOutOfBoundsException(); |
| } |
| |
| @Override |
| public String toString() { |
| return String.valueOf(keyCode); |
| } |
| |
| /** |
| * Simulate pressing many keys at once in a "chord". Takes a sequence of Keys.XXXX or strings; |
| * appends each to a string, adds the chord termination key (Keys.NULL), and returns it. |
| * |
| * <p>Note: Keys.NULL signals release of modifier keys like CTRL/ALT/SHIFT via keyup events. |
| * |
| * @param value characters to send |
| * @return String representation of the char sequence |
| */ |
| public static String chord(CharSequence... value) { |
| return chord(List.of(value)); |
| } |
| |
| /** |
| * Overload of {@link #chord(CharSequence...)} that accepts an iterable. |
| * |
| * @param value characters to send |
| * @return String representation of the char sequence |
| */ |
| public static String chord(Iterable<CharSequence> value) { |
| StringBuilder builder = new StringBuilder(); |
| for (CharSequence seq : value) { |
| builder.append(seq); |
| } |
| builder.append(Keys.NULL); |
| return builder.toString(); |
| } |
| |
| /** |
| * Retrieves the {@link Keys} enum constant corresponding to the given Unicode character. |
| * |
| * @param key unicode character code |
| * @return special key linked to the character code, or null if not found |
| */ |
| public static @Nullable Keys getKeyFromUnicode(char key) { |
| for (Keys unicodeKey : values()) { |
| if (unicodeKey.charAt(0) == key) { |
| return unicodeKey; |
| } |
| } |
| return null; |
| } |
| } |