JBR-5254: Fix Caps Lock not working properly on certain Chinese IMs

with fix for JBR-5300 Change source code and test files to use GPL license

(cherry picked from commit be64a4f3d0)
This commit is contained in:
Nikita Tsarev
2023-02-07 10:58:23 +01:00
committed by jbrbot
parent 69efdd9b59
commit 06f217bfa2
6 changed files with 162 additions and 6 deletions

View File

@@ -410,6 +410,7 @@ ifeq ($(call isTargetOs, macosx), true)
-framework AudioToolbox \
-framework Carbon \
-framework Cocoa \
-framework IOKit \
-framework ExceptionHandling \
-framework JavaRuntimeSupport \
-framework Metal \

View File

@@ -36,6 +36,8 @@ import java.awt.event.InputEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.KeyEvent;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Translates NSEvents/NPCocoaEvents into AWT events.
@@ -55,6 +57,7 @@ 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) {
@@ -235,14 +238,23 @@ final class CPlatformResponder {
}
}
// If Pinyin Simplified input method is selected, CAPS_LOCK key is supposed to switch
// 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.
LWCToolkit lwcToolkit = (LWCToolkit)Toolkit.getDefaultToolkit();
// 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
if (testChar != KeyEvent.CHAR_UNDEFINED &&
lwcToolkit.getLockingKeyState(KeyEvent.VK_CAPS_LOCK) &&
Locale.SIMPLIFIED_CHINESE.equals(lwcToolkit.getDefaultKeyboardLocale())) {
Toolkit.getDefaultToolkit().getLockingKeyState(KeyEvent.VK_CAPS_LOCK) &&
layoutsNeedingCapsLockFix.matcher(LWCToolkit.getKeyboardLayoutId()).matches()) {
testChar = testCharIgnoringModifiers;
}

View File

@@ -557,6 +557,8 @@ public final class LWCToolkit extends LWToolkit {
private native boolean isCapsLockOn();
private static native boolean setCapsLockState(boolean on);
/*
* NOTE: Among the keys this method is supposed to check,
* only Caps Lock works as a true locking key with OS X.
@@ -583,6 +585,25 @@ public final class LWCToolkit extends LWToolkit {
}
}
@Override
public void setLockingKeyState(int keyCode, boolean on) throws UnsupportedOperationException {
switch (keyCode) {
case KeyEvent.VK_NUM_LOCK:
case KeyEvent.VK_SCROLL_LOCK:
case KeyEvent.VK_KANA_LOCK:
throw new UnsupportedOperationException("Toolkit.setLockingKeyState");
case KeyEvent.VK_CAPS_LOCK:
if (!setCapsLockState(on)) {
throw new RuntimeException("failed to set caps lock state");
}
break;
default:
throw new IllegalArgumentException("invalid key for Toolkit.setLockingKeyState");
}
}
//Is it allowed to generate events assigned to extra mouse buttons.
//Set to true by default.
private static boolean areExtraMouseButtonsEnabled = true;

View File

@@ -45,6 +45,9 @@
#import <JavaRuntimeSupport/JavaRuntimeSupport.h>
#import <Carbon/Carbon.h>
#include <IOKit/hidsystem/IOHIDShared.h>
#include <IOKit/hidsystem/IOHIDParameter.h>
// SCROLL PHASE STATE
#define SCROLL_PHASE_UNSUPPORTED 1
#define SCROLL_PHASE_BEGAN 2
@@ -1034,4 +1037,34 @@ JNICALL Java_sun_lwawt_macosx_LWCToolkit_disableKeyboardLayoutNative(JNIEnv *env
}];
JNI_COCOA_EXIT(env);
return status == noErr;
}
/*
* Class: sun_lwawt_macosx_LWCToolkit
* Method: setCapsLockState
* Signature: (Z)Z
*/
JNIEXPORT jboolean JNICALL
JNICALL Java_sun_lwawt_macosx_LWCToolkit_setCapsLockState(JNIEnv *env, jclass cls, jboolean on) {
__block kern_return_t err = KERN_SUCCESS;
JNI_COCOA_ENTER(env);
[ThreadUtilities performOnMainThreadWaiting:YES block:^() {
io_service_t ioHIDService = IOServiceGetMatchingService(kIOMasterPortDefault,
IOServiceMatching(kIOHIDSystemClass));
if (!ioHIDService) {
err = KERN_FAILURE;
return;
}
io_connect_t ioHIDConnect;
err = IOServiceOpen(ioHIDService, mach_task_self(), kIOHIDParamConnectType, &ioHIDConnect);
if (err == KERN_SUCCESS) {
err = IOHIDSetModifierLockState(ioHIDConnect, kIOHIDCapsLockState, on);
IOServiceClose(ioHIDConnect);
}
IOObjectRelease(ioHIDService);
}];
JNI_COCOA_EXIT(env);
return err == KERN_SUCCESS;
}

View File

@@ -47,9 +47,10 @@ public class InputMethodTest {
private enum TestCases {
DeadKeysTest (new DeadKeysTest()),
KeyCodesTest (new KeyCodesTest()),
PinyinQuotesTest (new PinyinQuotesTest()),
PinyinCapsLockTest (new PinyinCapsLockTest()),
PinyinFullWidthPunctuationTest (new PinyinFullWidthPunctuationTest()),
PinyinHalfWidthPunctuationTest (new PinyinHalfWidthPunctuationTest())
PinyinHalfWidthPunctuationTest (new PinyinHalfWidthPunctuationTest()),
PinyinQuotesTest (new PinyinQuotesTest())
;
private Runnable test;
@@ -70,6 +71,7 @@ public class InputMethodTest {
runTest(arg);
}
} finally {
setCapsLockState(false);
LWCToolkit.switchKeyboardLayout(initialLayout);
for (String layoutId : addedLayouts) {
try {
@@ -121,6 +123,7 @@ public class InputMethodTest {
private static void runTest(String name) {
currentTest = name;
setCapsLockState(false);
try {
TestCases.valueOf(name).run();
} catch (Exception e) {
@@ -151,6 +154,7 @@ public class InputMethodTest {
}
LWCToolkit.switchKeyboardLayout(name);
robot.delay(250);
}
public static void type(int key, int modifiers) {
@@ -185,6 +189,11 @@ public class InputMethodTest {
}
}
public static void setCapsLockState(boolean desiredState) {
LWCToolkit.getDefaultToolkit().setLockingKeyState(KeyEvent.VK_CAPS_LOCK, desiredState);
robot.delay(250);
}
public static void expect(String expectedValue) {
var actualValue = textArea.getText();
if (actualValue.equals(expectedValue)) {

View File

@@ -0,0 +1,80 @@
/*
* 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-5254: CapsLock and Chinese IMs don't work properly
* @run shell Runner.sh PinyinCapsLockTest
* @requires (jdk.version.major >= 8 & os.family == "mac")
*/
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static java.awt.event.KeyEvent.*;
public class PinyinCapsLockTest implements Runnable {
private static final List<String> expectLowercase = new ArrayList<>(Arrays.asList(
"com.apple.inputmethod.SCIM.ITABC",
"com.apple.inputmethod.SCIM.Shuangpin",
"com.apple.inputmethod.SCIM.WBH",
"com.apple.inputmethod.TYIM.Cangjie",
"com.apple.inputmethod.TYIM.Sucheng",
"com.apple.inputmethod.TYIM.Stroke",
"com.apple.inputmethod.TCIM.Zhuyin",
"com.apple.inputmethod.TCIM.Cangjie",
"com.apple.inputmethod.TCIM.ZhuyinEten",
"com.apple.inputmethod.TCIM.Jianyi",
"com.apple.inputmethod.TCIM.Pinyin",
"com.apple.inputmethod.TCIM.Shuangpin",
"com.apple.inputmethod.TCIM.WBH"
));
// Wubi (Simplified) produces uppercase characters even in native apps.
private static final List<String> expectUppercase = new ArrayList<>(Arrays.asList(
"com.apple.inputmethod.SCIM.WBX"
));
@Override
public void run() {
for (String layout : expectLowercase) {
testLatinTyping(layout, false);
}
for (String layout : expectUppercase) {
testLatinTyping(layout, true);
}
}
private void testLatinTyping(String layout, boolean expectUppercase) {
InputMethodTest.section(layout);
InputMethodTest.layout(layout);
InputMethodTest.setCapsLockState(true);
InputMethodTest.type(VK_A, 0);
InputMethodTest.type(VK_B, 0);
InputMethodTest.type(VK_C, 0);
InputMethodTest.expect(expectUppercase ? "ABC" : "abc");
InputMethodTest.type(VK_ESCAPE, 0);
}
}