-
Bug
-
Resolution: Unresolved
-
P3
-
8
-
generic
-
windows
ADDITIONAL SYSTEM INFORMATION :
Windows 10, Java 8 to 20
A DESCRIPTION OF THE PROBLEM :
Entering characters using Alt-NumPad often produce garbage characters.
In a normal Win32 message loop Windows' TranslateMessage() is responsible for
translating WM_KEYDOWN/WK_SYSKEYDOWN and WM_KEYUP/WM_SYSKEYUP messages to
WM_CHAR messages. It handles composite keys, ligatures, dead keys and
Alt-NumPad sequences. For Alt-NumPad entry, the kernel-mode function
win32kbase.sys!xxxInternalToUnicode() has an internal process-wide NumPad
state to keep track of consecutive keyboard messages between pressing and
releasing the Alt key.
ToUnicodeEx() can be used to manually translate virtual-key codes to
characters. It also ultimately calls xxxInternalToUnicode() and changes the
state of the kernel-mode keyboard buffer. Official docs also warn that using
it in conjunction with TranslateMessage() can cause undesired side effects.
AWT's message loop uses ToUnicodeEx() in keydown and keyup message handlers.
These uses are benign since xxxInternalToUnicode() compensates by ignoring
duplicate consecutive key messages. The message loop then sends KeyEvents to
the AWT event queue thread, which in turn (in AwtComponent::_NativeHandleEvent
via AwtComponent::WindowsKeyToJavaChar) invokes ToUnicodeEx() again. This time
(depending on thread scheduling) it corrupts the NumPad state.
Not sure why WindowsKeyToJavaChar() is called here again, since the KeyEvent's
keyChar field already has the translated character. Replacing
modifiedChar = p->WindowsKeyToJavaChar(winKey, modifiers, AwtComponent::NONE, isDeadKey);
with
modifiedChar = (env)->GetCharField(event, AwtKeyEvent::keyCharID);
seems to work fine.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
I created a simple AWT program with a JTextField. I then used a separate C++ program to send Alt-NumPad sequences using the Win32 SendInput() API, simulating what barcode scanners do in Alt-Mode.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Correct text entered in the text field.
ACTUAL -
Text is incomplete and/or contains other characters.
---------- BEGIN SOURCE ----------
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.event.ItemEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.WindowConstants;
import javax.swing.text.BadLocationException;
public class Main {
public static void main(String[] args) {
JTextArea logBox = new JTextArea();
logBox.setSize(400, 400);
logBox.setFont(new Font("Consolas", Font.PLAIN, 12));
JScrollPane logScroller = new JScrollPane(logBox);
Logger logger = new Logger(logBox);
MyKeyListener keyListener = new MyKeyListener(logger);
JTextField textBox = new JTextField("", 20);
textBox.addKeyListener(keyListener);
JPanel p = new JPanel();
p.setLayout(new GridBagLayout());
p.add(textBox, new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.PAGE_START, 1,
new Insets(0, 0, 0, 0), 0, 0));
p.add(logScroller,
new GridBagConstraints(0, 1, 1, 1, 1, 1, GridBagConstraints.CENTER, 1, new Insets(0, 0, 0, 0),
0, 0));
JFrame f = new JFrame();
f.add(p);
f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
f.setSize(1400, 800);
f.setVisible(true);
}
private static final class Logger {
private final JTextArea logBox;
private long lastLogTime;
private String lastLogLine;
private int lastLogCount;
public Logger(final JTextArea logBox) {
this.logBox = logBox;
}
public void log(String line) {
long now = System.nanoTime();
if ((now - lastLogTime) >= 2 * 1000 * 1000 * 1000)
logBox.setText("");
lastLogTime = now;
if (!line.equals(lastLogLine)) {
logBox.append(line + "\n");
lastLogLine = line;
lastLogCount = 1;
} else {
++lastLogCount;
int lineNum = logBox.getLineCount() - 2;
try {
int start = logBox.getLineStartOffset(lineNum);
int end = logBox.getLineEndOffset(lineNum);
logBox.replaceRange("", start, end);
logBox.append(line + " [%dx]\n".formatted(lastLogCount));
} catch (BadLocationException ex) {
}
}
}
}
private static final class MyKeyListener implements KeyListener {
private final Logger logger;
public MyKeyListener(final Logger logger) {
this.logger = logger;
}
@Override
public void keyTyped(KeyEvent e) {
log(e);
}
@Override
public void keyPressed(KeyEvent e) {
log(e);
}
@Override
public void keyReleased(KeyEvent e) {
log(e);
}
public void log(KeyEvent e) {
logger.log(e.paramString());
}
}
}
// =============================================================================
// C++ helper using SendInput()
// =============================================================================
#include <cstdint>
#include <cstdio>
#include <string>
#include <vector>
#include <Windows.h>
#include <kbd.h>
#pragma comment(lib, "user32.lib")
#define SCANCODE_NUMPAD_7 0x47
#define SCANCODE_NUMPAD_8 0x48
#define SCANCODE_NUMPAD_9 0x49
#define SCANCODE_NUMPAD_4 0x4B
#define SCANCODE_NUMPAD_5 0x4C
#define SCANCODE_NUMPAD_6 0x4D
#define SCANCODE_NUMPAD_1 0x4F
#define SCANCODE_NUMPAD_2 0x50
#define SCANCODE_NUMPAD_3 0x51
#define SCANCODE_NUMPAD_0 0x52
UINT const NumPadScanCodes[] = {
SCANCODE_NUMPAD_0,
SCANCODE_NUMPAD_1, SCANCODE_NUMPAD_2, SCANCODE_NUMPAD_3,
SCANCODE_NUMPAD_4, SCANCODE_NUMPAD_5, SCANCODE_NUMPAD_6,
SCANCODE_NUMPAD_7, SCANCODE_NUMPAD_8, SCANCODE_NUMPAD_9,
};
INPUT CreateScanCodeEvent(WORD scanCode, bool isDown)
{
INPUT input = {};
input.type = INPUT_KEYBOARD;
input.ki.wVk = MapVirtualKeyW(scanCode, MAPVK_VSC_TO_VK);
input.ki.wScan = scanCode;
input.ki.dwFlags = (isDown ? 0 : KEYEVENTF_KEYUP) | KEYEVENTF_SCANCODE;
input.ki.time = 0;
input.ki.dwExtraInfo = 0;
return input;
}
void AddAltCode(std::vector<INPUT>& inputs, char ch)
{
uint8_t value = static_cast<uint8_t>(ch);
uint8_t v3 = value % 10;
value /= 10;
uint8_t v2 = value % 10;
value /= 10;
uint8_t v1 = value;
inputs.push_back(CreateScanCodeEvent(SCANCODE_ALT, true));
inputs.push_back(CreateScanCodeEvent(NumPadScanCodes[v1], true));
inputs.push_back(CreateScanCodeEvent(NumPadScanCodes[v1], false));
inputs.push_back(CreateScanCodeEvent(NumPadScanCodes[v2], true));
inputs.push_back(CreateScanCodeEvent(NumPadScanCodes[v2], false));
inputs.push_back(CreateScanCodeEvent(NumPadScanCodes[v3], true));
inputs.push_back(CreateScanCodeEvent(NumPadScanCodes[v3], false));
inputs.push_back(CreateScanCodeEvent(SCANCODE_ALT, false));
}
int main(int argc, char** argv)
{
std::string text = "12763172357126351723516753";
if (argc == 2)
text = argv[1];
std::vector<INPUT> inputs;
for (char ch : text)
AddAltCode(inputs, ch);
for (int i = 2; i >= 1; --i) {
printf("%d...\n", i);
Sleep(1000);
}
printf("Sending inputs (%s)\n", text.c_str());
UINT numSent = SendInput(inputs.size(), inputs.data(), sizeof(INPUT));
printf("Events sent: %u\n", numSent);
return 0;
}
---------- END SOURCE ----------
FREQUENCY : always
Windows 10, Java 8 to 20
A DESCRIPTION OF THE PROBLEM :
Entering characters using Alt-NumPad often produce garbage characters.
In a normal Win32 message loop Windows' TranslateMessage() is responsible for
translating WM_KEYDOWN/WK_SYSKEYDOWN and WM_KEYUP/WM_SYSKEYUP messages to
WM_CHAR messages. It handles composite keys, ligatures, dead keys and
Alt-NumPad sequences. For Alt-NumPad entry, the kernel-mode function
win32kbase.sys!xxxInternalToUnicode() has an internal process-wide NumPad
state to keep track of consecutive keyboard messages between pressing and
releasing the Alt key.
ToUnicodeEx() can be used to manually translate virtual-key codes to
characters. It also ultimately calls xxxInternalToUnicode() and changes the
state of the kernel-mode keyboard buffer. Official docs also warn that using
it in conjunction with TranslateMessage() can cause undesired side effects.
AWT's message loop uses ToUnicodeEx() in keydown and keyup message handlers.
These uses are benign since xxxInternalToUnicode() compensates by ignoring
duplicate consecutive key messages. The message loop then sends KeyEvents to
the AWT event queue thread, which in turn (in AwtComponent::_NativeHandleEvent
via AwtComponent::WindowsKeyToJavaChar) invokes ToUnicodeEx() again. This time
(depending on thread scheduling) it corrupts the NumPad state.
Not sure why WindowsKeyToJavaChar() is called here again, since the KeyEvent's
keyChar field already has the translated character. Replacing
modifiedChar = p->WindowsKeyToJavaChar(winKey, modifiers, AwtComponent::NONE, isDeadKey);
with
modifiedChar = (env)->GetCharField(event, AwtKeyEvent::keyCharID);
seems to work fine.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
I created a simple AWT program with a JTextField. I then used a separate C++ program to send Alt-NumPad sequences using the Win32 SendInput() API, simulating what barcode scanners do in Alt-Mode.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Correct text entered in the text field.
ACTUAL -
Text is incomplete and/or contains other characters.
---------- BEGIN SOURCE ----------
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.event.ItemEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.WindowConstants;
import javax.swing.text.BadLocationException;
public class Main {
public static void main(String[] args) {
JTextArea logBox = new JTextArea();
logBox.setSize(400, 400);
logBox.setFont(new Font("Consolas", Font.PLAIN, 12));
JScrollPane logScroller = new JScrollPane(logBox);
Logger logger = new Logger(logBox);
MyKeyListener keyListener = new MyKeyListener(logger);
JTextField textBox = new JTextField("", 20);
textBox.addKeyListener(keyListener);
JPanel p = new JPanel();
p.setLayout(new GridBagLayout());
p.add(textBox, new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.PAGE_START, 1,
new Insets(0, 0, 0, 0), 0, 0));
p.add(logScroller,
new GridBagConstraints(0, 1, 1, 1, 1, 1, GridBagConstraints.CENTER, 1, new Insets(0, 0, 0, 0),
0, 0));
JFrame f = new JFrame();
f.add(p);
f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
f.setSize(1400, 800);
f.setVisible(true);
}
private static final class Logger {
private final JTextArea logBox;
private long lastLogTime;
private String lastLogLine;
private int lastLogCount;
public Logger(final JTextArea logBox) {
this.logBox = logBox;
}
public void log(String line) {
long now = System.nanoTime();
if ((now - lastLogTime) >= 2 * 1000 * 1000 * 1000)
logBox.setText("");
lastLogTime = now;
if (!line.equals(lastLogLine)) {
logBox.append(line + "\n");
lastLogLine = line;
lastLogCount = 1;
} else {
++lastLogCount;
int lineNum = logBox.getLineCount() - 2;
try {
int start = logBox.getLineStartOffset(lineNum);
int end = logBox.getLineEndOffset(lineNum);
logBox.replaceRange("", start, end);
logBox.append(line + " [%dx]\n".formatted(lastLogCount));
} catch (BadLocationException ex) {
}
}
}
}
private static final class MyKeyListener implements KeyListener {
private final Logger logger;
public MyKeyListener(final Logger logger) {
this.logger = logger;
}
@Override
public void keyTyped(KeyEvent e) {
log(e);
}
@Override
public void keyPressed(KeyEvent e) {
log(e);
}
@Override
public void keyReleased(KeyEvent e) {
log(e);
}
public void log(KeyEvent e) {
logger.log(e.paramString());
}
}
}
// =============================================================================
// C++ helper using SendInput()
// =============================================================================
#include <cstdint>
#include <cstdio>
#include <string>
#include <vector>
#include <Windows.h>
#include <kbd.h>
#pragma comment(lib, "user32.lib")
#define SCANCODE_NUMPAD_7 0x47
#define SCANCODE_NUMPAD_8 0x48
#define SCANCODE_NUMPAD_9 0x49
#define SCANCODE_NUMPAD_4 0x4B
#define SCANCODE_NUMPAD_5 0x4C
#define SCANCODE_NUMPAD_6 0x4D
#define SCANCODE_NUMPAD_1 0x4F
#define SCANCODE_NUMPAD_2 0x50
#define SCANCODE_NUMPAD_3 0x51
#define SCANCODE_NUMPAD_0 0x52
UINT const NumPadScanCodes[] = {
SCANCODE_NUMPAD_0,
SCANCODE_NUMPAD_1, SCANCODE_NUMPAD_2, SCANCODE_NUMPAD_3,
SCANCODE_NUMPAD_4, SCANCODE_NUMPAD_5, SCANCODE_NUMPAD_6,
SCANCODE_NUMPAD_7, SCANCODE_NUMPAD_8, SCANCODE_NUMPAD_9,
};
INPUT CreateScanCodeEvent(WORD scanCode, bool isDown)
{
INPUT input = {};
input.type = INPUT_KEYBOARD;
input.ki.wVk = MapVirtualKeyW(scanCode, MAPVK_VSC_TO_VK);
input.ki.wScan = scanCode;
input.ki.dwFlags = (isDown ? 0 : KEYEVENTF_KEYUP) | KEYEVENTF_SCANCODE;
input.ki.time = 0;
input.ki.dwExtraInfo = 0;
return input;
}
void AddAltCode(std::vector<INPUT>& inputs, char ch)
{
uint8_t value = static_cast<uint8_t>(ch);
uint8_t v3 = value % 10;
value /= 10;
uint8_t v2 = value % 10;
value /= 10;
uint8_t v1 = value;
inputs.push_back(CreateScanCodeEvent(SCANCODE_ALT, true));
inputs.push_back(CreateScanCodeEvent(NumPadScanCodes[v1], true));
inputs.push_back(CreateScanCodeEvent(NumPadScanCodes[v1], false));
inputs.push_back(CreateScanCodeEvent(NumPadScanCodes[v2], true));
inputs.push_back(CreateScanCodeEvent(NumPadScanCodes[v2], false));
inputs.push_back(CreateScanCodeEvent(NumPadScanCodes[v3], true));
inputs.push_back(CreateScanCodeEvent(NumPadScanCodes[v3], false));
inputs.push_back(CreateScanCodeEvent(SCANCODE_ALT, false));
}
int main(int argc, char** argv)
{
std::string text = "12763172357126351723516753";
if (argc == 2)
text = argv[1];
std::vector<INPUT> inputs;
for (char ch : text)
AddAltCode(inputs, ch);
for (int i = 2; i >= 1; --i) {
printf("%d...\n", i);
Sleep(1000);
}
printf("Sending inputs (%s)\n", text.c_str());
UINT numSent = SendInput(inputs.size(), inputs.data(), sizeof(INPUT));
printf("Events sent: %u\n", numSent);
return 0;
}
---------- END SOURCE ----------
FREQUENCY : always
- relates to
-
JDK-8058526 Random characters in KeyEvents when ALT key is pressed
- Open
-
JDK-8163763 ComboBox: incorrect processing of ALT + Numeric Keypad KEY DOWN/UP combination
- Open