JBR-5558: macOS keyboard rewrite 2

This commit is contained in:
Nikita Tsarev
2023-06-02 15:23:41 +02:00
committed by jbrbot
parent 7e48c92759
commit 1682467d6d
21 changed files with 960 additions and 552 deletions

View File

@@ -100,11 +100,10 @@ public class CEmbeddedFrame extends EmbeddedFrame {
deltaY, NSEvent.SCROLL_PHASE_UNSUPPORTED);
}
public void handleKeyEvent(int eventType, int modifierFlags, String characters,
String charsIgnoringMods, boolean isRepeat, short keyCode,
public void handleKeyEvent(int eventType, int modifierFlags, String characters, boolean isRepeat, short keyCode,
boolean needsKeyTyped) {
responder.handleKeyEvent(eventType, modifierFlags, characters, charsIgnoringMods,
keyCode, needsKeyTyped, isRepeat);
responder.handleKeyEvent(eventType, modifierFlags, characters,
null, keyCode, needsKeyTyped, isRepeat);
}
public void handleInputEvent(String text) {

View File

@@ -57,7 +57,6 @@ final class CPlatformResponder {
private int lastDraggedAbsoluteY;
private int lastDraggedRelativeX;
private int lastDraggedRelativeY;
private static final Pattern layoutsNeedingCapsLockFix = Pattern.compile("com\\.apple\\.inputmethod\\.(SCIM|TCIM)\\.(Shuangpin|Pinyin|ITABC)");
CPlatformResponder(final PlatformEventNotifier eventNotifier,
final boolean isNpapiCallback) {
@@ -173,8 +172,17 @@ final class CPlatformResponder {
/**
* Handles key events.
*
* @param eventType macOS event type ID: keyDown, keyUp or flagsChanged
* @param modifierFlags macOS modifier flags mask (NSEventModifierFlags)
* @param chars NSEvent's characters property
* @param actualChars If non-null, then this key should generate KEY_TYPED events
* corresponding to characters in this string. Only valid for keyDown events.
* @param keyCode macOS virtual key code of the key being pressed or released
* @param needsKeyTyped post KEY_TYPED events?
* @param needsKeyReleased post KEY_RELEASED events?
*/
void handleKeyEvent(int eventType, int modifierFlags, String chars, String charsIgnoringModifiers,
void handleKeyEvent(int eventType, int modifierFlags, String chars, String actualChars,
short keyCode, boolean needsKeyTyped, boolean needsKeyReleased) {
boolean isFlagsChangedEvent =
isNpapiCallback ? (eventType == CocoaConstants.NPCocoaEventFlagsChanged) :
@@ -183,11 +191,10 @@ final class CPlatformResponder {
int jeventType = KeyEvent.KEY_PRESSED;
int jkeyCode = KeyEvent.VK_UNDEFINED;
int jkeyLocation = KeyEvent.KEY_LOCATION_UNKNOWN;
boolean postsTyped = false;
boolean spaceKeyTyped = false;
boolean charsReserved = false;
char testChar = KeyEvent.CHAR_UNDEFINED;
boolean isDeadChar = (chars != null && chars.length() == 0);
if (isFlagsChangedEvent) {
int[] in = new int[] {modifierFlags, keyCode};
@@ -200,59 +207,36 @@ final class CPlatformResponder {
jeventType = out[2];
} else {
if (chars != null && chars.length() > 0) {
// Find a suitable character to report as a keypress.
// `chars` might contain more than one character
// e.g. when Dead Grave + S were pressed, `chars` will contain "`s"
// Since we only really care about the last character, let's use it
testChar = chars.charAt(chars.length() - 1);
// `chars` might contain more than one character, so why are we using the last one?
// It doesn't really matter actually! If the string contains more than one character,
// the only way that this character will be used is to construct the keyChar field of the KeyEvent object.
// That field is only guaranteed to be meaningful for KEY_TYPED events, so let's not overthink it.
// Please note: this character is NOT used to construct extended key codes, that happens
// inside the NSEvent.nsToJavaKeyInfo function.
char ch = chars.charAt(chars.length() - 1);
//Check if String chars contains SPACE character.
if (chars.length() == 1) {
// NSEvent.h declares this range of characters to signify various function keys
// This is a subrange of the Unicode private use area.
// No builtin key layouts normally produce any characters within this range, except when
// pressing the corresponding function keys.
charsReserved = ch >= 0xF700 && ch <= 0xF7FF;
}
// Check if String chars contains SPACE character.
if (chars.trim().isEmpty()) {
spaceKeyTyped = true;
}
}
char testCharIgnoringModifiers = charsIgnoringModifiers != null && charsIgnoringModifiers.length() > 0 ?
charsIgnoringModifiers.charAt(0) : KeyEvent.CHAR_UNDEFINED;
int[] in = new int[] {testCharIgnoringModifiers, isDeadChar ? 1 : 0, modifierFlags, keyCode, KeyEventProcessing.useNationalLayouts ? 1 : 0};
int[] out = new int[3]; // [jkeyCode, jkeyLocation, deadChar]
postsTyped = NSEvent.nsToJavaKeyInfo(in, out);
if (!postsTyped) {
testChar = KeyEvent.CHAR_UNDEFINED;
}
if (isDeadChar) {
testChar = (char) out[2];
if (testChar == 0) {
// Not abandoning the input event here, since we want to catch dead key presses.
// Consider Option+E on the standard ABC layout. This key combination produces a dead accent.
// The key 'E' by itself is not dead, thus out[2] will be 0, even though isDeadChar is true.
// If we abandon the event there, this key press will never get delivered to the application.
testChar = KeyEvent.CHAR_UNDEFINED;
if (!charsReserved) {
testChar = ch;
}
}
// If a Chinese input method is selected, CAPS_LOCK key is supposed to switch
// input to latin letters.
// It is necessary to use testCharIgnoringModifiers instead of testChar for event
// generation in such case to avoid uppercase letters in text components.
// This is only necessary for the following Chinese input methods:
// com.apple.inputmethod.SCIM.ITABC
// com.apple.inputmethod.SCIM.Shuangpin
// com.apple.inputmethod.TCIM.Pinyin
// com.apple.inputmethod.TCIM.Shuangpin
// All the other ones work properly without this fix. Zhuyin (Traditional) for example reports
// Bopomofo characters in 'charactersIgnoringModifiers', and latin letters in 'characters'.
// This means that applying this fix will actually produce invalid behavior in this IM.
// Also see test/jdk/jb/sun/awt/macos/InputMethodTest/PinyinCapsLockTest.java
int[] in = new int[] {keyCode, KeyEventProcessing.useNationalLayouts ? 1 : 0};
int[] out = new int[2]; // [jkeyCode, jkeyLocation]
if (testChar != KeyEvent.CHAR_UNDEFINED &&
Toolkit.getDefaultToolkit().getLockingKeyState(KeyEvent.VK_CAPS_LOCK) &&
layoutsNeedingCapsLockFix.matcher(LWCToolkit.getKeyboardLayoutId()).matches()) {
testChar = testCharIgnoringModifiers;
}
NSEvent.nsToJavaKeyInfo(in, out);
jkeyCode = out[0];
jkeyLocation = out[1];
@@ -262,17 +246,6 @@ final class CPlatformResponder {
char javaChar = (testChar == KeyEvent.CHAR_UNDEFINED) ? KeyEvent.CHAR_UNDEFINED :
NSEvent.nsToJavaChar(testChar, modifierFlags, spaceKeyTyped);
// Some keys may generate a KEY_TYPED, but we can't determine what that character is.
// This may happen during the key combinations that produce dead keys (like Option+E described before),
// since we don't care about the dead key for the purposes of keyPressed event, nor do the dead keys
// produce input by themselves. In this case we set postsTyped to false, so that the application
// doesn't receive unnecessary KEY_TYPED events.
//
// In cases not involving dead keys combos, having javaChar == CHAR_UNDEFINED is most likely a bug.
// Since we can't determine which character is supposed to be typed let's just ignore it.
if (javaChar == KeyEvent.CHAR_UNDEFINED) {
postsTyped = false;
}
int jmodifiers = NSEvent.nsToJavaModifiers(modifierFlags);
long when = System.currentTimeMillis();
@@ -283,26 +256,28 @@ final class CPlatformResponder {
eventNotifier.notifyKeyEvent(jeventType, when, jmodifiers,
jkeyCode, javaChar, jkeyLocation);
// Current browser may be sending input events, so don't
// post the KEY_TYPED here.
postsTyped &= needsKeyTyped;
// That's the reaction on the PRESSED (not RELEASED) event as it comes to
// appear in MacOSX.
// Modifier keys (shift, etc) don't want to send TYPED events.
// On the other hand we don't want to generate keyTyped events
// for clipboard related shortcuts like Meta + [CVX]
if (jeventType == KeyEvent.KEY_PRESSED && postsTyped &&
if (jeventType == KeyEvent.KEY_PRESSED && needsKeyTyped && javaChar != KeyEvent.CHAR_UNDEFINED &&
(jmodifiers & KeyEvent.META_DOWN_MASK) == 0) {
// Enter and Space keys finish the input method processing,
// KEY_TYPED and KEY_RELEASED events for them are synthesized in handleInputEvent.
if (needsKeyReleased && (jkeyCode == KeyEvent.VK_ENTER || jkeyCode == KeyEvent.VK_SPACE)) {
return;
if (actualChars == null) {
// Either macOS didn't send us anything in insertText: to type,
// or this event was not generated in AWTView.m. Let's fall back to using javaChar
// since we still need to generate KEY_TYPED events, for instance for Ctrl+ combinations.
// javaChar is guaranteed to be a valid character, since postsTyped is true.
actualChars = String.valueOf(javaChar);
}
eventNotifier.notifyKeyEvent(KeyEvent.KEY_TYPED, when, jmodifiers,
KeyEvent.VK_UNDEFINED, javaChar,
KeyEvent.KEY_LOCATION_UNKNOWN);
//If events come from Firefox, released events should also be generated.
for (char ch : actualChars.toCharArray()) {
eventNotifier.notifyKeyEvent(KeyEvent.KEY_TYPED, when, jmodifiers,
KeyEvent.VK_UNDEFINED, ch,
KeyEvent.KEY_LOCATION_UNKNOWN);
}
// If events come from Firefox, released events should also be generated.
if (needsKeyReleased) {
eventNotifier.notifyKeyEvent(KeyEvent.KEY_RELEASED, when, jmodifiers,
jkeyCode, javaChar,

View File

@@ -202,8 +202,8 @@ public class CPlatformView extends CFRetainedResource {
}
private void deliverKeyEvent(NSEvent event) {
responder.handleKeyEvent(event.getType(), event.getModifierFlags(), event.getCharacters(),
event.getCharactersIgnoringModifiers(), event.getKeyCode(), true, false);
responder.handleKeyEvent(event.getType(), event.getModifierFlags(), event.getCharacters(), event.getActualCharacters(),
event.getKeyCode(), true, false);
}
/**

View File

@@ -56,15 +56,15 @@ final class NSEvent {
// Key event information
private short keyCode;
private String characters;
private String charactersIgnoringModifiers;
private String actualCharacters;
// Called from native
NSEvent(int type, int modifierFlags, short keyCode, String characters, String charactersIgnoringModifiers) {
NSEvent(int type, int modifierFlags, short keyCode, String characters, String actualCharacters) {
this.type = type;
this.modifierFlags = modifierFlags;
this.keyCode = keyCode;
this.characters = characters;
this.charactersIgnoringModifiers = charactersIgnoringModifiers;
this.actualCharacters = actualCharacters;
}
// Called from native
@@ -132,20 +132,20 @@ final class NSEvent {
return keyCode;
}
String getCharactersIgnoringModifiers() {
return charactersIgnoringModifiers;
}
String getCharacters() {
return characters;
}
String getActualCharacters() {
return actualCharacters;
}
@Override
public String toString() {
return "NSEvent[" + getType() + " ," + getModifierFlags() + " ,"
+ getClickCount() + " ," + getButtonNumber() + " ," + getX() + " ,"
+ getY() + " ," + getAbsX() + " ," + getAbsY()+ " ," + getKeyCode() + " ,"
+ getCharacters() + " ," + getCharactersIgnoringModifiers() + "]";
+ getCharacters() + " ," + getActualCharacters() + "]";
}
/*
@@ -256,7 +256,7 @@ final class NSEvent {
/*
* Converts NSEvent key info to AWT key info.
*/
static native boolean nsToJavaKeyInfo(int[] in, int[] out);
static native void nsToJavaKeyInfo(int[] in, int[] out);
/*
* Converts NSEvent key modifiers to AWT key info.

View File

@@ -60,15 +60,21 @@
#define KL_STANDARD java_awt_event_KeyEvent_KEY_LOCATION_STANDARD
#define KL_NUMPAD java_awt_event_KeyEvent_KEY_LOCATION_NUMPAD
#define KL_UNKNOWN java_awt_event_KeyEvent_KEY_LOCATION_UNKNOWN
static struct _key
struct KeyTableEntry
{
unsigned short keyCode;
BOOL postsTyped;
BOOL variesBetweenLayouts;
jint javaKeyLocation;
jint javaKeyCode;
}
const keyTable[] =
};
static const struct KeyTableEntry unknownKeyEntry = {
0xFFFF, NO, NO, KL_UNKNOWN, java_awt_event_KeyEvent_VK_UNDEFINED
};
static const struct KeyTableEntry keyTable[] =
{
{0x00, YES, YES, KL_STANDARD, java_awt_event_KeyEvent_VK_A},
{0x01, YES, YES, KL_STANDARD, java_awt_event_KeyEvent_VK_S},
@@ -163,9 +169,9 @@ const keyTable[] =
{0x5A, NO, NO, KL_STANDARD, java_awt_event_KeyEvent_VK_F20},
{0x5B, YES, NO, KL_NUMPAD, java_awt_event_KeyEvent_VK_NUMPAD8},
{0x5C, YES, NO, KL_NUMPAD, java_awt_event_KeyEvent_VK_NUMPAD9},
{0x5D, YES, NO, KL_STANDARD, java_awt_event_KeyEvent_VK_BACK_SLASH}, // This is a combo yen/backslash on JIS keyboards.
{0x5E, YES, NO, KL_NUMPAD, java_awt_event_KeyEvent_VK_UNDERSCORE},
{0x5F, YES, NO, KL_NUMPAD, java_awt_event_KeyEvent_VK_COMMA},
{0x5D, YES, YES, KL_STANDARD, 0x1000000 + 0x00A5}, // This is a combo yen/backslash on JIS keyboards.
{0x5E, YES, YES, KL_STANDARD, java_awt_event_KeyEvent_VK_UNDERSCORE}, // This is the key to the left of Right Shift on JIS keyboards.
{0x5F, YES, NO, KL_NUMPAD, java_awt_event_KeyEvent_VK_COMMA}, // This is a comma on the JIS keypad.
{0x60, NO, NO, KL_STANDARD, java_awt_event_KeyEvent_VK_F5},
{0x61, NO, NO, KL_STANDARD, java_awt_event_KeyEvent_VK_F6},
{0x62, NO, NO, KL_STANDARD, java_awt_event_KeyEvent_VK_F7},
@@ -200,6 +206,15 @@ const keyTable[] =
{0x7F, NO, NO, KL_UNKNOWN, java_awt_event_KeyEvent_VK_UNDEFINED},
};
static const struct KeyTableEntry keyTableJISOverride[] = {
{0x18, YES, YES, KL_STANDARD, java_awt_event_KeyEvent_VK_CIRCUMFLEX},
{0x1E, YES, YES, KL_STANDARD, java_awt_event_KeyEvent_VK_OPEN_BRACKET},
{0x21, YES, YES, KL_STANDARD, java_awt_event_KeyEvent_VK_AT},
{0x27, YES, YES, KL_STANDARD, java_awt_event_KeyEvent_VK_COLON},
{0x2A, YES, YES, KL_STANDARD, java_awt_event_KeyEvent_VK_CLOSE_BRACKET},
// Some other keys are already handled in the previous table, no need to repeat them here
};
/*
* This table was stolen from the Windows implementation for mapping
* Unicode values to VK codes for dead keys. On Windows, some layouts
@@ -213,6 +228,7 @@ struct CharToVKEntry {
};
static const struct CharToVKEntry charToDeadVKTable[] = {
{0x0060, java_awt_event_KeyEvent_VK_DEAD_GRAVE},
{0x0027, java_awt_event_KeyEvent_VK_DEAD_ACUTE},
{0x00B4, java_awt_event_KeyEvent_VK_DEAD_ACUTE},
{0x0384, java_awt_event_KeyEvent_VK_DEAD_ACUTE}, // Unicode "GREEK TONOS" -- Greek keyboard, semicolon key
{0x005E, java_awt_event_KeyEvent_VK_DEAD_CIRCUMFLEX},
@@ -222,6 +238,7 @@ static const struct CharToVKEntry charToDeadVKTable[] = {
{0x02D8, java_awt_event_KeyEvent_VK_DEAD_BREVE},
{0x02D9, java_awt_event_KeyEvent_VK_DEAD_ABOVEDOT},
{0x00A8, java_awt_event_KeyEvent_VK_DEAD_DIAERESIS},
{0x00B0, java_awt_event_KeyEvent_VK_DEAD_ABOVERING},
{0x02DA, java_awt_event_KeyEvent_VK_DEAD_ABOVERING},
{0x02DD, java_awt_event_KeyEvent_VK_DEAD_DOUBLEACUTE},
{0x02C7, java_awt_event_KeyEvent_VK_DEAD_CARON},
@@ -434,12 +451,30 @@ unichar NsCharToJavaChar(unichar nsChar, NSUInteger modifiers, BOOL spaceKeyType
return nsChar;
}
static unichar NsGetDeadKeyChar(unsigned short keyCode, BOOL useModifiers)
struct KeyCodeTranslationResult {
unichar character;
BOOL isSuccess;
BOOL isDead;
BOOL isTyped;
};
static struct KeyCodeTranslationResult NsTranslateKeyCode(TISInputSourceRef layout, unsigned short keyCode, BOOL useModifiers)
{
TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
CFDataRef uchr = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);
if (uchr == nil) { return 0; }
struct KeyCodeTranslationResult result = {
.character = (unichar)0,
.isSuccess = NO,
.isDead = NO,
.isTyped = NO
};
CFDataRef uchr = (CFDataRef)TISGetInputSourceProperty(layout, kTISPropertyUnicodeKeyLayoutData);
if (uchr == nil) {
return result;
}
const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout*)CFDataGetBytePtr(uchr);
if (keyboardLayout == NULL) {
return result;
}
UInt32 modifierKeyState = 0;
if (useModifiers) {
@@ -447,35 +482,64 @@ static unichar NsGetDeadKeyChar(unsigned short keyCode, BOOL useModifiers)
modifierKeyState = (GetCurrentEventKeyModifiers() >> 8) & 0xFF;
}
if (keyboardLayout) {
UInt32 deadKeyState = 0;
UniCharCount maxStringLength = 255;
UniCharCount actualStringLength = 0;
UniChar unicodeString[maxStringLength];
UInt32 deadKeyState = 0;
const UniCharCount maxStringLength = 255;
UniCharCount actualStringLength = 0;
UniChar unicodeString[maxStringLength];
// get the deadKeyState
OSStatus status = UCKeyTranslate(keyboardLayout,
keyCode, kUCKeyActionDown, modifierKeyState,
LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit,
&deadKeyState,
maxStringLength,
&actualStringLength, unicodeString);
// get the deadKeyState
OSStatus status = UCKeyTranslate(keyboardLayout,
keyCode, kUCKeyActionDown, modifierKeyState,
LMGetKbdType(), 0,
&deadKeyState,
maxStringLength,
&actualStringLength, unicodeString);
if (status == noErr && deadKeyState != 0) {
// Press SPACE to get the dead key char
status = UCKeyTranslate(keyboardLayout,
kVK_Space, kUCKeyActionDown, 0,
LMGetKbdType(), 0,
&deadKeyState,
maxStringLength,
&actualStringLength, unicodeString);
if (status == noErr && actualStringLength > 0) {
return unicodeString[0];
}
}
if (status != noErr) {
return result;
}
return 0;
if (deadKeyState == 0) {
result.isSuccess = YES;
result.isDead = NO;
if (actualStringLength > 0) {
result.isTyped = YES;
// This is the character that will determine the Java key code of this key.
// There are some keys (even on ASCII-capable key layouts) that produce more than one
// code point (not just more than one UTF-16 code unit mind you!). It's unclear how one
// would go around constructing an extended key code for these keys. Luckily, if we
// use the last code unit to construct the extended key codes, there won't be any collisions
// among the standard macOS ASCII-capable key layouts. That seems good enough to me.
result.character = unicodeString[actualStringLength - 1];
}
return result;
}
deadKeyState = 0;
// Extract the dead key non-combining character
status = UCKeyTranslate(keyboardLayout,
keyCode, kUCKeyActionDown, modifierKeyState,
LMGetKbdType(), kUCKeyTranslateNoDeadKeysMask,
&deadKeyState,
maxStringLength,
&actualStringLength, unicodeString);
if (status != noErr) {
return result;
}
result.isSuccess = YES;
result.isDead = YES;
if (actualStringLength > 0) {
result.character = unicodeString[actualStringLength - 1];
}
return result;
}
/*
@@ -483,127 +547,200 @@ static unichar NsGetDeadKeyChar(unsigned short keyCode, BOOL useModifiers)
* NSEvent keyCodes and translate to the Java virtual key code.
*/
static void
NsCharToJavaVirtualKeyCode(unichar ch, BOOL isDeadChar,
NSUInteger flags, unsigned short key, const BOOL useNationalLayouts,
jint *keyCode, jint *keyLocation, BOOL *postsTyped,
unichar *deadChar)
NsCharToJavaVirtualKeyCode(unsigned short key, const BOOL useNationalLayouts,
jint *keyCode, jint *keyLocation)
{
static const size_t keyTableSize = sizeof(keyTable) / sizeof(struct _key);
// This is going to be a lengthy explanation about what it is that we need to achieve in this function.
// It took me quite a while to figure out myself, so hopefully it will be useful to others as well.
// I will describe the desired behavior when useNationalLayouts = true. Setting this parameter to false should
// ideally make the behavior identical to the one of OpenJDK, barring a few obvious bugfixes, like JBR-3860.
//
// For clarity here's what I mean by certain phrases:
// - Input source: what macOS calls "Keyboard layout input source", so excluding emoji pickers and handwriting
// - Key layout: Input source in the com.apple.keylayout namespace, i.e. a "simple" keyboard
// - IME: Input source in the com.apple.inputmethod namespace, i.e. a "complex" input method
// - Physical layout: A property of the physical keyboard device that has to do with the physical location of keys and their mapping to key codes
// - Underlying key layout: The key layout which actually translates the keys that the user presses to Java events.
// - Key code: A macOS virtual key code (the property `keyCode` on `NSEvent`)
// - Java key code: The values returned by `KeyEvent.getKeyCode()`
// - Key: Keyboard key without any modifiers
// - Combo: Keyboard key with modifiers
// - Dead key/combo: A key/combo that sets a dead key state when interpreted using UCKeyTranslate
//
// Whenever I refer to a key on the physical keyboard I will use the US layout.
//
// This function needs to determine the following:
// - Java key code
// - Java key location
//
// Obtaining the key location is fairly easy, since it can be looked up in the table by key code and physical layout type.
//
// To understand how to obtain the Java key code, let's take a look at the types of input sources that we want to handle:
// - Latin-based key layouts (ABC, German, French, Spanish, etc)
// - Non-latin-based key layouts (Arabic, Armenian, Russian, etc)
// - Latin-based IMEs (Pinyin, Cantonese - Phonetic, Japanese Romaji, etc.)
// - Non-latin-based IMEs (Korean, Zhuyin, Japanese Kana, etc.)
//
// These are possible physical layouts supported on macOS:
// - ANSI (North America, most of Asia and others)
// - ISO (Europe, Latin America, Middle East and others)
// - JIS (Japan)
//
// As a rule, any input source can be used on any physical layout.
// This might cause some key codes to correspond to different characters on the same input source.
//
// Basically we want the following behavior:
// - Latin-based key layouts should report their own keys unchanged.
// - Other input sources should report the key on their underlying key layout.
//
// Latin-based IMEs make it easy to determine the underlying key layout.
// macOS allows us to obtain a copy of the input source by calling TISCopyCurrentKeyboardLayoutInputSource().
// There's also the TISCopyInputMethodKeyboardLayoutOverride() function, but certain IMs (Google Japanese IME)
// don't set it properly.
//
// Non-latin-based key layouts and IMEs will use the US key layout as the underlying one.
// This is the behavior of native apps.
//
// Java has builtin key codes for most characters that can appear at the base layer of various key layouts.
// The rest are constructed like this: 0x01000000 + codePoint. All keys on builtin ASCII-capable layouts produce
// no surrogate pairs, but some of them can produce strings containing more than one code point. These need to be
// dealt with carefully as to avoid having different keys produce same Java key codes.
//
// Here's the various groups of named Java key codes that we need to handle:
// - Fixed keys that don't vary between input sources: VK_SPACE, VK_SHIFT, VK_NUMPAD0-VK_NUMPAD9, VK_F1-VK_F24, etc.
// - Dead keys: VK_DEAD_ACUTE, VK_DEAD_GRAVE, etc.
// - Punctuation: VK_PLUS, VK_SLASH, VK_SEMICOLON, etc.
// - Latin letters: VK_A-VK_Z
// - Numbers: VK_0-VK_9
//
// Fixed keys are hardcoded in keyTable and keyTableJISOverride.
//
// Dead keys need to be mapped into the corresponding VK_DEAD_ key codes in the same way the normal keys are mapped,
// that is using an underlying layout. This is done by using the UCKeyTranslate function together with charToDeadVKTable.
// It is possible to extract a (usually) non-combining dead key character by calling UCKeyTranslate
// with the kUCKeyTranslateNoDeadKeysMask option.
//
// Punctuation is hardcoded in extraCharToVKTable. Latin letters and numbers are dealt with separately.
//
// Bonus! What does it mean to have the "national layouts" disabled? In my opinion this simply means that
// the underlying key layout is the one that the user currently uses, or the override key layout for the input method
// that the user currently uses. I think this approach strikes the right balance between preserving compatibility
// with OpenJDK where it matters, while at the same time fixing a lot of annoying bugs.
NSInteger offset;
static const size_t keyTableSize = sizeof(keyTable) / sizeof(struct KeyTableEntry);
static const size_t keyTableJISOverrideSize = sizeof(keyTableJISOverride) / sizeof(struct KeyTableEntry);
BOOL isJIS = KBGetLayoutType(LMGetKbdType()) == kKeyboardJIS;
// If the key without modifiers generates a dead char, then this is the character
// that is produced when pressing the key followed by a space
// Otherwise, it's the null character
unichar testDeadCharWithoutModifiers = NsGetDeadKeyChar(key, NO);
// Find out which key does the key code correspond to in the US/ABC key layout.
// Need to take into account that the same virtual key code may correspond to
// different keys depending on the physical layout.
if (testDeadCharWithoutModifiers != 0) {
// Same as testDeadCharWithoutModifiers above, only this time we take modifiers into account.
unichar testDeadChar = NsGetDeadKeyChar(key, YES);
const struct KeyTableEntry* usKey = &unknownKeyEntry;
const struct CharToVKEntry *map;
for (map = charToDeadVKTable; map->c != 0; ++map) {
if (testDeadCharWithoutModifiers == map->c) {
// The base key is a dead key in the current layout.
// The key with modifiers might or might not be dead.
// We report it here so as not to cause any confusion,
// since non-dead keys can reuse the same characters as dead keys
if (key < keyTableSize) {
usKey = &keyTable[key];
}
*keyCode = map->javaKey;
*postsTyped = (BOOL)(testDeadChar == 0);
// TODO: use UNKNOWN here?
*keyLocation = java_awt_event_KeyEvent_KEY_LOCATION_UNKNOWN;
*deadChar = testDeadChar;
return;
if (isJIS) {
for (int i = 0; i < keyTableJISOverrideSize; ++i) {
if (keyTableJISOverride[i].keyCode == key) {
usKey = &keyTableJISOverride[i];
break;
}
}
}
if (key < keyTableSize) {
// US physical key -> character mapping
*postsTyped = keyTable[key].postsTyped;
*keyCode = keyTable[key].javaKeyCode;
*keyLocation = keyTable[key].javaKeyLocation;
// Determine the underlying layout.
// If underlyingLayout is nil then fall back to using the usKey.
if (!keyTable[key].variesBetweenLayouts) {
return;
}
} else {
// Should we report this? This means we've got a keyboard
// we don't know about...
*postsTyped = NO;
*keyCode = java_awt_event_KeyEvent_VK_UNDEFINED;
*keyLocation = java_awt_event_KeyEvent_KEY_LOCATION_UNKNOWN;
// TISCopyCurrentKeyboardLayoutInputSource() should always return a key layout
// that has valid unicode character data for use with the UCKeyTranslate() function.
// This is more robust than checking whether the current input source has key layout data
// and then falling back to the override input source if it doesn't. This is because some
// custom IMEs don't set the override input source properly.
TISInputSourceRef currentLayout = TISCopyCurrentKeyboardLayoutInputSource();
Boolean currentAscii = currentLayout == nil ? NO :
CFBooleanGetValue((CFBooleanRef) TISGetInputSourceProperty(currentLayout, kTISPropertyInputSourceIsASCIICapable));
TISInputSourceRef underlyingLayout = (!useNationalLayouts || currentAscii) ? currentLayout : nil;
// Default to returning the US key data.
*keyCode = usKey->javaKeyCode;
*keyLocation = usKey->javaKeyLocation;
if (underlyingLayout == nil || !usKey->variesBetweenLayouts) {
return;
}
TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
// Translate the key using the underlying key layout.
struct KeyCodeTranslationResult translatedKey = NsTranslateKeyCode(underlyingLayout, key, NO);
// Whether this is a latin-based keyboard layout (English, German, French, etc)
BOOL asciiCapable = (BOOL)((Boolean)CFBooleanGetValue(
(CFBooleanRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyInputSourceIsASCIICapable)));
unichar testLowercaseChar = tolower(ch);
if (!useNationalLayouts || asciiCapable) {
// If national layouts are enabled and the current keyboard is latin-based then
// we try to look up a character in a table first, before falling back to looking up
// the virtual key code from macOS's hardware key code, since hardware key codes
// don't respect the specific keyboard layout the user uses.
// The same happens when the national layouts are disabled to be consistent
// with the default behavior of OpenJDK.
// Together with the following two checks (letters and digits) this table
// properly handles all keys that have corresponding VK_ codes.
// Unfortunately not all keys are like that. They are handled separately.
for (const struct CharToVKEntry *map = extraCharToVKTable; map->c != 0; ++map) {
if (map->c == testLowercaseChar) {
// Test whether this key is dead.
if (translatedKey.isDead) {
for (const struct CharToVKEntry *map = charToDeadVKTable; map->c != 0; ++map) {
if (translatedKey.character == map->c) {
*keyCode = map->javaKey;
*postsTyped = !isDeadChar;
*keyLocation = java_awt_event_KeyEvent_KEY_LOCATION_STANDARD;
return;
}
}
// No builtin VK_DEAD_ constant for this dead key,
// nothing better to do than to fall back to the extended key code.
// This can happen on the following ascii-capable key layouts:
// - Apache (com.apple.keylayout.Apache)
// - Chickasaw (com.apple.keylayout.Chickasaw)
// - Choctaw (com.apple.keylayout.Choctaw)
// - Navajo (com.apple.keylayout.Navajo)
// - Vietnamese (com.apple.keylayout.Vietnamese)
// Vietnamese layout is unique among these in that the "dead key" is actually a self-containing symbol,
// that can be modified by an accent typed after it. In essence, it's like a dead key in reverse:
// the user should first type the letter and only then the necessary accent.
// This way the key code would be what the user expects.
*keyCode = 0x1000000 + translatedKey.character;
return;
}
if (testLowercaseChar >= 'a' && testLowercaseChar <= 'z') {
unichar ch = 0;
if (translatedKey.isTyped) {
ch = translatedKey.character;
}
// Together with the following two checks (letters and digits) this table
// properly handles all keys that have corresponding VK_ codes.
// Unfortunately not all keys are like that. They are handled separately.
for (const struct CharToVKEntry *map = extraCharToVKTable; map->c != 0; ++map) {
if (map->c == ch) {
*keyCode = map->javaKey;
return;
}
}
if (ch >= 'a' && ch <= 'z') {
// key is a basic latin letter
*postsTyped = YES;
*keyCode = java_awt_event_KeyEvent_VK_A + testLowercaseChar - 'a';
*keyLocation = java_awt_event_KeyEvent_KEY_LOCATION_STANDARD;
*keyCode = java_awt_event_KeyEvent_VK_A + ch - 'a';
return;
}
if (ch >= '0' && ch <= '9') {
// key is a digit
// numpad digits are already handled, since they don't vary between layouts
offset = ch - '0';
*keyCode = offset + java_awt_event_KeyEvent_VK_0;
*keyLocation = java_awt_event_KeyEvent_KEY_LOCATION_STANDARD;
*keyCode = java_awt_event_KeyEvent_VK_0 + ch - '0';
return;
}
BOOL isLetter = [[NSCharacterSet letterCharacterSet] characterIsMember:ch];
BOOL needExtendedKeyCodeConversion = useNationalLayouts ? asciiCapable : isLetter;
if (needExtendedKeyCodeConversion) {
if (useNationalLayouts || [[NSCharacterSet letterCharacterSet] characterIsMember:ch]) {
// If useNationalLayouts = false, then we only convert the key codes for letters here.
// This is the default behavior in OpenJDK and I don't think it's a good idea to change that.
// If useNationalLayouts = true but the keyboard is not ASCII-capable then this conversion
// doesn't happen, meaning that key codes remain in the US layout.
// Otherwise we also need to report characters other than letters.
// If we ended up in this branch, this means that the character doesn't have its own VK_ code.
// Apart from letters, this is the case for characters like the Section Sign (U+00A7) on the
// US ISO English keyboard or the Left-Pointing Double Angle Quotation Mark (U+00AB) found on the
// Canadian French - PC (ISO) keyboard. I couldn't find examples of ANSI keyboards that have non-letter
// characters that don't have a VK_ code.
// Apart from letters, this is the case for characters like the Section Sign (U+00A7)
// on the French keyboard (key ANSI_6) or Pound Sign (U+00A3) on the Italian QZERTY keyboard (key ANSI_8).
*postsTyped = YES;
*keyCode = 0x01000000 + testLowercaseChar;
*keyLocation = java_awt_event_KeyEvent_KEY_LOCATION_STANDARD;
*keyCode = 0x01000000 + ch;
}
}
@@ -642,8 +779,6 @@ NsKeyModifiersToJavaKeyInfo(NSUInteger nsFlags, unsigned short eventKeyCode,
*javaKeyLocation = java_awt_event_KeyEvent_KEY_LOCATION_LEFT;
} else if (eventKeyCode == cur->rightKeyCode) {
*javaKeyLocation = java_awt_event_KeyEvent_KEY_LOCATION_RIGHT;
} else if (cur->nsMask == NSAlternateKeyMask) {
continue;
}
*javaKeyType = (cur->nsMask & nsFlags) ?
java_awt_event_KeyEvent_KEY_PRESSED :
@@ -774,47 +909,36 @@ JNI_COCOA_EXIT(env);
/*
* Class: sun_lwawt_macosx_NSEvent
* Method: nsToJavaKeyInfo
* Signature: ([I[I)Z
* Signature: ([I[I)V
*/
JNIEXPORT jboolean JNICALL
JNIEXPORT void JNICALL
Java_sun_lwawt_macosx_NSEvent_nsToJavaKeyInfo
(JNIEnv *env, jclass cls, jintArray inData, jintArray outData)
{
BOOL postsTyped = NO;
JNI_COCOA_ENTER(env);
jboolean copy = JNI_FALSE;
jint *data = (*env)->GetIntArrayElements(env, inData, &copy);
CHECK_NULL_RETURN(data, postsTyped);
CHECK_NULL(data);
// in = [testChar, testDeadChar, modifierFlags, keyCode, useNationalLayouts]
jchar testChar = (jchar)data[0];
BOOL isDeadChar = (data[1] != 0);
jint modifierFlags = data[2];
jshort keyCode = (jshort)data[3];
BOOL useNationalLayouts = (data[4] != 0);
// in = [keyCode, useNationalLayouts]
jshort keyCode = (jshort)data[0];
BOOL useNationalLayouts = (data[1] != 0);
jint jkeyCode = java_awt_event_KeyEvent_VK_UNDEFINED;
jint jkeyLocation = java_awt_event_KeyEvent_KEY_LOCATION_UNKNOWN;
jint testDeadChar = 0;
NsCharToJavaVirtualKeyCode((unichar)testChar, isDeadChar,
(NSUInteger)modifierFlags, (unsigned short)keyCode,
NsCharToJavaVirtualKeyCode((unsigned short)keyCode,
useNationalLayouts,
&jkeyCode, &jkeyLocation, &postsTyped,
(unichar *) &testDeadChar);
&jkeyCode, &jkeyLocation);
// out = [jkeyCode, jkeyLocation, deadChar];
// out = [jkeyCode, jkeyLocation];
(*env)->SetIntArrayRegion(env, outData, 0, 1, &jkeyCode);
(*env)->SetIntArrayRegion(env, outData, 1, 1, &jkeyLocation);
(*env)->SetIntArrayRegion(env, outData, 2, 1, &testDeadChar);
(*env)->ReleaseIntArrayElements(env, inData, data, 0);
JNI_COCOA_EXIT(env);
return postsTyped;
}
/*

View File

@@ -45,7 +45,7 @@
jobject fInputMethodLOCKABLE;
BOOL fKeyEventsNeeded;
BOOL fProcessingKeystroke;
BOOL fComplexInputNeeded;
NSString* actualCharacters;
BOOL fEnablePressAndHold;
BOOL fInPressAndHold;

View File

@@ -50,13 +50,13 @@ static NSString *kbdLayout;
-(void) deliverResize: (NSRect) rect;
-(void) resetTrackingArea;
-(void) deliverJavaKeyEventHelper: (NSEvent*) event;
-(BOOL) isCodePointInUnicodeBlockNeedingIMEvent: (unichar) codePoint;
-(NSMutableString *) parseString : (id) complexString;
@end
// Uncomment this line to see fprintfs of each InputMethod API being called on this View
//#define IM_DEBUG TRUE
//#define EXTRA_DEBUG
//#define LOG_KEY_EVENTS
static BOOL shouldUsePressAndHold() {
return YES;
@@ -85,7 +85,6 @@ extern bool isSystemShortcut_NextWindowInApplication(NSUInteger modifiersMask, N
fInputMethodLOCKABLE = NULL;
fKeyEventsNeeded = NO;
fProcessingKeystroke = NO;
fComplexInputNeeded = NO;
fEnablePressAndHold = shouldUsePressAndHold();
fInPressAndHold = NO;
@@ -303,22 +302,66 @@ extern bool isSystemShortcut_NextWindowInApplication(NSUInteger modifiersMask, N
* KeyEvents support
*/
- (void) keyDown: (NSEvent *)event {
fProcessingKeystroke = YES;
fKeyEventsNeeded = YES;
fComplexInputNeeded = NO;
NSString *eventCharacters = [event characters];
if ([eventCharacters length] > 0) {
unichar codePoint = [eventCharacters characterAtIndex:0];
if ((codePoint >= 0x3000 && codePoint <= 0x303F) || (codePoint >= 0xff00 && codePoint <= 0xffef)) {
// "CJK Symbols and Punctuation" or "Halfwidth and Fullwidth Forms"
// Force the complex input method because macOS doesn't properly send us
// the half-width characters when the user has them enabled.
fComplexInputNeeded = YES;
#ifdef LOG_KEY_EVENTS
static void debugPrintNSString(const char* name, NSString* s) {
if (s == nil) {
fprintf(stderr, "\t%s: nil\n", name);
return;
}
const char* utf8 = [s UTF8String];
int codeUnits = [s length];
int bytes = strlen(utf8);
fprintf(stderr, "\t%s: [utf8 = \"", name);
for (const unsigned char* c = (const unsigned char*)utf8; *c; ++c) {
if (*c >= 0x20 && *c <= 0x7e) {
fputc(*c, stderr);
} else {
fprintf(stderr, "\\x%02x", *c);
}
}
fprintf(stderr, "\", utf16 = \"");
for (int i = 0; i < codeUnits; ++i) {
int c = (int)[s characterAtIndex:i];
if (c >= 0x20 && c <= 0x7e) {
fputc(c, stderr);
} else {
fprintf(stderr, "\\u%04x", c);
}
}
fprintf(stderr, "\", bytes = %d, codeUnits = %d]\n", bytes, codeUnits);
}
static void debugPrintNSEvent(NSEvent* event, const char* comment) {
NSEventType type = [event type];
if (type == NSEventTypeKeyDown) {
fprintf(stderr, "[AWTView.m] keyDown in %s\n", comment);
} else if (type == NSEventTypeKeyUp) {
fprintf(stderr, "[AWTView.m] keyUp in %s\n", comment);
} else if (type == NSEventTypeFlagsChanged) {
fprintf(stderr, "[AWTView.m] flagsChanged in %s\n", comment);
} else {
fprintf(stderr, "[AWTView.m] unknown event %d in %s\n", (int)type, comment);
return;
}
if (type == NSEventTypeKeyDown || type == NSEventTypeKeyUp) {
debugPrintNSString("characters", [event characters]);
debugPrintNSString("charactersIgnoringModifiers", [event charactersIgnoringModifiers]);
fprintf(stderr, "\tkeyCode: %d (0x%02x)\n", [event keyCode], [event keyCode]);
}
fprintf(stderr, "\tmodifierFlags: 0x%08x\n", (unsigned)[event modifierFlags]);
TISInputSourceRef is = TISCopyCurrentKeyboardLayoutInputSource();
fprintf(stderr, "\tTISCopyCurrentKeyboardLayoutInputSource: %s\n", is == nil ? "(nil)" : [(NSString*) TISGetInputSourceProperty(is, kTISPropertyInputSourceID) UTF8String]);
}
#endif
- (void) keyDown: (NSEvent *)event {
#ifdef LOG_KEY_EVENTS
debugPrintNSEvent(event, "keyDown");
#endif
fProcessingKeystroke = YES;
fKeyEventsNeeded = YES;
NSString *eventCharacters = [event characters];
// Allow TSM to look at the event and potentially send back NSTextInputClient messages.
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
@@ -364,18 +407,32 @@ extern bool isSystemShortcut_NextWindowInApplication(NSUInteger modifiersMask, N
[self deliverJavaKeyEventHelper: event];
}
if (actualCharacters != nil) {
[actualCharacters release];
actualCharacters = nil;
}
fProcessingKeystroke = NO;
}
- (void) keyUp: (NSEvent *)event {
#ifdef LOG_KEY_EVENTS
debugPrintNSEvent(event, "keyUp");
#endif
[self deliverJavaKeyEventHelper: event];
}
- (void) flagsChanged: (NSEvent *)event {
#ifdef LOG_KEY_EVENTS
debugPrintNSEvent(event, "flagsChanged");
#endif
[self deliverJavaKeyEventHelper: event];
}
- (BOOL) performKeyEquivalent: (NSEvent *) event {
#ifdef LOG_KEY_EVENTS
debugPrintNSEvent(event, "performKeyEquivalent");
#endif
// if IM is active key events should be ignored
if (![self hasMarkedText] && !fInPressAndHold) {
[self deliverJavaKeyEventHelper: event];
@@ -536,41 +593,6 @@ extern bool isSystemShortcut_NextWindowInApplication(NSUInteger modifiersMask, N
[self resetTrackingArea];
}
- (NSString *) extractCharactersIgnoringAllModifiers: (NSEvent *) event {
// event.charactersIgnoringModifiers is actually not what we want, since it doesn't ignore Shift.
// What we really want is event.charactersByApplyingModifiers:0, but that's only available on macOS 10.15+
// The code below simulates what that function does by looking up the current keyboard and emulating
// a corresponding key press on it.
TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
CFDataRef uchr = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);
if (uchr == nil) {
return [event charactersIgnoringModifiers];
}
UInt32 deadKeyState = 0;
UniCharCount maxStringLength = 8;
UniCharCount actualStringLength = 0;
unichar unicodeString[maxStringLength];
const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *) CFDataGetBytePtr(uchr);
OSStatus status = UCKeyTranslate(
keyboardLayout,
[event keyCode],
kUCKeyActionDown,
0,
LMGetKbdType(),
kUCKeyTranslateNoDeadKeysMask,
&deadKeyState,
maxStringLength,
&actualStringLength,
unicodeString
);
return [NSString stringWithCharacters:unicodeString length:actualStringLength];
}
-(void) deliverJavaKeyEventHelper: (NSEvent *) event {
static NSEvent* sLastKeyEvent = nil;
if (event == sLastKeyEvent) {
@@ -584,20 +606,22 @@ extern bool isSystemShortcut_NextWindowInApplication(NSUInteger modifiersMask, N
JNIEnv *env = [ThreadUtilities getJNIEnv];
jstring characters = NULL;
jstring charactersIgnoringModifiers = NULL;
if ([event type] != NSEventTypeFlagsChanged) {
characters = NSStringToJavaString(env, [event characters]);
charactersIgnoringModifiers = NSStringToJavaString(env, [self extractCharactersIgnoringAllModifiers:event]);
}
jstring jActualCharacters = NULL;
if (actualCharacters != nil) {
jActualCharacters = NSStringToJavaString(env, actualCharacters);
}
DECLARE_CLASS(jc_NSEvent, "sun/lwawt/macosx/NSEvent");
DECLARE_METHOD(jctor_NSEvent, jc_NSEvent, "<init>", "(IISLjava/lang/String;Ljava/lang/String;)V");
jobject jEvent = (*env)->NewObject(env, jc_NSEvent, jctor_NSEvent,
[event type],
[event modifierFlags],
[event keyCode],
characters,
charactersIgnoringModifiers);
[event type],
[event modifierFlags],
[event keyCode],
characters,
jActualCharacters);
CHECK_NULL(jEvent);
DECLARE_CLASS(jc_PlatformView, "sun/lwawt/macosx/CPlatformView");
@@ -612,6 +636,9 @@ extern bool isSystemShortcut_NextWindowInApplication(NSUInteger modifiersMask, N
if (characters != NULL) {
(*env)->DeleteLocalRef(env, characters);
}
if (jActualCharacters != NULL) {
(*env)->DeleteLocalRef(env, jActualCharacters);
}
(*env)->DeleteLocalRef(env, jEvent);
}
@@ -670,30 +697,6 @@ extern bool isSystemShortcut_NextWindowInApplication(NSUInteger modifiersMask, N
}
}
-(BOOL) isChineseInputMethod {
return ([kbdLayout containsString:@"com.apple.inputmethod.SCIM"] ||
[kbdLayout containsString:@"com.apple.inputmethod.TCIM"] ||
[kbdLayout containsString:@"com.apple.inputmethod.TYIM"]);
}
-(BOOL) isCodePointInUnicodeBlockNeedingIMEvent: (unichar) codePoint {
if ((codePoint == 0x2018 || codePoint == 0x2019 || codePoint == 0x201C || codePoint == 0x201D) && [self isChineseInputMethod]) {
// left/right single/double quotation mark
return YES;
}
if (((codePoint >= 0x900) && (codePoint <= 0x97F)) ||
((codePoint >= 0x20A3) && (codePoint <= 0x20BF)) ||
((codePoint >= 0x3000) && (codePoint <= 0x303F)) ||
((codePoint >= 0xFF00) && (codePoint <= 0xFFEF))) {
// Code point is in 'CJK Symbols and Punctuation' or
// 'Halfwidth and Fullwidth Forms' Unicode block or
// currency symbols unicode or Devanagari script
return YES;
}
return NO;
}
-(NSMutableString *) parseString : (id) complexString {
if ([complexString isKindOfClass:[NSString class]]) {
return [complexString mutableCopy];
@@ -1104,57 +1107,41 @@ static jclass jc_CInputMethod = NULL;
// Unicode value.
NSMutableString * useString = [self parseString:aString];
NSUInteger utf16Length = [useString lengthOfBytesUsingEncoding:NSUTF16StringEncoding];
NSUInteger utf8Length = [useString lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
BOOL aStringIsComplex = NO;
unichar codePoint = [useString characterAtIndex:0];
BOOL usingComplexIM = [self hasMarkedText] || !fProcessingKeystroke;
#ifdef IM_DEBUG
NSUInteger utf16Length = [useString lengthOfBytesUsingEncoding:NSUTF16StringEncoding];
NSUInteger utf8Length = [useString lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
NSLog(@"insertText kbdlayout %@ ",(NSString *)kbdLayout);
NSLog(@"utf8Length %lu utf16Length %lu", (unsigned long)utf8Length, (unsigned long)utf16Length);
NSLog(@"codePoint %x", codePoint);
#endif // IM_DEBUG
if ((utf16Length > 2) ||
((utf8Length > 1) && [self isCodePointInUnicodeBlockNeedingIMEvent:codePoint]) ||
((codePoint == 0x5c) && ([(NSString *)kbdLayout containsString:@"Kotoeri"]))) {
#ifdef IM_DEBUG
NSLog(@"string complex ");
#endif
aStringIsComplex = YES;
JNIEnv *env = [ThreadUtilities getJNIEnv];
GET_CIM_CLASS();
// We need to select the previous glyph so that it is overwritten.
if (fPAHNeedsToSelect) {
DECLARE_METHOD(jm_selectPreviousGlyph, jc_CInputMethod, "selectPreviousGlyph", "()V");
(*env)->CallVoidMethod(env, fInputMethodLOCKABLE, jm_selectPreviousGlyph);
CHECK_EXCEPTION();
fPAHNeedsToSelect = NO;
}
if ([self hasMarkedText] || !fProcessingKeystroke || aStringIsComplex || fComplexInputNeeded) {
JNIEnv *env = [ThreadUtilities getJNIEnv];
GET_CIM_CLASS();
DECLARE_METHOD(jm_selectPreviousGlyph, jc_CInputMethod, "selectPreviousGlyph", "()V");
// We need to select the previous glyph so that it is overwritten.
if (fPAHNeedsToSelect) {
(*env)->CallVoidMethod(env, fInputMethodLOCKABLE, jm_selectPreviousGlyph);
CHECK_EXCEPTION();
fPAHNeedsToSelect = NO;
}
if (usingComplexIM) {
DECLARE_METHOD(jm_insertText, jc_CInputMethod, "insertText", "(Ljava/lang/String;)V");
jstring insertedText = NSStringToJavaString(env, useString);
(*env)->CallVoidMethod(env, fInputMethodLOCKABLE, jm_insertText, insertedText);
CHECK_EXCEPTION();
(*env)->DeleteLocalRef(env, insertedText);
// The input method event will create psuedo-key events for each character in the committed string.
// We also don't want to send the character that triggered the insertText, usually a return. [3337563]
fKeyEventsNeeded = NO;
fComplexInputNeeded = NO;
}
else {
// Need to set back the fKeyEventsNeeded flag so that the string following the
// marked text is not ignored by keyDown
if ([useString length] > 0) {
fKeyEventsNeeded = YES;
} else {
if (actualCharacters != nil) {
[actualCharacters release];
}
actualCharacters = [useString copy];
fKeyEventsNeeded = YES;
}
fPAHNeedsToSelect = NO;

View File

@@ -326,22 +326,29 @@ Java_sun_lwawt_macosx_CRobot_keyEvent
[ThreadUtilities performOnMainThreadWaiting:YES block:^(){
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
CGKeyCode keyCode;
if (javaKeyCode == 0x1000000 + 0x0060) {
if (javaKeyCode & 0x2000000) {
// This is a dirty, dirty hack and is only used in tests.
// When receiving this key code, Robot should switch the keyboard type to ISO
// and then send the key code corresponding to VK_BACK_QUOTE.
// This allows us to send macOS virtual key code directly, without first looking up a Java key code.
// It also allows the caller to set the physical keyboard layout directly.
// The key code that should be passed to Robot in this case is the following:
// 0x2000000 | keyCode | (physicalLayout << 8)
// where physicalLayout is:
// 0 - ANSI
// 1 - ISO
// 2 - JIS
// find an ISO keyboard type...
// LMGetKbdType() returns Uint8, why don't we just iterate over all the possible values and find one
// that works? It's really sad that macOS doesn't provide a decent API for this sort of thing.
for (UInt32 keyboardType = 0; keyboardType < 0x100; ++keyboardType) {
if (KBGetLayoutType(keyboardType) == kKeyboardISO) {
CGEventSourceSetKeyboardType(source, keyboardType);
break;
}
UInt32 physicalLayout = (javaKeyCode >> 8) & 0xff;
UInt32 keyboardType;
if (physicalLayout == 1) {
keyboardType = 41; // ISO
} else if (physicalLayout == 2) {
keyboardType = 42; // JIS
} else {
keyboardType = 40; // ANSI
}
keyCode = OSX_kVK_ANSI_Grave;
CGEventSourceSetKeyboardType(source, keyboardType);
keyCode = javaKeyCode & 0xff;
} else {
keyCode = GetCGKeyCode(javaKeyCode);
}

View File

@@ -99,6 +99,7 @@ const static int OSX_kVK_Tab = 0x30;
const static int OSX_kVK_Space = 0x31;
const static int OSX_Delete = 0x33;
const static int OSX_Escape = 0x35;
const static int OSX_RightCommand = 0x36;
const static int OSX_Command = 0x37;
const static int OSX_Shift = 0x38;
const static int OSX_CapsLock = 0x39;

View File

@@ -43,7 +43,7 @@ public class ExtendedKeyCodes {
// known keyboard layout. For instance, sterling sign is on the primary layer
// of the Mac Italian layout.
private static final HashSet<Integer> extendedKeyCodesSet =
new HashSet<>(496, 1.0f);
new HashSet<>(510, 1.0f);
public static int getExtendedKeyCodeForChar( int c ) {
int uc = Character.toUpperCase( c );
Integer regularKeyCode = regularKeyCodesMap.get(c);
@@ -657,5 +657,21 @@ public class ExtendedKeyCodes {
extendedKeyCodesSet.add(0x01000000+0x01A1);
extendedKeyCodesSet.add(0x01000000+0x01B0);
extendedKeyCodesSet.add(0x01000000+0x20AB);
// Additional keys discovered on the base layers of macOS ascii-capable keyboards
extendedKeyCodesSet.add(0x01000000+0x00A4);
extendedKeyCodesSet.add(0x01000000+0x00B8);
extendedKeyCodesSet.add(0x01000000+0x0219);
extendedKeyCodesSet.add(0x01000000+0x021B);
extendedKeyCodesSet.add(0x01000000+0x0254);
extendedKeyCodesSet.add(0x01000000+0x025B);
extendedKeyCodesSet.add(0x01000000+0x028B);
extendedKeyCodesSet.add(0x01000000+0x02BB);
extendedKeyCodesSet.add(0x01000000+0x02BC);
extendedKeyCodesSet.add(0x01000000+0x02C7);
extendedKeyCodesSet.add(0x01000000+0x0301);
extendedKeyCodesSet.add(0x01000000+0x0309);
extendedKeyCodesSet.add(0x01000000+0x0323);
extendedKeyCodesSet.add(0x01000000+0x0331);
}
}

View File

@@ -24,30 +24,47 @@
/**
* @test
* @summary Regression test for JBR-5006 Dead keys exhibit invalid behavior on macOS
* @run shell Runner.sh DeadKeysTest
* @modules java.desktop/sun.lwawt.macosx
* @run main InputMethodTest DeadKeysTest
* @requires (jdk.version.major >= 8 & os.family == "mac")
*/
import static java.awt.event.KeyEvent.*;
public class DeadKeysTest implements Runnable {
static private final int VK_SECTION = 0x01000000+0x00A7;
@Override
public void run() {
InputMethodTest.layout("com.apple.keylayout.ABC");
InputMethodTest.section("Acute accent + vowel");
InputMethodTest.section("ABC: Acute accent + vowel");
InputMethodTest.type(VK_E, ALT_DOWN_MASK);
InputMethodTest.type(VK_A, 0);
InputMethodTest.expect("\u00e1");
InputMethodTest.expectText("\u00e1");
InputMethodTest.section("Acute accent + consonant");
InputMethodTest.section("ABC: Acute accent + consonant");
InputMethodTest.type(VK_E, ALT_DOWN_MASK);
InputMethodTest.type(VK_S, 0);
InputMethodTest.expect("\u00b4s");
InputMethodTest.expectText("\u00b4s");
InputMethodTest.section("Acute accent + space");
InputMethodTest.section("ABC: Acute accent + space");
InputMethodTest.type(VK_E, ALT_DOWN_MASK);
InputMethodTest.type(VK_SPACE, 0);
InputMethodTest.expect("\u00b4");
InputMethodTest.expectText("\u00b4");
InputMethodTest.section("German - Standard: Opt+K, Section = Dead circumflex below");
InputMethodTest.layout("com.apple.keylayout.German-DIN-2137");
InputMethodTest.type(VK_K, ALT_DOWN_MASK);
InputMethodTest.type(VK_SECTION, 0);
InputMethodTest.type(VK_D, 0);
InputMethodTest.expectText("\u1e13");
InputMethodTest.section("UnicodeHexInput: U+0041 = A");
InputMethodTest.layout("com.apple.keylayout.UnicodeHexInput");
InputMethodTest.type(VK_0, ALT_DOWN_MASK);
InputMethodTest.type(VK_0, ALT_DOWN_MASK);
InputMethodTest.type(VK_4, ALT_DOWN_MASK);
InputMethodTest.type(VK_1, ALT_DOWN_MASK);
InputMethodTest.expectText("A");
}
}

View File

@@ -28,10 +28,11 @@ import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.*;
import java.util.List;
import java.util.Set;
import static java.awt.event.KeyEvent.KEY_PRESSED;
import static java.awt.event.KeyEvent.KEY_RELEASED;
public class InputMethodTest {
private static JFrame frame;
@@ -42,7 +43,7 @@ public class InputMethodTest {
private static String initialLayout;
private static final Set<String> addedLayouts = new HashSet<>();
private static boolean success = true;
private static int lastKeyCode = -1;
private static final List<KeyEvent> triggeredEvents = new ArrayList<>();
private enum TestCases {
DeadKeysTest (new DeadKeysTest()),
@@ -50,7 +51,11 @@ public class InputMethodTest {
PinyinCapsLockTest (new PinyinCapsLockTest()),
PinyinFullWidthPunctuationTest (new PinyinFullWidthPunctuationTest()),
PinyinHalfWidthPunctuationTest (new PinyinHalfWidthPunctuationTest()),
PinyinQuotesTest (new PinyinQuotesTest())
PinyinQuotesTest (new PinyinQuotesTest()),
RomajiYenTest (new RomajiYenTest(false)),
RomajiYenBackslashTest (new RomajiYenTest(true)),
UnderlyingLayoutQWERTYTest (new UnderlyingLayoutTest(false)),
UnderlyingLayoutQWERTZTest (new UnderlyingLayoutTest(true)),
;
private Runnable test;
@@ -79,7 +84,11 @@ public class InputMethodTest {
} catch (Exception ignored) {}
}
}
System.exit(success ? 0 : 1);
if (success) {
System.out.println("TEST PASSED");
} else {
throw new RuntimeException("TEST FAILED: check output");
}
}
private static void init() {
@@ -101,15 +110,19 @@ public class InputMethodTest {
textArea = new JTextArea();
textArea.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent keyEvent) {}
@Override
public void keyPressed(KeyEvent keyEvent) {
lastKeyCode = keyEvent.getKeyCode();
public void keyTyped(KeyEvent keyEvent) {
triggeredEvents.add(keyEvent);
}
@Override
public void keyReleased(KeyEvent keyEvent) {}
public void keyPressed(KeyEvent keyEvent) {
triggeredEvents.add(keyEvent);
}
@Override
public void keyReleased(KeyEvent keyEvent) {
triggeredEvents.add(keyEvent);
}
});
frame.setLayout(new BorderLayout());
@@ -127,20 +140,49 @@ public class InputMethodTest {
try {
TestCases.valueOf(name).run();
} catch (Exception e) {
System.out.printf("Test %s (%s) failed: %s\n", currentTest, currentSection, e);
System.out.printf("Test %s (%s) FAILED: %s\n", currentTest, currentSection, e);
success = false;
}
}
private static String readDefault(String domain, String key) {
try {
var proc = Runtime.getRuntime().exec(new String[]{"defaults", "read", domain, key});
var exitCode = proc.waitFor();
if (exitCode == 0) {
return new Scanner(proc.getInputStream()).next();
}
} catch (Exception exc) {
exc.printStackTrace();
throw new RuntimeException("internal error");
}
return null;
}
private static void writeDefault(String domain, String key, String value) {
try {
Runtime.getRuntime().exec(new String[]{"defaults", "write", domain, key, value}).waitFor();
} catch (Exception exc) {
exc.printStackTrace();
throw new RuntimeException("internal error");
}
}
public static void section(String description) {
// clear dead key state
robot.keyPress(KeyEvent.VK_ESCAPE);
robot.keyRelease(KeyEvent.VK_ESCAPE);
currentSection = description;
textArea.setText("");
frame.setTitle(currentTest + ": " + description);
triggeredEvents.clear();
}
public static void layout(String name) {
List<String> layouts = new ArrayList<>();
if (name.matches("com\\.apple\\.inputmethod\\.(SCIM|TCIM|TYIM)\\.\\w+")) {
if (name.matches("com\\.apple\\.inputmethod\\.(SCIM|TCIM|TYIM|Kotoeri\\.\\w+)\\.\\w+")) {
layouts.add(name.replaceFirst("\\.\\w+$", ""));
}
@@ -157,8 +199,34 @@ public class InputMethodTest {
robot.delay(250);
}
public static void setUseHalfWidthPunctuation(boolean flag) {
writeDefault("com.apple.inputmethod.CoreChineseEngineFramework", "usesHalfwidthPunctuation", flag ? "1" : "0");
}
private static void restartKotoeri() {
// Need to kill Kotoeri, since it doesn't reload the config otherwise. This makes me sad.
try {
Runtime.getRuntime().exec(new String[]{"killall", "-9", "-m", "JapaneseIM"}).waitFor();
} catch (Exception exc) {
exc.printStackTrace();
throw new RuntimeException("internal error");
}
// wait for it to restart...
robot.delay(5000);
}
public static void setUseBackslashInsteadOfYen(boolean flag) {
writeDefault("com.apple.inputmethod.Kotoeri", "JIMPrefCharacterForYenKey", flag ? "1" : "0");
restartKotoeri();
}
public static void setRomajiLayout(String layout) {
writeDefault("com.apple.inputmethod.Kotoeri", "JIMPrefRomajiKeyboardLayoutKey", layout);
restartKotoeri();
}
public static void type(int key, int modifiers) {
lastKeyCode = -1;
List<Integer> modKeys = new ArrayList<>();
if ((modifiers & InputEvent.ALT_DOWN_MASK) != 0) {
@@ -194,22 +262,58 @@ public class InputMethodTest {
robot.delay(250);
}
public static void expect(String expectedValue) {
public static List<KeyEvent> getTriggeredEvents() {
return Collections.unmodifiableList(triggeredEvents);
}
public static void expectText(String expectedValue) {
var actualValue = textArea.getText();
if (actualValue.equals(expectedValue)) {
System.out.printf("Test %s (%s) passed, got '%s'\n", currentTest, currentSection, actualValue);
System.out.printf("Test %s (%s) passed: got '%s'\n", currentTest, currentSection, actualValue);
} else {
success = false;
System.out.printf("Test %s (%s) failed, expected '%s', got '%s'\n", currentTest, currentSection, expectedValue, actualValue);
System.out.printf("Test %s (%s) FAILED: expected '%s', got '%s'\n", currentTest, currentSection, expectedValue, actualValue);
}
}
public static void expectKeyCode(int keyCode) {
if (lastKeyCode == keyCode) {
System.out.printf("Test %s (%s) passed, got key code %d\n", currentTest, currentSection, keyCode);
public static void expectKeyPress(int vk, int location, int modifiers, boolean strict) {
var pressed = triggeredEvents.stream().filter(e -> e.getID() == KEY_PRESSED).toList();
var released = triggeredEvents.stream().filter(e -> e.getID() == KEY_RELEASED).toList();
if (pressed.size() == 1 || (pressed.size() > 1 && !strict)) {
var keyCode = pressed.get(pressed.size() - 1).getKeyCode();
expectTrue(keyCode == vk, "key press, actual key code: " + keyCode + ", expected: " + vk);
var keyLocation = pressed.get(pressed.size() - 1).getKeyLocation();
expectTrue(keyLocation == location, "key press, actual key location: " + keyLocation + ", expected: " + location);
var keyModifiers = pressed.get(pressed.size() - 1).getModifiersEx();
expectTrue(keyModifiers == modifiers, "key press, actual key modifiers: " + keyModifiers + ", expected: " + modifiers);
} else {
success = false;
System.out.printf("Test %s (%s) failed, expected key code %d, got %d\n", currentTest, currentSection, keyCode, lastKeyCode);
if (strict) {
fail("expected exactly one KEY_PRESSED event, got " + pressed.size());
} else {
fail("expected at least one KEY_PRESSED event, got none");
}
}
if (released.size() == 1 || (released.size() > 1 && !strict)) {
var keyCode = released.get(0).getKeyCode();
expectTrue(keyCode == vk, "key release, actual key code: " + keyCode + ", expected: " + vk);
var keyLocation = released.get(0).getKeyLocation();
expectTrue(keyLocation == location, "key release, actual key location: " + keyLocation + ", expected: " + location);
if (strict) {
var keyModifiers = released.get(0).getModifiersEx();
expectTrue(keyModifiers == 0, "key release, actual key modifiers: " + keyModifiers + ", expected: 0");
}
} else {
if (strict) {
fail("expected exactly one KEY_RELEASED event, got " + released.size());
} else {
fail("expected at least one KEY_RELEASED event, got none");
}
}
}
@@ -218,7 +322,11 @@ public class InputMethodTest {
System.out.printf("Test %s (%s) passed: %s\n", currentTest, currentSection, comment);
} else {
success = false;
System.out.printf("Test %s (%s) failed: %s\n", currentTest, currentSection, comment);
System.out.printf("Test %s (%s) FAILED: %s\n", currentTest, currentSection, comment);
}
}
public static void fail(String comment) {
expectTrue(false, comment);
}
}

View File

@@ -24,90 +24,136 @@
/**
* @test
* @summary Regression test for JBR-5173 macOS keyboard support rewrite
* @run shell Runner.sh KeyCodesTest
* @modules java.desktop/sun.lwawt.macosx
* @run main InputMethodTest KeyCodesTest
* @requires (jdk.version.major >= 8 & os.family == "mac")
*/
import static java.awt.event.KeyEvent.*;
public class KeyCodesTest implements Runnable {
static private final int VK_BACK_QUOTE_ISO = 0x01000000+0x0060;
static private final int ROBOT_KEYCODE_BACK_QUOTE_ISO = 0x2000132;
static private final int ROBOT_KEYCODE_RIGHT_COMMAND = 0x2000036;
static private final int ROBOT_KEYCODE_RIGHT_SHIFT = 0x200003C;
static private final int ROBOT_KEYCODE_RIGHT_CONTROL = 0x200003E;
static private final int ROBOT_KEYCODE_YEN_SYMBOL_JIS = 0x200025D;
static private final int ROBOT_KEYCODE_CIRCUMFLEX_JIS = 0x2000218;
static private final int ROBOT_KEYCODE_NUMPAD_COMMA_JIS = 0x200025F;
static private final int ROBOT_KEYCODE_NUMPAD_ENTER = 0x200004C;
static private final int ROBOT_KEYCODE_NUMPAD_EQUALS = 0x2000051;
static private final int VK_SECTION = 0x01000000+0x00A7;
@Override
public void run() {
verify('!', VK_EXCLAMATION_MARK, "com.apple.keylayout.French-PC", VK_SLASH);
verify('"', VK_QUOTEDBL, "com.apple.keylayout.French-PC", VK_3);
verify('#', VK_NUMBER_SIGN, "com.apple.keylayout.British-PC", VK_BACK_SLASH);
verify('$', VK_DOLLAR, "com.apple.keylayout.French-PC", VK_CLOSE_BRACKET);
verify('&', VK_AMPERSAND, "com.apple.keylayout.French-PC", VK_1);
verify('\'', VK_QUOTE, "com.apple.keylayout.French-PC", VK_4);
verify('(', VK_LEFT_PARENTHESIS, "com.apple.keylayout.French-PC", VK_5);
verify(')', VK_RIGHT_PARENTHESIS, "com.apple.keylayout.French-PC", VK_MINUS);
verify('*', VK_ASTERISK, "com.apple.keylayout.French-PC", VK_BACK_SLASH);
verify('+', VK_PLUS, "com.apple.keylayout.German", VK_CLOSE_BRACKET);
verify(',', VK_COMMA, "com.apple.keylayout.ABC", VK_COMMA);
verify('-', VK_MINUS, "com.apple.keylayout.ABC", VK_MINUS);
verify('.', VK_PERIOD, "com.apple.keylayout.ABC", VK_PERIOD);
verify('/', VK_SLASH, "com.apple.keylayout.ABC", VK_SLASH);
verify(':', VK_COLON, "com.apple.keylayout.French-PC", VK_PERIOD);
verify(';', VK_SEMICOLON, "com.apple.keylayout.ABC", VK_SEMICOLON);
verify('<', VK_LESS, "com.apple.keylayout.French-PC", VK_BACK_QUOTE);
verify('=', VK_EQUALS, "com.apple.keylayout.ABC", VK_EQUALS);
// TODO: figure out which keyboard layout has VK_GREATER as a key on the primary layer
verify('@', VK_AT, "com.apple.keylayout.Norwegian", VK_BACK_SLASH);
verify('[', VK_OPEN_BRACKET, "com.apple.keylayout.ABC", VK_OPEN_BRACKET);
verify('\\', VK_BACK_SLASH, "com.apple.keylayout.ABC", VK_BACK_SLASH);
verify(']', VK_CLOSE_BRACKET, "com.apple.keylayout.ABC", VK_CLOSE_BRACKET);
// TODO: figure out which keyboard layout has VK_CIRCUMFLEX as a key on the primary layer
verify('_', VK_UNDERSCORE, "com.apple.keylayout.French-PC", VK_8);
verify('`', VK_BACK_QUOTE, "com.apple.keylayout.ABC", VK_BACK_QUOTE);
verify('{', VK_BRACELEFT, "com.apple.keylayout.LatinAmerican", VK_QUOTE);
verify('}', VK_BRACERIGHT, "com.apple.keylayout.LatinAmerican", VK_BACK_SLASH);
verify('\u00a1', VK_INVERTED_EXCLAMATION_MARK, "com.apple.keylayout.Spanish-ISO", VK_EQUALS);
// ordinary non-letter character with VK_ key codes
verify("!", VK_EXCLAMATION_MARK, "com.apple.keylayout.French-PC", VK_SLASH);
verify("\"", VK_QUOTEDBL, "com.apple.keylayout.French-PC", VK_3);
verify("#", VK_NUMBER_SIGN, "com.apple.keylayout.British-PC", VK_BACK_SLASH);
verify("$", VK_DOLLAR, "com.apple.keylayout.French-PC", VK_CLOSE_BRACKET);
verify("&", VK_AMPERSAND, "com.apple.keylayout.French-PC", VK_1);
verify("'", VK_QUOTE, "com.apple.keylayout.French-PC", VK_4);
verify("(", VK_LEFT_PARENTHESIS, "com.apple.keylayout.French-PC", VK_5);
verify(")", VK_RIGHT_PARENTHESIS, "com.apple.keylayout.French-PC", VK_MINUS);
verify("*", VK_ASTERISK, "com.apple.keylayout.French-PC", VK_BACK_SLASH);
verify("+", VK_PLUS, "com.apple.keylayout.German", VK_CLOSE_BRACKET);
verify(",", VK_COMMA, "com.apple.keylayout.ABC", VK_COMMA);
verify("-", VK_MINUS, "com.apple.keylayout.ABC", VK_MINUS);
verify(".", VK_PERIOD, "com.apple.keylayout.ABC", VK_PERIOD);
verify("/", VK_SLASH, "com.apple.keylayout.ABC", VK_SLASH);
verify(":", VK_COLON, "com.apple.keylayout.French-PC", VK_PERIOD);
verify(";", VK_SEMICOLON, "com.apple.keylayout.ABC", VK_SEMICOLON);
verify("<", VK_LESS, "com.apple.keylayout.French-PC", VK_BACK_QUOTE);
verify("=", VK_EQUALS, "com.apple.keylayout.ABC", VK_EQUALS);
verify(">", VK_GREATER, "com.apple.keylayout.Turkish", VK_CLOSE_BRACKET);
verify("@", VK_AT, "com.apple.keylayout.Norwegian", VK_BACK_SLASH);
verify("[", VK_OPEN_BRACKET, "com.apple.keylayout.ABC", VK_OPEN_BRACKET);
verify("\\", VK_BACK_SLASH, "com.apple.keylayout.ABC", VK_BACK_SLASH);
verify("]", VK_CLOSE_BRACKET, "com.apple.keylayout.ABC", VK_CLOSE_BRACKET);
verify("^", VK_CIRCUMFLEX, "com.apple.keylayout.ABC", ROBOT_KEYCODE_CIRCUMFLEX_JIS);
verify("_", VK_UNDERSCORE, "com.apple.keylayout.French-PC", VK_8);
verify("`", VK_BACK_QUOTE, "com.apple.keylayout.ABC", VK_BACK_QUOTE);
verify("{", VK_BRACELEFT, "com.apple.keylayout.LatinAmerican", VK_QUOTE);
verify("}", VK_BRACERIGHT, "com.apple.keylayout.LatinAmerican", VK_BACK_SLASH);
verify("\u00a1", VK_INVERTED_EXCLAMATION_MARK, "com.apple.keylayout.Spanish-ISO", VK_EQUALS);
// TODO: figure out which keyboard layout has VK_EURO_SIGN as a key on the primary layer
verify('/', VK_DIVIDE, "com.apple.keylayout.ABC", VK_DIVIDE, VK_SLASH);
verify('*', VK_MULTIPLY, "com.apple.keylayout.ABC", VK_MULTIPLY, VK_ASTERISK);
verify('+', VK_ADD, "com.apple.keylayout.ABC", VK_ADD, VK_PLUS);
verify('-', VK_SUBTRACT, "com.apple.keylayout.ABC", VK_SUBTRACT, VK_MINUS);
verify('\t', VK_TAB, "com.apple.keylayout.ABC", VK_TAB);
verify(' ', VK_SPACE, "com.apple.keylayout.ABC", VK_SPACE);
verify(" ", VK_SPACE, "com.apple.keylayout.ABC", VK_SPACE);
// Test numpad numbers
// control characters
verify("\t", VK_TAB, "com.apple.keylayout.ABC", VK_TAB);
verify("\n", VK_ENTER, "com.apple.keylayout.ABC", VK_ENTER);
verify("", VK_BACK_SPACE, "com.apple.keylayout.ABC", VK_BACK_SPACE);
verify("", VK_ESCAPE, "com.apple.keylayout.ABC", VK_ESCAPE);
// keypad
verify("/", VK_DIVIDE, "com.apple.keylayout.ABC", VK_DIVIDE, VK_SLASH, KEY_LOCATION_NUMPAD, 0);
verify("*", VK_MULTIPLY, "com.apple.keylayout.ABC", VK_MULTIPLY, VK_ASTERISK, KEY_LOCATION_NUMPAD, 0);
verify("+", VK_ADD, "com.apple.keylayout.ABC", VK_ADD, VK_PLUS, KEY_LOCATION_NUMPAD, 0);
verify("-", VK_SUBTRACT, "com.apple.keylayout.ABC", VK_SUBTRACT, VK_MINUS, KEY_LOCATION_NUMPAD, 0);
verify("", VK_CLEAR, "com.apple.keylayout.ABC", VK_CLEAR, VK_UNDEFINED, KEY_LOCATION_NUMPAD, 0);
verify("\n", VK_ENTER, "com.apple.keylayout.ABC", ROBOT_KEYCODE_NUMPAD_ENTER, VK_ENTER, KEY_LOCATION_NUMPAD, 0);
verify(",", VK_COMMA, "com.apple.keylayout.ABC", ROBOT_KEYCODE_NUMPAD_COMMA_JIS, VK_COMMA, KEY_LOCATION_NUMPAD, 0);
verify("=", VK_EQUALS, "com.apple.keylayout.ABC", ROBOT_KEYCODE_NUMPAD_EQUALS, VK_EQUALS, KEY_LOCATION_NUMPAD, 0);
verify(".", VK_DECIMAL, "com.apple.keylayout.ABC", VK_DECIMAL, VK_PERIOD, KEY_LOCATION_NUMPAD, 0);
// keypad numbers
for (int i = 0; i < 10; ++i) {
verify((char)('0' + i), VK_NUMPAD0 + i, "com.apple.keylayout.ABC", VK_NUMPAD0 + i, VK_0 + i);
verify(String.valueOf((char)('0' + i)), VK_NUMPAD0 + i, "com.apple.keylayout.ABC", VK_NUMPAD0 + i, VK_0 + i, KEY_LOCATION_NUMPAD, 0);
}
verify('\0', VK_F1, "com.apple.keylayout.ABC", VK_F1);
verify('\0', VK_F19, "com.apple.keylayout.ABC", VK_F19);
// Test ANSI/ISO keyboard weirdness
verify('\u00a7', 0x01000000+0x00A7, "com.apple.keylayout.ABC", VK_SECTION);
verify('\u00b2', 0x01000000+0x00B2, "com.apple.keylayout.French-PC", VK_SECTION);
verify('#', VK_NUMBER_SIGN, "com.apple.keylayout.CanadianFrench-PC", VK_SECTION);
verify('\u00ab', 0x01000000+0x00AB, "com.apple.keylayout.CanadianFrench-PC", VK_BACK_QUOTE_ISO);
verify('#', VK_NUMBER_SIGN, "com.apple.keylayout.CanadianFrench-PC", VK_BACK_QUOTE);
// function keys
verify("", VK_F1, "com.apple.keylayout.ABC", VK_F1);
verify("", VK_F19, "com.apple.keylayout.ABC", VK_F19);
// Test extended key codes that don't match the unicode char
verify('\u00e4', 0x01000000+0x00C4, "com.apple.keylayout.German", VK_QUOTE);
verify('\u00e5', 0x01000000+0x00C5, "com.apple.keylayout.Norwegian", VK_OPEN_BRACKET);
verify('\u00e6', 0x01000000+0x00C6, "com.apple.keylayout.Norwegian", VK_QUOTE);
verify('\u00e7', 0x01000000+0x00C7, "com.apple.keylayout.French-PC", VK_9);
verify('\u00f1', 0x01000000+0x00D1, "com.apple.keylayout.Spanish-ISO", VK_SEMICOLON);
verify('\u00f6', 0x01000000+0x00D6, "com.apple.keylayout.German", VK_SEMICOLON);
verify('\u00f8', 0x01000000+0x00D8, "com.apple.keylayout.Norwegian", VK_SEMICOLON);
// Test ANSI/ISO/JIS keyboard weirdness
verify("\u00a7", 0x01000000+0x00A7, "com.apple.keylayout.ABC", VK_SECTION);
verify("\u00b2", 0x01000000+0x00B2, "com.apple.keylayout.French-PC", VK_SECTION);
verify("#", VK_NUMBER_SIGN, "com.apple.keylayout.CanadianFrench-PC", VK_SECTION);
verify("\u00ab", 0x01000000+0x00AB, "com.apple.keylayout.CanadianFrench-PC", ROBOT_KEYCODE_BACK_QUOTE_ISO);
verify("#", VK_NUMBER_SIGN, "com.apple.keylayout.CanadianFrench-PC", VK_BACK_QUOTE);
verify("\u00a5", 0x01000000+0x00A5, "com.apple.keylayout.ABC", ROBOT_KEYCODE_YEN_SYMBOL_JIS);
// Test extended key codes that don"t match the unicode char
verify("\u00e4", 0x01000000+0x00C4, "com.apple.keylayout.German", VK_QUOTE);
verify("\u00e5", 0x01000000+0x00C5, "com.apple.keylayout.Norwegian", VK_OPEN_BRACKET);
verify("\u00e6", 0x01000000+0x00C6, "com.apple.keylayout.Norwegian", VK_QUOTE);
verify("\u00e7", 0x01000000+0x00C7, "com.apple.keylayout.French-PC", VK_9);
verify("\u00f1", 0x01000000+0x00D1, "com.apple.keylayout.Spanish-ISO", VK_SEMICOLON);
verify("\u00f6", 0x01000000+0x00D6, "com.apple.keylayout.German", VK_SEMICOLON);
verify("\u00f8", 0x01000000+0x00D8, "com.apple.keylayout.Norwegian", VK_SEMICOLON);
// test modifier keys
verify("", VK_ALT, "com.apple.keylayout.ABC", VK_ALT, VK_UNDEFINED, KEY_LOCATION_LEFT, ALT_DOWN_MASK);
verify("", VK_ALT, "com.apple.keylayout.ABC", VK_ALT_GRAPH, VK_UNDEFINED, KEY_LOCATION_RIGHT, ALT_DOWN_MASK);
verify("", VK_META, "com.apple.keylayout.ABC", VK_META, VK_UNDEFINED, KEY_LOCATION_LEFT, META_DOWN_MASK);
verify("", VK_META, "com.apple.keylayout.ABC", ROBOT_KEYCODE_RIGHT_COMMAND, VK_UNDEFINED, KEY_LOCATION_RIGHT, META_DOWN_MASK);
verify("", VK_CONTROL, "com.apple.keylayout.ABC", VK_CONTROL, VK_UNDEFINED, KEY_LOCATION_LEFT, CTRL_DOWN_MASK);
verify("", VK_CONTROL, "com.apple.keylayout.ABC", ROBOT_KEYCODE_RIGHT_CONTROL, VK_UNDEFINED, KEY_LOCATION_RIGHT, CTRL_DOWN_MASK);
verify("", VK_SHIFT, "com.apple.keylayout.ABC", VK_SHIFT, VK_UNDEFINED, KEY_LOCATION_LEFT, SHIFT_DOWN_MASK);
verify("", VK_SHIFT, "com.apple.keylayout.ABC", ROBOT_KEYCODE_RIGHT_SHIFT, VK_UNDEFINED, KEY_LOCATION_RIGHT, SHIFT_DOWN_MASK);
// duplicate key codes: Vietnamese ANSI_6 / ANSI_9
verify(" \u0309", 0x1000000+0x0309, "com.apple.keylayout.Vietnamese", VK_6);
verify(" \u0323", 0x1000000+0x0323, "com.apple.keylayout.Vietnamese", VK_9);
// duplicated key codes (dead): Apache ANSI_LeftBracket / ANSI_RightBracket
verify("\u02db", VK_DEAD_OGONEK, "com.apple.keylayout.Apache", VK_OPEN_BRACKET, 0x1000000+0x02DB, KEY_LOCATION_STANDARD, 0);
verify("\u02db\u0301", 0x1000000+0x0301, "com.apple.keylayout.Apache", VK_CLOSE_BRACKET);
}
private void verify(char ch, int vk, String layout, int key, int correctKeyCode) {
InputMethodTest.section("Key code test: " + vk + ", char: " + ch);
private void verify(String typed, int vk, String layout, int key, int charKeyCode, int location, int modifiers) {
char ch = (typed.length() == 1) ? typed.charAt(0) : 0;
InputMethodTest.section("Key code test: " + vk + ", layout: " + layout + ", char: " + String.format("U+%04X", (int)ch));
InputMethodTest.layout(layout);
InputMethodTest.type(key, 0);
InputMethodTest.expectKeyCode(vk);
InputMethodTest.expectText(typed);
if (ch != 0) {
InputMethodTest.expect(String.valueOf(ch));
InputMethodTest.expectTrue(getExtendedKeyCodeForChar(ch) == correctKeyCode, "getExtendedKeyCodeForChar");
InputMethodTest.expectTrue(getExtendedKeyCodeForChar(ch) == charKeyCode, "getExtendedKeyCodeForChar");
}
InputMethodTest.expectKeyPress(vk, location, modifiers, true);
}
private void verify(char ch, int vk, String layout, int key) {
verify(ch, vk, layout, key, vk);
private void verify(String typed, int vk, String layout, int key) {
verify(typed, vk, layout, key, vk, KEY_LOCATION_STANDARD, 0);
}
}

View File

@@ -24,7 +24,8 @@
/**
* @test
* @summary Regression test for JBR-5254: CapsLock and Chinese IMs don't work properly
* @run shell Runner.sh PinyinCapsLockTest
* @modules java.desktop/sun.lwawt.macosx
* @run main InputMethodTest PinyinCapsLockTest
* @requires (jdk.version.major >= 8 & os.family == "mac")
*/
@@ -74,7 +75,7 @@ public class PinyinCapsLockTest implements Runnable {
InputMethodTest.type(VK_A, 0);
InputMethodTest.type(VK_B, 0);
InputMethodTest.type(VK_C, 0);
InputMethodTest.expect(expectUppercase ? "ABC" : "abc");
InputMethodTest.expectText(expectUppercase ? "ABC" : "abc");
InputMethodTest.type(VK_ESCAPE, 0);
}
}

View File

@@ -24,7 +24,8 @@
/**
* @test
* @summary Regression test for IDEA-221385: Cannot input with half-width punctuation.
* @run shell Runner.sh --fullwidth PinyinFullWidthPunctuationTest
* @modules java.desktop/sun.lwawt.macosx
* @run main InputMethodTest PinyinFullWidthPunctuationTest
* @requires (jdk.version.major >= 8 & os.family == "mac")
*/
@@ -34,33 +35,34 @@ public class PinyinFullWidthPunctuationTest implements Runnable {
@Override
public void run() {
InputMethodTest.layout("com.apple.inputmethod.SCIM.ITABC");
InputMethodTest.setUseHalfWidthPunctuation(false);
InputMethodTest.section("comma");
InputMethodTest.type(VK_COMMA, 0);
InputMethodTest.expect("\uff0c");
InputMethodTest.expectText("\uff0c");
InputMethodTest.section("period");
InputMethodTest.type(VK_PERIOD, 0);
InputMethodTest.expect("\u3002");
InputMethodTest.expectText("\u3002");
InputMethodTest.section("question mark");
InputMethodTest.type(VK_SLASH, SHIFT_DOWN_MASK);
InputMethodTest.expect("\uff1f");
InputMethodTest.expectText("\uff1f");
InputMethodTest.section("semicolon");
InputMethodTest.type(VK_SEMICOLON, 0);
InputMethodTest.expect("\uff1b");
InputMethodTest.expectText("\uff1b");
InputMethodTest.section("colon");
InputMethodTest.type(VK_SEMICOLON, SHIFT_DOWN_MASK);
InputMethodTest.expect("\uff1a");
InputMethodTest.expectText("\uff1a");
InputMethodTest.section("left square bracket");
InputMethodTest.type(VK_OPEN_BRACKET, 0);
InputMethodTest.expect("\u3010");
InputMethodTest.expectText("\u3010");
InputMethodTest.section("right square bracket");
InputMethodTest.type(VK_CLOSE_BRACKET, 0);
InputMethodTest.expect("\u3011");
InputMethodTest.expectText("\u3011");
}
}

View File

@@ -24,7 +24,8 @@
/**
* @test
* @summary Regression test for IDEA-221385: Cannot input with half-width punctuation.
* @run shell Runner.sh --halfwidth PinyinHalfWidthPunctuationTest
* @modules java.desktop/sun.lwawt.macosx
* @run main InputMethodTest PinyinHalfWidthPunctuationTest
* @requires (jdk.version.major >= 8 & os.family == "mac")
*/
@@ -34,33 +35,34 @@ public class PinyinHalfWidthPunctuationTest implements Runnable {
@Override
public void run() {
InputMethodTest.layout("com.apple.inputmethod.SCIM.ITABC");
InputMethodTest.setUseHalfWidthPunctuation(true);
InputMethodTest.section("comma");
InputMethodTest.type(VK_COMMA, 0);
InputMethodTest.expect(",");
InputMethodTest.expectText(",");
InputMethodTest.section("period");
InputMethodTest.type(VK_PERIOD, 0);
InputMethodTest.expect(".");
InputMethodTest.expectText(".");
InputMethodTest.section("question mark");
InputMethodTest.type(VK_SLASH, SHIFT_DOWN_MASK);
InputMethodTest.expect("?");
InputMethodTest.expectText("?");
InputMethodTest.section("semicolon");
InputMethodTest.type(VK_SEMICOLON, 0);
InputMethodTest.expect(";");
InputMethodTest.expectText(";");
InputMethodTest.section("colon");
InputMethodTest.type(VK_SEMICOLON, SHIFT_DOWN_MASK);
InputMethodTest.expect(":");
InputMethodTest.expectText(":");
InputMethodTest.section("left square bracket");
InputMethodTest.type(VK_OPEN_BRACKET, 0);
InputMethodTest.expect("[");
InputMethodTest.expectText("[");
InputMethodTest.section("right square bracket");
InputMethodTest.type(VK_CLOSE_BRACKET, 0);
InputMethodTest.expect("]");
InputMethodTest.expectText("]");
}
}

View File

@@ -26,7 +26,8 @@
* @test
* @summary Regression test for IDEA-271898: Cannot enter Chinese full-corner single and double quotes (IDEA: macOS Intel version)
* @requires (jdk.version.major >= 8 & os.family == "mac")
* @run shell Runner.sh PinyinQuotesTest
* @modules java.desktop/sun.lwawt.macosx
* @run main InputMethodTest PinyinQuotesTest
*/
import static java.awt.event.KeyEvent.*;
@@ -47,7 +48,7 @@ public class PinyinQuotesTest implements Runnable {
InputMethodTest.type(VK_SPACE, 0);
InputMethodTest.type(VK_QUOTE, 0);
InputMethodTest.expect("\u2018 \u2019");
InputMethodTest.expectText("\u2018 \u2019");
}
private void doubleQuotes() {
@@ -58,6 +59,6 @@ public class PinyinQuotesTest implements Runnable {
InputMethodTest.type(VK_SPACE, 0);
InputMethodTest.type(VK_QUOTE, SHIFT_DOWN_MASK);
InputMethodTest.expect("\u201c \u201d");
InputMethodTest.expectText("\u201c \u201d");
}
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* 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.
*
* 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.
*/
/**
* @test
* @summary Regression test for JBR-5309: Minor keyboard inconsistencies on macOS
* @requires (jdk.version.major >= 8 & os.family == "mac")
* @modules java.desktop/sun.lwawt.macosx
* @run main InputMethodTest RomajiYenTest
* @run main InputMethodTest RomajiYenBackslashTest
*/
import static java.awt.event.KeyEvent.*;
public class RomajiYenTest implements Runnable {
private final boolean isBackslash;
static private final int ROBOT_KEYCODE_YEN_SYMBOL_JIS = 0x200025D;
static private final String YEN_SYMBOL = "\u00a5";
static private final String RIGHT_POINTING_DOUBLE_ANGLE_QUOTATION_MARK = "\u00bb";
public RomajiYenTest(boolean isBackslash) {
this.isBackslash = isBackslash;
}
@Override
public void run() {
InputMethodTest.setUseBackslashInsteadOfYen(isBackslash);
InputMethodTest.setRomajiLayout("com.apple.keylayout.ABC");
InputMethodTest.layout("com.apple.inputmethod.Kotoeri.RomajiTyping.Roman");
backslash();
optBackslash();
shiftBackslash();
optShiftBackslash();
optY();
yen();
optYen();
shiftYen();
optShiftYen();
}
private void backslash() {
InputMethodTest.section("Backslash");
InputMethodTest.type(VK_BACK_SLASH, 0);
InputMethodTest.expectText(isBackslash ? "\\" : YEN_SYMBOL);
}
private void optBackslash() {
InputMethodTest.section("Opt+Backslash");
InputMethodTest.type(VK_BACK_SLASH, ALT_DOWN_MASK);
InputMethodTest.expectText(isBackslash ? YEN_SYMBOL : "\\");
}
private void shiftBackslash() {
InputMethodTest.section("Shift+Backslash");
InputMethodTest.type(VK_BACK_SLASH, SHIFT_DOWN_MASK);
InputMethodTest.expectText("|");
}
private void optShiftBackslash() {
InputMethodTest.section("Opt+Shift+Backslash");
InputMethodTest.type(VK_BACK_SLASH, SHIFT_DOWN_MASK | ALT_DOWN_MASK);
InputMethodTest.expectText(RIGHT_POINTING_DOUBLE_ANGLE_QUOTATION_MARK);
}
private void optY() {
InputMethodTest.section("Opt+Y");
InputMethodTest.type(VK_Y, ALT_DOWN_MASK);
InputMethodTest.expectText(isBackslash ? "\\" : YEN_SYMBOL);
}
private void yen() {
InputMethodTest.section("Yen");
InputMethodTest.type(ROBOT_KEYCODE_YEN_SYMBOL_JIS, 0);
InputMethodTest.expectText(isBackslash ? "\\" : YEN_SYMBOL);
}
private void optYen() {
InputMethodTest.section("Opt+Yen");
InputMethodTest.type(ROBOT_KEYCODE_YEN_SYMBOL_JIS, ALT_DOWN_MASK);
InputMethodTest.expectText(isBackslash ? YEN_SYMBOL : "\\");
}
private void shiftYen() {
InputMethodTest.section("Shift+Yen");
InputMethodTest.type(ROBOT_KEYCODE_YEN_SYMBOL_JIS, SHIFT_DOWN_MASK);
InputMethodTest.expectText("|");
}
private void optShiftYen() {
InputMethodTest.section("Opt+Shift+Yen");
InputMethodTest.type(ROBOT_KEYCODE_YEN_SYMBOL_JIS, SHIFT_DOWN_MASK | ALT_DOWN_MASK);
InputMethodTest.expectText("|");
}
}

View File

@@ -1,75 +0,0 @@
#!/bin/sh
# Copyright 2000-2023 JetBrains s.r.o.
# 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.
#
# 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.
#
[ -z "${TESTSRC}" ] && echo "TESTSRC not set" && exit 1
[ -z "${TESTCLASSES}" ] && echo "TESTCLASSES not set" && exit 1
[ -z "${TESTJAVA}" ] && echo "TESTJAVA not set" && exit 1
cd "${TESTSRC}" || exit 1
"${TESTJAVA}/bin/javac" -d "${TESTCLASSES}" --add-modules java.desktop --add-exports java.desktop/sun.lwawt.macosx=ALL-UNNAMED InputMethodTest.java
half_width_override=
while :; do
case $1 in
--halfwidth)
half_width_override=1
;;
--fullwidth)
half_width_override=0
;;
--)
shift
break
;;
*)
break
;;
esac
shift
done
if [ -n "$half_width_override" ]; then
half_width_old_value=$(defaults read com.apple.inputmethod.CoreChineseEngineFramework usesHalfwidthPunctuation)
defaults write com.apple.inputmethod.CoreChineseEngineFramework usesHalfwidthPunctuation "$half_width_override"
fi
"${TESTJAVA}/bin/java" -cp "${TESTCLASSES}" --add-modules java.desktop --add-exports java.desktop/sun.lwawt.macosx=ALL-UNNAMED InputMethodTest "$1"
exit_code=$?
if [ -n "$half_width_override" ]; then
defaults write com.apple.inputmethod.CoreChineseEngineFramework usesHalfwidthPunctuation "$half_width_old_value"
fi
case $exit_code in
0) echo "PASSED"
;;
*) echo "FAILED"
exit 1
;;
esac
exit 0

View File

@@ -0,0 +1,83 @@
/*
* Copyright (c) 2000-2023 JetBrains s.r.o.
* 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.
*
* 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.
*/
/**
* @test
* @summary Regression test for JBR-5558 macOS keyboard rewrite 2
* @requires (jdk.version.major >= 8 & os.family == "mac")
* @modules java.desktop/sun.lwawt.macosx
* @run main InputMethodTest UnderlyingLayoutQWERTYTest
* @run main InputMethodTest UnderlyingLayoutQWERTZTest
*/
import static java.awt.event.KeyEvent.*;
public class UnderlyingLayoutTest implements Runnable {
private final boolean isQwertz;
public UnderlyingLayoutTest(boolean isQwertz) {
this.isQwertz = isQwertz;
}
@Override
public void run() {
if (isQwertz) {
InputMethodTest.setRomajiLayout("com.apple.keylayout.ABC-QWERTZ");
qwertz("com.apple.keylayout.German");
qwertz("com.apple.keylayout.ABC-QWERTZ");
qwertz("com.apple.keylayout.Polish");
qwertz("com.apple.inputmethod.Kotoeri.RomajiTyping.Roman");
qwertz("com.apple.inputmethod.Kotoeri.RomajiTyping.Japanese");
qwertz("com.apple.inputmethod.Kotoeri.KanaTyping.Roman");
// qwerty("com.apple.inputmethod.Kotoeri.KanaTyping.Japanese");
} else {
InputMethodTest.setRomajiLayout("com.apple.keylayout.ABC");
qwerty("com.apple.keylayout.US");
qwerty("com.apple.keylayout.ABC");
qwerty("com.apple.keylayout.Russian");
qwerty("com.apple.inputmethod.Kotoeri.RomajiTyping.Roman");
qwerty("com.apple.inputmethod.Kotoeri.RomajiTyping.Japanese");
qwerty("com.apple.inputmethod.Kotoeri.KanaTyping.Roman");
qwerty("com.apple.inputmethod.Kotoeri.KanaTyping.Japanese");
}
}
private void qwerty(String layout) {
testImpl(layout, VK_Y);
}
private void qwertz(String layout) {
testImpl(layout, VK_Z);
}
private void testImpl(String layout, int vkY) {
InputMethodTest.section("Cmd " + layout);
InputMethodTest.layout(layout);
InputMethodTest.type(VK_Y, META_DOWN_MASK);
InputMethodTest.expectKeyPress(vkY, KEY_LOCATION_STANDARD, META_DOWN_MASK, false);
InputMethodTest.section("Ctrl " + layout);
InputMethodTest.type(VK_Y, CTRL_DOWN_MASK);
InputMethodTest.expectKeyPress(vkY, KEY_LOCATION_STANDARD, CTRL_DOWN_MASK, false);
}
}

View File

@@ -107,8 +107,8 @@ public class Key {
put((char) 0x02DA, VK_DEAD_ABOVERING);
put((char) 0x00B4, VK_DEAD_ACUTE); // ACUTE ACCENT
put((char) 0x0384, VK_DEAD_ACUTE); // GREEK TONOS
// TODO No corresponding VK_DEAD constant for this key as it may add either acute or cedilla to the next key
//put((char) 0x0027 /* ' */, VK_DEAD_QUOTE); // APOSTROPHE, QUOTE
// This key may either be an acute accent or a cedilla. On Windows, it is reported as DEAD_ACUTE, let's do the same.
put((char) 0x0027 /* ' */, VK_DEAD_ACUTE); // APOSTROPHE, QUOTE
put((char) 0x02D8, VK_DEAD_BREVE);
put((char) 0x02C7, VK_DEAD_CARON);
put((char) 0x00B8, VK_DEAD_CEDILLA); // CEDILLA