Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8307942

Alt+NumPad entry does not work reliably and can produce garbled text

XMLWordPrintable

    • 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


        1. Main.java
          4 kB
        2. Capture.PNG
          Capture.PNG
          128 kB

            rmahajan Rajat Mahajan
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated: