Skip to content

Commit

Permalink
macOS national keyboard support
Browse files Browse the repository at this point in the history
  • Loading branch information
Denis Fokin authored and alexey.ushakov@jetbrains.com committed Nov 22, 2022
1 parent 6ac5b5a commit 21bffd0
Show file tree
Hide file tree
Showing 12 changed files with 333 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ public void handleScrollEvent(double pluginX, double pluginY, int modifierFlags,
public void handleKeyEvent(int eventType, int modifierFlags, String characters,
String charsIgnoringMods, boolean isRepeat, short keyCode,
boolean needsKeyTyped) {
responder.handleKeyEvent(eventType, modifierFlags, characters, charsIgnoringMods,
responder.handleKeyEvent(eventType, modifierFlags, characters,
charsIgnoringMods, /*charsIgnoringModifiersAndShift*/ null,
keyCode, needsKeyTyped, isRepeat);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
package sun.lwawt.macosx;

import sun.awt.SunToolkit;
import sun.awt.event.KeyEventProcessing;
import sun.lwawt.LWWindowPeer;
import sun.lwawt.PlatformEventNotifier;

Expand All @@ -34,6 +35,7 @@
import java.awt.event.InputEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.KeyEvent;
import java.util.Arrays;
import java.util.Locale;

/**
Expand Down Expand Up @@ -167,21 +169,39 @@ private void dispatchScrollEvent(final int x, final int y, final int absX,
-roundDelta, -delta, null);
}

private static final String [] cyrillicKeyboardLayouts = new String [] {
"com.apple.keylayout.Russian",
"com.apple.keylayout.RussianWin",
"com.apple.keylayout.Russian-Phonetic",
"com.apple.keylayout.Byelorussian",
"com.apple.keylayout.Ukrainian",
"com.apple.keylayout.UkrainianWin",
"com.apple.keylayout.Bulgarian",
"com.apple.keylayout.Serbian"
};

private static boolean isCyrillicKeyboardLayout() {
return Arrays.stream(cyrillicKeyboardLayouts).anyMatch(l -> l.equals(LWCToolkit.getKeyboardLayoutId()));
}

/**
* Handles key events.
*/
void handleKeyEvent(int eventType, int modifierFlags, String chars, String charsIgnoringModifiers,
void handleKeyEvent(int eventType, int modifierFlags, String chars,
String charsIgnoringModifiers, String charsIgnoringModifiersAndShift,
short keyCode, boolean needsKeyTyped, boolean needsKeyReleased) {
boolean isFlagsChangedEvent =
isNpapiCallback ? (eventType == CocoaConstants.NPCocoaEventFlagsChanged) :
(eventType == CocoaConstants.NSFlagsChanged);
isNpapiCallback ? (eventType == CocoaConstants.NPCocoaEventFlagsChanged) :
(eventType == CocoaConstants.NSFlagsChanged);

int jeventType = KeyEvent.KEY_PRESSED;
int jkeyCode = KeyEvent.VK_UNDEFINED;
int jkeyLocation = KeyEvent.KEY_LOCATION_UNKNOWN;
boolean postsTyped = false;
boolean spaceKeyTyped = false;

int jmodifiers = NSEvent.nsToJavaModifiers(modifierFlags);

char testChar = KeyEvent.CHAR_UNDEFINED;
boolean isDeadChar = (chars!= null && chars.length() == 0);

Expand All @@ -204,10 +224,19 @@ void handleKeyEvent(int eventType, int modifierFlags, String chars, String chars
}
}

// Workaround for JBR-2981
int metaAltCtrlMods = KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK;
boolean metaAltCtrlAreNotPressed = (jmodifiers & metaAltCtrlMods) == 0;
boolean useShiftedCharacter = ((jmodifiers & KeyEvent.SHIFT_DOWN_MASK) == KeyEvent.SHIFT_DOWN_MASK) && metaAltCtrlAreNotPressed;

char testCharIgnoringModifiers = charsIgnoringModifiers != null && charsIgnoringModifiers.length() > 0 ?
charsIgnoringModifiers.charAt(0) : KeyEvent.CHAR_UNDEFINED;
if (!useShiftedCharacter && charsIgnoringModifiersAndShift != null && charsIgnoringModifiersAndShift.length() > 0) {
testCharIgnoringModifiers = charsIgnoringModifiersAndShift.charAt(0);
}

int[] in = new int[] {testCharIgnoringModifiers, isDeadChar ? 1 : 0, modifierFlags, keyCode};
int useNationalLayouts = (KeyEventProcessing.useNationalLayouts && !isCyrillicKeyboardLayout()) ? 1 : 0;
int[] in = new int[] {testCharIgnoringModifiers, isDeadChar ? 1 : 0, modifierFlags, keyCode, useNationalLayouts};
int[] out = new int[3]; // [jkeyCode, jkeyLocation, deadChar]

postsTyped = NSEvent.nsToJavaKeyInfo(in, out);
Expand All @@ -217,7 +246,8 @@ void handleKeyEvent(int eventType, int modifierFlags, String chars, String chars

if(isDeadChar){
testChar = (char) out[2];
if(testChar == 0){
jkeyCode = out[0];
if(testChar == 0 && jkeyCode == KeyEvent.VK_UNDEFINED){
return;
}
}
Expand Down Expand Up @@ -249,7 +279,6 @@ void handleKeyEvent(int eventType, int modifierFlags, String chars, String chars
postsTyped = false;
}

int jmodifiers = NSEvent.nsToJavaModifiers(modifierFlags);
long when = System.currentTimeMillis();

if (jeventType == KeyEvent.KEY_PRESSED) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,8 @@ private void deliverMouseEvent(final NSEvent event) {

private void deliverKeyEvent(NSEvent event) {
responder.handleKeyEvent(event.getType(), event.getModifierFlags(), event.getCharacters(),
event.getCharactersIgnoringModifiers(), event.getKeyCode(), true, false);
event.getCharactersIgnoringModifiers(), event.getCharactersIgnoringModifiersAndShift(),
event.getKeyCode(), true, false);
}

/**
Expand Down
15 changes: 15 additions & 0 deletions src/java.desktop/macosx/classes/sun/lwawt/macosx/LWCToolkit.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ public final class LWCToolkit extends LWToolkit {

private static native void initIDs();
private static native void initAppkit(ThreadGroup appKitThreadGroup, boolean headless);
private static native void switchKeyboardLayoutNative(String layoutName);

static native String getKeyboardLayoutNativeId();

private static CInputMethodDescriptor sInputMethodDescriptor;

static {
Expand Down Expand Up @@ -1069,6 +1073,17 @@ private static boolean isValidPath(String path) {
!path.endsWith(".");
}

public static void switchKeyboardLayout (String layoutName) {
if (layoutName == null || layoutName.isEmpty()) {
throw new RuntimeException("A valid layout ID is expected. Found: " + layoutName);
}
switchKeyboardLayoutNative(layoutName);
}

public static String getKeyboardLayoutId () {
return getKeyboardLayoutNativeId();
}

@Override
protected PlatformWindow getPlatformWindowUnderMouse() {
return CPlatformWindow.nativeGetTopmostPlatformWindowUnderMouse();
Expand Down
34 changes: 32 additions & 2 deletions src/java.desktop/macosx/classes/sun/lwawt/macosx/NSEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ final class NSEvent {
static final int SCROLL_PHASE_CONTINUED = 3;
static final int SCROLL_PHASE_MOMENTUM_BEGAN = 4;
static final int SCROLL_PHASE_ENDED = 5;
private boolean hasDeadKey;
private int deadKeyCode;

private int type;
private int modifierFlags;
Expand All @@ -57,14 +59,32 @@ final class NSEvent {
private short keyCode;
private String characters;
private String charactersIgnoringModifiers;
private String oldCharacters;
private String oldCharactersIgnoringModifiers;
private String charactersIgnoringModifiersAndShift;

public boolean isHasDeadKey() {
return hasDeadKey;
}

public int getDeadKeyCode() {
return deadKeyCode;
}

// Called from native
NSEvent(int type, int modifierFlags, short keyCode, String characters, String charactersIgnoringModifiers) {
NSEvent(int type, int modifierFlags, short keyCode, String characters, String charactersIgnoringModifiers,
String charactersIgnoringModifiersAndShift, boolean hasDeadKey, int deadKeyCode,
String oldCharacters, String oldCharactersIgnoringModifiers) {
this.type = type;
this.modifierFlags = modifierFlags;
this.keyCode = keyCode;
this.characters = characters;
this.charactersIgnoringModifiers = charactersIgnoringModifiers;
this.charactersIgnoringModifiersAndShift = charactersIgnoringModifiersAndShift;
this.hasDeadKey = hasDeadKey;
this.deadKeyCode = deadKeyCode;
this.oldCharacters = oldCharacters;
this.oldCharactersIgnoringModifiers = oldCharactersIgnoringModifiers;
}

// Called from native
Expand Down Expand Up @@ -136,16 +156,26 @@ String getCharactersIgnoringModifiers() {
return charactersIgnoringModifiers;
}

String getCharactersIgnoringModifiersAndShift() {return charactersIgnoringModifiersAndShift;}

String getCharacters() {
return characters;
}

String getOldCharactersIgnoringModifiers() {
return oldCharactersIgnoringModifiers;
}

String getOldCharacters() {
return oldCharacters;
}

@Override
public String toString() {
return "NSEvent[" + getType() + " ," + getModifierFlags() + " ,"
+ getClickCount() + " ," + getButtonNumber() + " ," + getX() + " ,"
+ getY() + " ," + getAbsX() + " ," + getAbsY()+ " ," + getKeyCode() + " ,"
+ getCharacters() + " ," + getCharactersIgnoringModifiers() + "]";
+ getCharacters() + " ," + getCharactersIgnoringModifiers() + " ," + getCharactersIgnoringModifiersAndShift() + "]";
}

/*
Expand Down
6 changes: 6 additions & 0 deletions src/java.desktop/macosx/native/libawt_lwawt/awt/AWTEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
#ifndef __AWTEVENT_H
#define __AWTEVENT_H

#import "LWCToolkit.h"

@interface NSEvent (NSEventExtension)
- (NSString *)charactersIgnoringModifiersAndShift;
@end

jlong UTC(NSEvent *event);
void DeliverJavaKeyEvent(JNIEnv *env, NSEvent *event, jobject peer);
void DeliverJavaMouseEvent(JNIEnv *env, NSEvent *event, jobject peer);
Expand Down
104 changes: 101 additions & 3 deletions src/java.desktop/macosx/native/libawt_lwawt/awt/AWTEvent.m
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@
static const struct CharToVKEntry charToDeadVKTable[] = {
{0x0060, java_awt_event_KeyEvent_VK_DEAD_GRAVE},
{0x00B4, java_awt_event_KeyEvent_VK_DEAD_ACUTE},
{0xFFFF, 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},
{0x007E, java_awt_event_KeyEvent_VK_DEAD_TILDE},
Expand Down Expand Up @@ -421,13 +422,74 @@ static unichar NsGetDeadKeyChar(unsigned short keyCode)
return 0;
}

static const int UNICODE_OFFSET = 0x01000000;

static BOOL isLatinUnicode(unichar ch) {
// Latin-1 Supplement 0x0080 - 0x00FF
// Latin Extended-A 0x0100 - 0x017F
// Latin Extended-B 0x0180 - 0x024F
return 0x0080 <= ch && ch <= 0x024F;
}

static NSDictionary* getUnicharToVkCodeDictionary() {

static NSDictionary* unicharToVkCodeDictionary = nil;
static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{
unicharToVkCodeDictionary =
[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_BACK_QUOTE], @"`",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_1], @"1",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_2], @"2",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_3], @"3",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_4], @"4",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_5], @"5",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_6], @"6",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_7], @"7",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_8], @"8",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_9], @"9",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_0], @"0",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_EQUALS], @"=",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_MINUS], @"-",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_CLOSE_BRACKET], @"]",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_OPEN_BRACKET], @"[",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_QUOTE], @"\'",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_SEMICOLON], @";",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_COLON], @":",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_BACK_SLASH], @"\\",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_COMMA], @",",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_SLASH], @"/",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_PERIOD], @".",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_ASTERISK], @"*",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_PLUS], @"+",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_COMMA], @",",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_NUMBER_SIGN], @"#",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_DOLLAR], @"$",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_CIRCUMFLEX], @"^",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_LEFT_PARENTHESIS], @"(",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_RIGHT_PARENTHESIS], @")",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_UNDERSCORE], @"_",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_AMPERSAND], @"&",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_QUOTEDBL], @"\"",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_EXCLAMATION_MARK], @"!",
[NSNumber numberWithInt:java_awt_event_KeyEvent_VK_LESS], @"<",
nil
];
// This is ok to retain a singleton object
[unicharToVkCodeDictionary retain];
});

return unicharToVkCodeDictionary;
}

/*
* This is the function that uses the table above to take incoming
* NSEvent keyCodes and translate to the Java virtual key code.
*/
static void
NsCharToJavaVirtualKeyCode(unichar ch, BOOL isDeadChar,
NSUInteger flags, unsigned short key,
NSUInteger flags, unsigned short key, BOOL useNationalLayouts,
jint *keyCode, jint *keyLocation, BOOL *postsTyped,
unichar *deadChar)
{
Expand Down Expand Up @@ -494,6 +556,41 @@ static unichar NsGetDeadKeyChar(unsigned short keyCode)
}
}

if (useNationalLayouts) {
if (keyTable[key].javaKeyLocation == java_awt_event_KeyEvent_KEY_LOCATION_NUMPAD) {
*postsTyped = keyTable[key].postsTyped;
*keyCode = keyTable[key].javaKeyCode;
*keyLocation = keyTable[key].javaKeyLocation;
return;
}

NSDictionary* unicharToVkCodeDictionary = getUnicharToVkCodeDictionary();
if ([[NSCharacterSet punctuationCharacterSet] characterIsMember:ch] ||
[[NSCharacterSet symbolCharacterSet] characterIsMember:ch])
{
// punctuationCharacterSet and symbolCharacterSet are too big
// to store them all in UnicharToVkCodeDictionary
int tmpKeyCode = [[unicharToVkCodeDictionary objectForKey:[NSString stringWithFormat:@"%C",ch]] intValue];
if (tmpKeyCode != 0) {
*keyCode = tmpKeyCode;
// we cannot find key location from a char, so let's use key code
*postsTyped = YES;
*keyLocation = keyTable[key].javaKeyLocation;
return;
}
}

// Latin-1 suplement & Latin Extended A & B
if (isLatinUnicode(ch)) {
*keyCode = ((int) ch) + UNICODE_OFFSET;
// we cannot find key location from a char, so let's use key code
*postsTyped = YES;
*keyLocation = keyTable[key].javaKeyLocation;
return;
}
}


if (key < size) {
*postsTyped = keyTable[key].postsTyped;
*keyCode = keyTable[key].javaKeyCode;
Expand Down Expand Up @@ -688,19 +785,20 @@ jlong UTC(NSEvent *event) {
jint *data = (*env)->GetIntArrayElements(env, inData, &copy);
CHECK_NULL_RETURN(data, postsTyped);

// in = [testChar, testDeadChar, modifierFlags, keyCode]
// 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] == 1);

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,
&jkeyCode, &jkeyLocation, &postsTyped,
useNationalLayouts, &jkeyCode, &jkeyLocation, &postsTyped,
(unichar *) &testDeadChar);

// out = [jkeyCode, jkeyLocation, deadChar];
Expand Down
Loading

0 comments on commit 21bffd0

Please sign in to comment.