--- /dev/null 2017-09-21 14:03:56.000000000 +0530 +++ new/test/java/awt/Robot/RobotUnicodeCodeTest.java 2017-09-21 14:03:54.729185900 +0530 @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * 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 + * @bug 8148344 + * @summary Unicode text print via Java Robot + @run main/manual RobotUnicodeCodeTest + */ +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.event.ActionEvent; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JTextArea; +import javax.swing.SwingUtilities; +import java.awt.AWTException; +import java.awt.Robot; +import java.awt.event.InputEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +public class RobotUnicodeCodeTest { + private static TestUI test = null; + private static CountDownLatch testLatch = null; + + public static void disposeUI() throws Exception { + SwingUtilities.invokeAndWait(() -> { + if(test != null) { + test.disposeUI(); + } + }); + } + + public static void main(String[] args) throws Exception { + testLatch = new CountDownLatch(1); + test = new TestUI(testLatch); + int [] unicodeCodes = { + // U+201A(8218 in decimal) represents comma characher(,) + // U+49, U+4F, U+50, + 73, 8218, 79, 8218, 80, 8218, + + // U+C5, + 197, 8218, + + // Surrogate pair for U+10437, + 55297, 56375, + 8218, + + // Surrogare pair for U+24B62, + 55378, 57186, + 8218, + + // U+4B, U+4A + 75, 8218, 74}; + + // initialize the test UI system + SwingUtilities.invokeAndWait(() -> { + try { + test.createUI(); + } catch (Exception ex) { + throw new RuntimeException("Exception while creating UI"); + } + }); + + RobotFrame robotFrame = new RobotFrame(); + try { + Robot robot = new Robot(); + robot.setAutoDelay(300); + robot.mouseMove(450, 450); + robot.mousePress(InputEvent.BUTTON1_MASK); + robot.mouseRelease(InputEvent.BUTTON1_MASK); + + // Input unicode keys to Robot + for (int i = 0; i < unicodeCodes.length; i++) { + robot.keyPressUnicode(unicodeCodes[i]); + robot.keyReleaseUnicode(unicodeCodes[i]); + } + } catch (AWTException e) { + robotFrame.dispose(); + disposeUI(); + throw new RuntimeException("Exception while Java Robot unicode key input"); + } + + boolean status = testLatch.await(1, TimeUnit.MINUTES); + if (!status) { + System.out.println("Test timed out."); + } + + if (test.testResult == false) { + disposeUI(); + throw new RuntimeException("Test Failed."); + } + + robotFrame.dispose(); + } + + @SuppressWarnings("serial") + private static class RobotFrame extends JFrame { + RobotFrame() { + this.setLocation(300, 300); + this.setTitle("RobotUnicodeCodeFrame"); + JTextArea textArea = new JTextArea(25, 50); + this.add(textArea); + this.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + System.exit(0); + } + }); + this.pack(); + this.setVisible(true); + } + } +} + +class TestUI { + + private static JFrame mainFrame; + private static JPanel mainControlPanel; + + private static JTextArea instructionTextArea; + + private static JPanel resultButtonPanel; + private static JButton passButton; + private static JButton failButton; + + private GridBagConstraints gbc; + private static GridBagLayout layout; + private final CountDownLatch latch; + public boolean testResult = false; + + public TestUI(CountDownLatch latch) { + this.latch = latch; + } + + public final void createUI() throws Exception { + mainFrame = new JFrame(); + + layout = new GridBagLayout(); + mainControlPanel = new JPanel(layout); + resultButtonPanel = new JPanel(layout); + + gbc = new GridBagConstraints(); + + // Create Test instructions + String instructions + = "See for the Unicode chars(UTF-16) printed in the following order \n" + + " U+49, U+4F, U+50, U+C5, U+10437(Surrogate pairs), \n" + + " U+24B62(Surrogate pairs), U+4B, U+4A \n" + + "If yes, click on 'pass' else click on 'fail'\n"; + + instructionTextArea = new JTextArea(); + instructionTextArea.setText(instructions); + instructionTextArea.setEditable(false); + instructionTextArea.setBorder(BorderFactory. + createTitledBorder("Test Instructions")); + + gbc.gridx = 0; + gbc.gridy = 0; + mainControlPanel.add(instructionTextArea, gbc); + + // Add customization to this test ui + customize(); + + // Create resultButtonPanel with Pass, Fail buttons + passButton = new JButton("Pass"); + passButton.setActionCommand("Pass"); + passButton.addActionListener((ActionEvent e) -> { + System.out.println("Pass Button pressed!"); + testResult = true; + latch.countDown(); + disposeUI(); + }); + + failButton = new JButton("Fail"); + failButton.setActionCommand("Fail"); + failButton.addActionListener((ActionEvent e) -> { + System.out.println("Fail Button pressed!"); + testResult = false; + latch.countDown(); + disposeUI(); + }); + + gbc.gridx = 0; + gbc.gridy = 0; + resultButtonPanel.add(passButton, gbc); + + gbc.gridx = 1; + gbc.gridy = 0; + resultButtonPanel.add(failButton, gbc); + + gbc.gridx = 0; + gbc.gridy = 2; + mainControlPanel.add(resultButtonPanel, gbc); + + mainFrame.add(mainControlPanel); + mainFrame.pack(); + mainFrame.setVisible(true); + } + + public void disposeUI() { + mainFrame.dispose(); + } + + private void customize() throws Exception { + // Customize the test UI title + mainFrame.setTitle("RobotUnicodeCodeTest"); + } +} --- old/src/java.desktop/macosx/classes/sun/lwawt/macosx/CRobot.java 2017-09-21 14:04:01.103897600 +0530 +++ new/src/java.desktop/macosx/classes/sun/lwawt/macosx/CRobot.java 2017-09-21 14:04:00.110756700 +0530 @@ -166,6 +166,42 @@ } /** + * Presses a given key. + *

+ * Key codes that have more than one physical key associated with them + * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the + * left or right shift key) will map to the left key. + *

+ * Assumes that the + * peer implementations will throw an exception for other bogus + * values e.g. -1, 999999 + * + * @param keycode the key to press (e.g. {@code KeyEvent.VK_A}) + */ + @Override + public void keyPressUnicode(final int key) { + keyEventUnicode(key, true); + } + + /** + * Releases a given key. + *

+ * Key codes that have more than one physical key associated with them + * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the + * left or right shift key) will map to the left key. + *

+ * Assumes that the + * peer implementations will throw an exception for other bogus + * values e.g. -1, 999999 + * + * @param keycode the key to release (e.g. {@code KeyEvent.VK_A}) + */ + @Override + public void keyReleaseUnicode(final int key) { + keyEventUnicode(key, false); + } + + /** * Returns the color of a pixel at the given screen coordinates. * @param x X position of pixel * @param y Y position of pixel @@ -198,6 +234,7 @@ boolean isButtonsDownState, boolean isMouseMove); private native void keyEvent(int javaKeyCode, boolean keydown); + private native void keyEventUnicode(int javaKeyCode, boolean keydown); private void getScreenPixels(Rectangle r, int[] pixels){ double scale = fDevice.getScaleFactor(); nativeGetScreenPixels(r.x, r.y, r.width, r.height, scale, pixels); --- old/src/java.desktop/macosx/native/libawt_lwawt/awt/CRobot.m 2017-09-21 14:04:08.453202200 +0530 +++ new/src/java.desktop/macosx/native/libawt_lwawt/awt/CRobot.m 2017-09-21 14:04:07.439914000 +0530 @@ -260,9 +260,35 @@ (JNIEnv *env, jobject peer, jint javaKeyCode, jboolean keyPressed) { CGKeyCode keyCode = GetCGKeyCode(javaKeyCode); - CGEventRef event = CGEventCreateKeyboardEvent(NULL, keyCode, keyPressed); + + if (event != NULL) { + CGEventPost(kCGSessionEventTap, event); + CFRelease(event); + } +} + +/* + * Class: sun_lwawt_macosx_CRobot + * Method: keyEventUnicode + * Signature: (IZ)V + */ +JNIEXPORT void JNICALL +Java_sun_lwawt_macosx_CRobot_keyEventUnicode +(JNIEnv *env, jobject peer, jint key, jboolean keyPressed) +{ + CGEventRef event; + UniChar uCh = key; + + // create a null keyboard event + event = CGEventCreateKeyboardEvent(NULL, 0, keyPressed); + if (event != NULL) { + /* Note that application frameworks may ignore the Unicode string in a + keyboard event and do their own translation based on the + virtual keycode and perceived event state. + */ + CGEventKeyboardSetUnicodeString(event, 1, &uCh); CGEventPost(kCGSessionEventTap, event); CFRelease(event); } @@ -286,7 +312,7 @@ jint picHeight = height; CGRect screenRect = CGRectMake(picX / scale, picY / scale, - picWidth / scale, picHeight / scale); + picWidth / scale, picHeight / scale); CGImageRef screenPixelsImage = CGWindowListCreateImage(screenRect, kCGWindowListOptionOnScreenOnly, kCGNullWindowID, kCGWindowImageBestResolution); --- old/src/java.desktop/share/classes/java/awt/Robot.java 2017-09-21 14:04:16.771886600 +0530 +++ new/src/java.desktop/share/classes/java/awt/Robot.java 2017-09-21 14:04:15.338576700 +0530 @@ -377,6 +377,39 @@ afterEvent(); } + /** + * Presses a given unicode key. The key should be released using the + * {@code keyReleaseUnicode} method. + *

+ * + * @param key unicode to press + * @throws IllegalArgumentException if {@code key} is not + * a valid unicode key + * @see #keyReleaseUnicode(int) + * @see java.awt.event.KeyEvent + */ + public synchronized void keyPressUnicode(int key) { + checkKeycodeArgument(key); + peer.keyPressUnicode(key); + afterEvent(); + } + + /** + * Releases a given unicode key. + *

+ * + * @param key unicode to release + * @throws IllegalArgumentException if {@code key} is not a + * valid key + * @see #keyPressUnicode(int) + * @see java.awt.event.KeyEvent + */ + public synchronized void keyReleaseUnicode(int key) { + checkKeycodeArgument(key); + peer.keyReleaseUnicode(key); + afterEvent(); + } + private void checkKeycodeArgument(int keycode) { // rather than build a big table or switch statement here, we'll // just check that the key isn't VK_UNDEFINED and assume that the --- old/src/java.desktop/share/classes/java/awt/peer/RobotPeer.java 2017-09-21 14:04:25.293661500 +0530 +++ new/src/java.desktop/share/classes/java/awt/peer/RobotPeer.java 2017-09-21 14:04:24.161149700 +0530 @@ -95,6 +95,24 @@ void keyRelease(int keycode); /** + * Simulates a key press of the specified unicode key. + * + * @param key unicode to press + * + * @see Robot#keyPressUnicode(int) + */ + void keyPressUnicode(int key); + + /** + * Simulates a key release of the specified unicode key. + * + * @param key unicode to release + * + * @see Robot#keyReleaseUnicode(int) + */ + void keyReleaseUnicode(int key); + + /** * Gets the RGB value of the specified pixel on screen. * * @param x the X screen coordinate --- old/src/java.desktop/unix/classes/sun/awt/X11/XRobotPeer.java 2017-09-21 14:04:33.313119100 +0530 +++ new/src/java.desktop/unix/classes/sun/awt/X11/XRobotPeer.java 2017-09-21 14:04:32.272351600 +0530 @@ -105,6 +105,17 @@ } @Override + public void keyPressUnicode( int key ) { + keyPressImpl(key); + } + + @Override + public void keyReleaseUnicode( int key ) { + keyReleaseImpl(key); + } + + + @Override public int getRGBPixel(int x, int y) { int pixelArray[] = new int[1]; getRGBPixelsImpl(xgc, x, y, 1, 1, pixelArray, useGtk); --- old/src/java.desktop/windows/classes/sun/awt/windows/WRobotPeer.java 2017-09-21 14:04:41.370664500 +0530 +++ new/src/java.desktop/windows/classes/sun/awt/windows/WRobotPeer.java 2017-09-21 14:04:40.328404400 +0530 @@ -63,6 +63,11 @@ public native void keyRelease( int keycode ); @Override + public native void keyPressUnicode( int key ); + @Override + public native void keyReleaseUnicode( int key ); + + @Override public int getRGBPixel(int x, int y) { // See 7002846: that's ineffective, but works correctly with non-opaque windows return getRGBPixels(new Rectangle(x, y, 1, 1))[0]; --- old/src/java.desktop/windows/native/libawt/windows/awt_Robot.cpp 2017-09-21 14:04:49.375472700 +0530 +++ new/src/java.desktop/windows/native/libawt/windows/awt_Robot.cpp 2017-09-21 14:04:48.340200500 +0530 @@ -299,44 +299,81 @@ void AwtRobot::KeyPress( jint jkey ) { - DoKeyEvent(jkey, 0); // no flags means key down + DoKeyEvent(jkey, 0, false); // no flags means key down } void AwtRobot::KeyRelease( jint jkey ) { - DoKeyEvent(jkey, KEYEVENTF_KEYUP); + DoKeyEvent(jkey, KEYEVENTF_KEYUP, false); } -void AwtRobot::DoKeyEvent( jint jkey, DWORD dwFlags ) +void AwtRobot::KeyPressUnicode( jint jkey ) +{ + DoKeyEvent(jkey, 0, true); // no flags means key down +} + +void AwtRobot::KeyReleaseUnicode( jint jkey ) +{ + DoKeyEvent(jkey, KEYEVENTF_KEYUP, true); +} + +void AwtRobot::DoKeyEvent( jint jkey, DWORD dwFlags, BOOL isUnicode ) { UINT vkey; UINT modifiers; UINT scancode; JNIEnv * env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2); - // convert Java key into Windows key (and modifiers too) - AwtComponent::JavaKeyToWindowsKey(jkey, &vkey, &modifiers); - if (vkey == 0) { - // no equivalent Windows key found for given Java keycode - JNU_ThrowIllegalArgumentException(env, "Invalid key code"); - } else { - // get the scancode from the virtual key - scancode = ::MapVirtualKey(vkey, 0); - if (vkey == VK_RMENU || - vkey == VK_DELETE || - vkey == VK_INSERT || - vkey == VK_NEXT || - vkey == VK_PRIOR || - vkey == VK_HOME || - vkey == VK_END || - vkey == VK_LEFT || - vkey == VK_RIGHT || - vkey == VK_UP || - vkey == VK_DOWN) { - dwFlags |= KEYEVENTF_EXTENDEDKEY; + if(isUnicode) {printf("In unicode func:%d", jkey); + /* + HandleUnicodeKeys() returns the status of the SendInput() + which returns 0 if there is a fail else non-zero. + The status 0 tells that the SendInput() in unable to interpret + the supplied input upon which the illegal argument exception + would be raised. + */ + if(!HandleUnicodeKeys(jkey, dwFlags)) { + // no equivalent Windows key found for given unicode key + JNU_ThrowIllegalArgumentException(env, "Invalid unicode key"); } - keybd_event(vkey, scancode, dwFlags, 0); - } + } + else { + // convert Java key into Windows key (and modifiers too) + AwtComponent::JavaKeyToWindowsKey(jkey, &vkey, &modifiers); + + if (vkey == 0) { + // no equivalent Windows key found for given Java keycode + JNU_ThrowIllegalArgumentException(env, "Invalid key code"); + } else { + // get the scancode from the virtual key + scancode = ::MapVirtualKey(vkey, 0); + if (vkey == VK_RMENU || + vkey == VK_DELETE || + vkey == VK_INSERT || + vkey == VK_NEXT || + vkey == VK_PRIOR || + vkey == VK_HOME || + vkey == VK_END || + vkey == VK_LEFT || + vkey == VK_RIGHT || + vkey == VK_UP || + vkey == VK_DOWN) { + dwFlags |= KEYEVENTF_EXTENDEDKEY; + } + keybd_event(vkey, scancode, dwFlags, 0); + } + } +} + +UINT AwtRobot::HandleUnicodeKeys(jint key, DWORD dwFlags) +{ + NSWinInput::INPUT ip; + ip.type = 1; //INPUT_KEYBOARD; + ip.ki.wVk = 0; + ip.ki.wScan = key; + ip.ki.dwFlags = (DWORD)(dwFlags | 4); //KEYEVENTF_UNICODE(4) + ip.ki.dwExtraInfo = 0; + return SendInput(1, (LPINPUT)&ip, sizeof(INPUT)); } // @@ -444,3 +481,24 @@ CATCH_BAD_ALLOC; } + +JNIEXPORT void JNICALL Java_sun_awt_windows_WRobotPeer_keyPressUnicode( + JNIEnv *, jobject self, jint javakey ) +{ + TRY; + + AwtRobot::GetRobot(self)->KeyPressUnicode(javakey); + + CATCH_BAD_ALLOC; +} + +JNIEXPORT void JNICALL Java_sun_awt_windows_WRobotPeer_keyReleaseUnicode( + JNIEnv *, jobject self, jint javakey ) +{ + TRY; + + AwtRobot::GetRobot(self)->KeyReleaseUnicode(javakey); + + CATCH_BAD_ALLOC; +} + --- old/src/java.desktop/windows/native/libawt/windows/awt_Robot.h 2017-09-21 14:04:57.442764500 +0530 +++ new/src/java.desktop/windows/native/libawt/windows/awt_Robot.h 2017-09-21 14:04:56.365038600 +0530 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,6 +31,40 @@ #include "sun_awt_windows_WRobotPeer.h" #include "jlong.h" +namespace NSWinInput { + typedef struct tagHARDWAREINPUT { + DWORD uMsg; + WORD wParamL; + WORD wParamH; + } HARDWAREINPUT; + + typedef struct tagMOUSEINPUT { + LONG dx; + LONG dy; + DWORD mouseData; + DWORD dwFlags; + DWORD time; + ULONG_PTR dwExtraInfo; + } MOUSEINPUT; + + typedef struct tagKEYBDINPUT { + WORD wVk; + WORD wScan; + DWORD dwFlags; + DWORD time; + ULONG_PTR dwExtraInfo; + } KEYBDINPUT; + + typedef struct tagINPUT { + DWORD type; + union { + MOUSEINPUT mi; + KEYBDINPUT ki; + HARDWAREINPUT hi; + }; + } INPUT; +} + class AwtRobot : public AwtObject { public: @@ -48,10 +82,13 @@ void KeyPress( jint key ); void KeyRelease( jint key ); + void KeyPressUnicode( jint key ); + void KeyReleaseUnicode( jint key ); static AwtRobot * GetRobot( jobject self ); private: - void DoKeyEvent( jint jkey, DWORD dwFlags ); + void DoKeyEvent( jint jkey, DWORD dwFlags, BOOL isUnicode ); + UINT HandleUnicodeKeys(jint key, DWORD dwFlags); static jint WinToJavaPixel(USHORT r, USHORT g, USHORT b); };