mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2025-12-06 09:29:38 +01:00
JBR-5558: macOS keyboard rewrite 2
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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, ©);
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
jobject fInputMethodLOCKABLE;
|
||||
BOOL fKeyEventsNeeded;
|
||||
BOOL fProcessingKeystroke;
|
||||
BOOL fComplexInputNeeded;
|
||||
NSString* actualCharacters;
|
||||
|
||||
BOOL fEnablePressAndHold;
|
||||
BOOL fInPressAndHold;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
114
test/jdk/jb/sun/awt/macos/InputMethodTest/RomajiYenTest.java
Normal file
114
test/jdk/jb/sun/awt/macos/InputMethodTest/RomajiYenTest.java
Normal 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("|");
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user