jdk/src/java.desktop/macosx/native/libawt_lwawt/awt/CRobot.m
changeset 26751 70bac69b37c9
parent 25859 3317bb8137f4
child 30924 c8765f1fea21
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/macosx/native/libawt_lwawt/awt/CRobot.m	Fri Sep 19 09:41:05 2014 -0700
@@ -0,0 +1,654 @@
+/*
+ * Copyright (c) 2011, 2014, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+
+#import "jni_util.h"
+
+#import <JavaNativeFoundation/JavaNativeFoundation.h>
+#import <ApplicationServices/ApplicationServices.h>
+
+#import "LWCToolkit.h"
+#import "sun_lwawt_macosx_CRobot.h"
+#import "java_awt_event_InputEvent.h"
+#import "sizecalc.h"
+
+
+// Starting number for event numbers generated by Robot.
+// Apple docs don't mention at all what are the requirements
+// for these numbers. It seems that they must be higher
+// than event numbers from real events, which start at some
+// value close to zero. There is no API for obtaining current
+// event number, so we have to start from some random number.
+// 32000 as starting value works for me, let's hope that it will
+// work for others as well.
+#define ROBOT_EVENT_NUMBER_START 32000
+
+#define k_JAVA_ROBOT_WHEEL_COUNT 1
+
+#if !defined(kCGBitmapByteOrder32Host)
+#define kCGBitmapByteOrder32Host 0
+#endif
+
+// In OS X, left and right mouse button share the same click count.
+// That is, if one starts clicking the left button rapidly and then
+// switches to the right button, then the click count will continue
+// increasing, without dropping to 1 in between. The middle button,
+// however, has its own click count.
+// For robot, we aren't going to emulate all that complexity. All our
+// synhtetic clicks share the same click count.
+static int gsClickCount;
+static NSTimeInterval gsLastClickTime;
+
+// Apparently, for mouse up/down events we have to set an event number
+// that is incremented on each button press. Otherwise, strange things
+// happen with z-order.
+static int gsEventNumber;
+static int* gsButtonEventNumber;
+
+static inline CGKeyCode GetCGKeyCode(jint javaKeyCode);
+
+static void PostMouseEvent(const CGPoint point, CGMouseButton button,
+                           CGEventType type, int clickCount, int eventNumber);
+
+static int GetClickCount(BOOL isDown);
+
+static void
+CreateJavaException(JNIEnv* env, CGError err)
+{
+    // Throw a java exception indicating what is wrong.
+    NSString* s = [NSString stringWithFormat:@"Robot: CGError: %d", err];
+    (*env)->ThrowNew(env, (*env)->FindClass(env, "java/awt/AWTException"),
+                     [s UTF8String]);
+}
+
+/*
+ * Class:     sun_lwawt_macosx_CRobot
+ * Method:    initRobot
+ * Signature: (V)V
+ */
+JNIEXPORT void JNICALL
+Java_sun_lwawt_macosx_CRobot_initRobot
+(JNIEnv *env, jobject peer)
+{
+    // Set things up to let our app act like a synthetic keyboard and mouse.
+    // Always set all states, in case Apple ever changes default behaviors.
+    static int setupDone = 0;
+    if (!setupDone) {
+        int i;
+        jint* tmp;
+        jboolean copy = JNI_FALSE;
+
+        setupDone = 1;
+        // Don't block local events after posting ours
+        CGSetLocalEventsSuppressionInterval(0.0);
+
+        // Let our event's modifier key state blend with local hardware events
+        CGEnableEventStateCombining(TRUE);
+
+        // Don't let our events block local hardware events
+        CGSetLocalEventsFilterDuringSupressionState(
+                                    kCGEventFilterMaskPermitAllEvents,
+                                    kCGEventSupressionStateSupressionInterval);
+        CGSetLocalEventsFilterDuringSupressionState(
+                                    kCGEventFilterMaskPermitAllEvents,
+                                    kCGEventSupressionStateRemoteMouseDrag);
+
+        gsClickCount = 0;
+        gsLastClickTime = 0;
+        gsEventNumber = ROBOT_EVENT_NUMBER_START;
+
+        gsButtonEventNumber = (int*)SAFE_SIZE_ARRAY_ALLOC(malloc, sizeof(int), gNumberOfButtons);
+        if (gsButtonEventNumber == NULL) {
+            JNU_ThrowOutOfMemoryError(env, NULL);
+            return;
+        }
+
+        for (i = 0; i < gNumberOfButtons; ++i) {
+            gsButtonEventNumber[i] = ROBOT_EVENT_NUMBER_START;
+        }
+    }
+}
+
+/*
+ * Class:     sun_lwawt_macosx_CRobot
+ * Method:    mouseEvent
+ * Signature: (IIIIZZ)V
+ */
+JNIEXPORT void JNICALL
+Java_sun_lwawt_macosx_CRobot_mouseEvent
+(JNIEnv *env, jobject peer,
+ jint displayID, jint mouseLastX, jint mouseLastY, jint buttonsState,
+ jboolean isButtonsDownState, jboolean isMouseMove)
+{
+    JNF_COCOA_ENTER(env);
+
+    // This is the native method called when Robot mouse events occur.
+    // The CRobot tracks the mouse position, and which button was
+    // pressed. If the mouse position is unknown it is obtained from
+    // CGEvents. The peer also tracks the mouse button desired state,
+    // the appropriate key modifier state, and whether the mouse action
+    // is simply a mouse move with no mouse button state changes.
+
+    CGError err = kCGErrorSuccess;
+
+    CGRect globalDeviceBounds = CGDisplayBounds(displayID);
+
+    // Set unknown mouse location, if needed.
+    if ((mouseLastX == sun_lwawt_macosx_CRobot_MOUSE_LOCATION_UNKNOWN) ||
+        (mouseLastY == sun_lwawt_macosx_CRobot_MOUSE_LOCATION_UNKNOWN))
+    {
+        CGEventRef event = CGEventCreate(NULL);
+        if (event == NULL) {
+            return;
+        }
+
+        CGPoint globalPos = CGEventGetLocation(event);
+        CFRelease(event);
+
+        // Normalize the coords within this display device, as
+        // per Robot rules.
+        if (globalPos.x < CGRectGetMinX(globalDeviceBounds)) {
+            globalPos.x = CGRectGetMinX(globalDeviceBounds);
+        }
+        else if (globalPos.x > CGRectGetMaxX(globalDeviceBounds)) {
+            globalPos.x = CGRectGetMaxX(globalDeviceBounds);
+        }
+
+        if (globalPos.y < CGRectGetMinY(globalDeviceBounds)) {
+            globalPos.y = CGRectGetMinY(globalDeviceBounds);
+        }
+        else if (globalPos.y > CGRectGetMaxY(globalDeviceBounds)) {
+            globalPos.y = CGRectGetMaxY(globalDeviceBounds);
+        }
+
+        mouseLastX = (jint)globalPos.x;
+        mouseLastY = (jint)globalPos.y;
+    }
+
+    // volatile, otherwise it warns that it might be clobbered by 'longjmp'
+    volatile CGPoint point;
+
+    point.x = mouseLastX;
+    point.y = mouseLastY;
+
+    __block CGMouseButton button = kCGMouseButtonLeft;
+    __block CGEventType type = kCGEventMouseMoved;
+
+    void (^HandleRobotButton)(CGMouseButton, CGEventType, CGEventType, CGEventType) =
+        ^(CGMouseButton cgButton, CGEventType cgButtonUp, CGEventType cgButtonDown,
+          CGEventType cgButtonDragged) {
+
+            button = cgButton;
+            type = cgButtonUp;
+
+            if (isButtonsDownState) {
+                if (isMouseMove) {
+                    type = cgButtonDragged;
+                } else {
+                    type = cgButtonDown;
+                }
+            }
+        };
+
+    // Left
+    if (buttonsState & java_awt_event_InputEvent_BUTTON1_MASK ||
+        buttonsState & java_awt_event_InputEvent_BUTTON1_DOWN_MASK ) {
+
+        HandleRobotButton(kCGMouseButtonLeft, kCGEventLeftMouseUp,
+                          kCGEventLeftMouseDown, kCGEventLeftMouseDragged);
+    }
+
+    // Other
+    if (buttonsState & java_awt_event_InputEvent_BUTTON2_MASK ||
+        buttonsState & java_awt_event_InputEvent_BUTTON2_DOWN_MASK ) {
+
+        HandleRobotButton(kCGMouseButtonCenter, kCGEventOtherMouseUp,
+                          kCGEventOtherMouseDown, kCGEventOtherMouseDragged);
+    }
+
+    // Right
+    if (buttonsState & java_awt_event_InputEvent_BUTTON3_MASK ||
+        buttonsState & java_awt_event_InputEvent_BUTTON3_DOWN_MASK ) {
+
+        HandleRobotButton(kCGMouseButtonRight, kCGEventRightMouseUp,
+                          kCGEventRightMouseDown, kCGEventRightMouseDragged);
+    }
+
+    // Extra
+    if (gNumberOfButtons > 3) {
+        int extraButton;
+        for (extraButton = 3; extraButton < gNumberOfButtons; ++extraButton) {
+            if ((buttonsState & gButtonDownMasks[extraButton])) {
+                HandleRobotButton(extraButton, kCGEventOtherMouseUp,
+                            kCGEventOtherMouseDown, kCGEventOtherMouseDragged);
+            }
+        }
+    }
+
+    int clickCount = 0;
+    int eventNumber = gsEventNumber;
+
+    if (isMouseMove) {
+        // any mouse movement resets click count
+        gsLastClickTime = 0;
+    } else {
+        clickCount = GetClickCount(isButtonsDownState);
+
+        if (isButtonsDownState) {
+            gsButtonEventNumber[button] = gsEventNumber++;
+        }
+        eventNumber = gsButtonEventNumber[button];
+    }
+
+    PostMouseEvent(point, button, type, clickCount, eventNumber);
+
+    JNF_COCOA_EXIT(env);
+}
+
+/*
+ * Class:     sun_lwawt_macosx_CRobot
+ * Method:    mouseWheel
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_sun_lwawt_macosx_CRobot_mouseWheel
+(JNIEnv *env, jobject peer, jint wheelAmt)
+{
+    CGEventRef event = CGEventCreateScrollWheelEvent(NULL,
+                                            kCGScrollEventUnitLine,
+                                            k_JAVA_ROBOT_WHEEL_COUNT, wheelAmt);
+
+    if (event != NULL) {
+        CGEventPost(kCGSessionEventTap, event);
+        CFRelease(event);
+    }
+}
+
+/*
+ * Class:     sun_lwawt_macosx_CRobot
+ * Method:    keyEvent
+ * Signature: (IZ)V
+ */
+JNIEXPORT void JNICALL
+Java_sun_lwawt_macosx_CRobot_keyEvent
+(JNIEnv *env, jobject peer, jint javaKeyCode, jboolean keyPressed)
+{
+    /*
+     * Well, using CGEventCreateKeyboardEvent/CGEventPost would have been
+     * a better solution, however, it gives me all kinds of trouble and I have
+     * no idea how to solve them without inserting delays between simulated
+     * events. So, I've ended up disabling it and opted for another approach
+     * that uses Accessibility API instead.
+     */
+    CGKeyCode keyCode = GetCGKeyCode(javaKeyCode);
+    AXUIElementRef elem = AXUIElementCreateSystemWide();
+    AXUIElementPostKeyboardEvent(elem, (CGCharCode)0, keyCode, keyPressed);
+    CFRelease(elem);
+
+
+#if 0
+    CGEventRef event = CGEventCreateKeyboardEvent(NULL, keyCode, keyPressed);
+    if (event != NULL) {
+        CGEventPost(kCGSessionEventTap, event);
+        CFRelease(event);
+    }
+#endif
+}
+
+/*
+ * Class:     sun_lwawt_macosx_CRobot
+ * Method:    nativeGetScreenPixels
+ * Signature: (IIIII[I)V
+ */
+JNIEXPORT void JNICALL
+Java_sun_lwawt_macosx_CRobot_nativeGetScreenPixels
+(JNIEnv *env, jobject peer,
+ jint x, jint y, jint width, jint height, jintArray pixels)
+{
+    JNF_COCOA_ENTER(env);
+
+    jint picX = x;
+    jint picY = y;
+    jint picWidth = width;
+    jint picHeight = height;
+
+    CGRect screenRect = CGRectMake(picX, picY, picWidth, picHeight);
+    CGImageRef screenPixelsImage = CGWindowListCreateImage(screenRect,
+                                        kCGWindowListOptionOnScreenOnly,
+                                        kCGNullWindowID, kCGWindowImageDefault);
+
+    if (screenPixelsImage == NULL) {
+        return;
+    }
+
+    // get a pointer to the Java int array
+    void *jPixelData = (*env)->GetPrimitiveArrayCritical(env, pixels, 0);
+    CHECK_NULL(jPixelData);
+
+    // create a graphics context around the Java int array
+    CGColorSpaceRef picColorSpace = CGColorSpaceCreateWithName(
+                                            kCGColorSpaceGenericRGB);
+    CGContextRef jPicContextRef = CGBitmapContextCreate(
+                                            jPixelData,
+                                            picWidth, picHeight,
+                                            8, picWidth * sizeof(jint),
+                                            picColorSpace,
+                                            kCGBitmapByteOrder32Host |
+                                            kCGImageAlphaPremultipliedFirst);
+
+    CGColorSpaceRelease(picColorSpace);
+
+    // flip, scale, and color correct the screen image into the Java pixels
+    CGRect bounds = { { 0, 0 }, { picWidth, picHeight } };
+    CGContextDrawImage(jPicContextRef, bounds, screenPixelsImage);
+    CGContextFlush(jPicContextRef);
+
+    // cleanup
+    CGContextRelease(jPicContextRef);
+    CGImageRelease(screenPixelsImage);
+
+    // release the Java int array back up to the JVM
+    (*env)->ReleasePrimitiveArrayCritical(env, pixels, jPixelData, 0);
+
+    JNF_COCOA_EXIT(env);
+}
+
+/****************************************************
+ * Helper methods
+ ****************************************************/
+
+static void PostMouseEvent(const CGPoint point, CGMouseButton button,
+                           CGEventType type, int clickCount, int eventNumber)
+{
+    CGEventRef mouseEvent = CGEventCreateMouseEvent(NULL, type, point, button);
+    if (mouseEvent != NULL) {
+        CGEventSetIntegerValueField(mouseEvent, kCGMouseEventClickState, clickCount);
+        CGEventSetIntegerValueField(mouseEvent, kCGMouseEventNumber, eventNumber);
+        CGEventPost(kCGSessionEventTap, mouseEvent);
+        CFRelease(mouseEvent);
+    }
+}
+
+// NOTE: Don't modify this table directly. It is machine generated. See below.
+static const unsigned char javaToMacKeyCode[] = {
+    127,    //     0     0 VK_UNDEFINED                      No_Equivalent
+    127,    //     1   0x1 Not_Used
+    127,    //     2   0x2 Not_Used
+    127,    //     3   0x3 VK_CANCEL                         No_Equivalent
+    127,    //     4   0x4 Not_Used
+    127,    //     5   0x5 Not_Used
+    127,    //     6   0x6 Not_Used
+    127,    //     7   0x7 Not_Used
+     51,    //     8   0x8 VK_BACK_SPACE
+     48,    //     9   0x9 VK_TAB
+     36,    //    10   0xa VK_ENTER
+    127,    //    11   0xb Not_Used
+     71,    //    12   0xc VK_CLEAR
+    127,    //    13   0xd Not_Used
+    127,    //    14   0xe Not_Used
+    127,    //    15   0xf Not_Used
+     56,    //    16  0x10 VK_SHIFT
+     59,    //    17  0x11 VK_CONTROL
+     58,    //    18  0x12 VK_ALT
+    113,    //    19  0x13 VK_PAUSE
+     57,    //    20  0x14 VK_CAPS_LOCK
+    127,    //    21  0x15 VK_KANA                           No_Equivalent
+    127,    //    22  0x16 Not_Used
+    127,    //    23  0x17 Not_Used
+    127,    //    24  0x18 VK_FINAL                          No_Equivalent
+    127,    //    25  0x19 VK_KANJI                          No_Equivalent
+    127,    //    26  0x1a Not_Used
+     53,    //    27  0x1b VK_ESCAPE
+    127,    //    28  0x1c VK_CONVERT                        No_Equivalent
+    127,    //    29  0x1d VK_NONCONVERT                     No_Equivalent
+    127,    //    30  0x1e VK_ACCEPT                         No_Equivalent
+    127,    //    31  0x1f VK_MODECHANGE                     No_Equivalent
+     49,    //    32  0x20 VK_SPACE
+    116,    //    33  0x21 VK_PAGE_UP
+    121,    //    34  0x22 VK_PAGE_DOWN
+    119,    //    35  0x23 VK_END
+    115,    //    36  0x24 VK_HOME
+    123,    //    37  0x25 VK_LEFT
+    126,    //    38  0x26 VK_UP
+    124,    //    39  0x27 VK_RIGHT
+    125,    //    40  0x28 VK_DOWN
+    127,    //    41  0x29 Not_Used
+    127,    //    42  0x2a Not_Used
+    127,    //    43  0x2b Not_Used
+     43,    //    44  0x2c VK_COMMA
+     27,    //    45  0x2d VK_MINUS
+     47,    //    46  0x2e VK_PERIOD
+     44,    //    47  0x2f VK_SLASH
+     29,    //    48  0x30 VK_0
+     18,    //    49  0x31 VK_1
+     19,    //    50  0x32 VK_2
+     20,    //    51  0x33 VK_3
+     21,    //    52  0x34 VK_4
+     23,    //    53  0x35 VK_5
+     22,    //    54  0x36 VK_6
+     26,    //    55  0x37 VK_7
+     28,    //    56  0x38 VK_8
+     25,    //    57  0x39 VK_9
+    127,    //    58  0x3a Not_Used
+     41,    //    59  0x3b VK_SEMICOLON
+    127,    //    60  0x3c Not_Used
+     24,    //    61  0x3d VK_EQUALS
+    127,    //    62  0x3e Not_Used
+    127,    //    63  0x3f Not_Used
+    127,    //    64  0x40 Not_Used
+      0,    //    65  0x41 VK_A
+     11,    //    66  0x42 VK_B
+      8,    //    67  0x43 VK_C
+      2,    //    68  0x44 VK_D
+     14,    //    69  0x45 VK_E
+      3,    //    70  0x46 VK_F
+      5,    //    71  0x47 VK_G
+      4,    //    72  0x48 VK_H
+     34,    //    73  0x49 VK_I
+     38,    //    74  0x4a VK_J
+     40,    //    75  0x4b VK_K
+     37,    //    76  0x4c VK_L
+     46,    //    77  0x4d VK_M
+     45,    //    78  0x4e VK_N
+     31,    //    79  0x4f VK_O
+     35,    //    80  0x50 VK_P
+     12,    //    81  0x51 VK_Q
+     15,    //    82  0x52 VK_R
+      1,    //    83  0x53 VK_S
+     17,    //    84  0x54 VK_T
+     32,    //    85  0x55 VK_U
+      9,    //    86  0x56 VK_V
+     13,    //    87  0x57 VK_W
+      7,    //    88  0x58 VK_X
+     16,    //    89  0x59 VK_Y
+      6,    //    90  0x5a VK_Z
+     33,    //    91  0x5b VK_OPEN_BRACKET
+     42,    //    92  0x5c VK_BACK_SLASH
+     30,    //    93  0x5d VK_CLOSE_BRACKET
+    127,    //    94  0x5e Not_Used
+    127,    //    95  0x5f Not_Used
+     82,    //    96  0x60 VK_NUMPAD0
+     83,    //    97  0x61 VK_NUMPAD1
+     84,    //    98  0x62 VK_NUMPAD2
+     85,    //    99  0x63 VK_NUMPAD3
+     86,    //   100  0x64 VK_NUMPAD4
+     87,    //   101  0x65 VK_NUMPAD5
+     88,    //   102  0x66 VK_NUMPAD6
+     89,    //   103  0x67 VK_NUMPAD7
+     91,    //   104  0x68 VK_NUMPAD8
+     92,    //   105  0x69 VK_NUMPAD9
+     67,    //   106  0x6a VK_MULTIPLY
+     69,    //   107  0x6b VK_ADD
+    127,    //   108  0x6c VK_SEPARATER                      No_Equivalent
+     78,    //   109  0x6d VK_SUBTRACT
+     65,    //   110  0x6e VK_DECIMAL
+     75,    //   111  0x6f VK_DIVIDE
+    122,    //   112  0x70 VK_F1
+    120,    //   113  0x71 VK_F2
+     99,    //   114  0x72 VK_F3
+    118,    //   115  0x73 VK_F4
+     96,    //   116  0x74 VK_F5
+     97,    //   117  0x75 VK_F6
+     98,    //   118  0x76 VK_F7
+    100,    //   119  0x77 VK_F8
+    101,    //   120  0x78 VK_F9
+    109,    //   121  0x79 VK_F10
+    103,    //   122  0x7a VK_F11
+    111,    //   123  0x7b VK_F12
+    127,    //   124  0x7c Not_Used
+    127,    //   125  0x7d Not_Used
+    127,    //   126  0x7e Not_Used
+    117,    //   127  0x7f VK_DELETE
+    127,    //   128  0x80 VK_DEAD_GRAVE                     No_Equivalent
+    127,    //   129  0x81 VK_DEAD_ACUTE                     No_Equivalent
+    127,    //   130  0x82 VK_DEAD_CIRCUMFLEX                No_Equivalent
+    127,    //   131  0x83 VK_DEAD_TILDE                     No_Equivalent
+    127,    //   132  0x84 VK_DEAD_MACRON                    No_Equivalent
+    127,    //   133  0x85 VK_DEAD_BREVE                     No_Equivalent
+    127,    //   134  0x86 VK_DEAD_ABOVEDOT                  No_Equivalent
+    127,    //   135  0x87 VK_DEAD_DIAERESIS                 No_Equivalent
+    127,    //   136  0x88 VK_DEAD_ABOVERING                 No_Equivalent
+    127,    //   137  0x89 VK_DEAD_DOUBLEACUTE               No_Equivalent
+    127,    //   138  0x8a VK_DEAD_CARON                     No_Equivalent
+    127,    //   139  0x8b VK_DEAD_CEDILLA                   No_Equivalent
+    127,    //   140  0x8c VK_DEAD_OGONEK                    No_Equivalent
+    127,    //   141  0x8d VK_DEAD_IOTA                      No_Equivalent
+    127,    //   142  0x8e VK_DEAD_VOICED_SOUND              No_Equivalent
+    127,    //   143  0x8f VK_DEAD_SEMIVOICED_SOUND          No_Equivalent
+    127,    //   144  0x90 VK_NUM_LOCK                       No_Equivalent
+    107,    //   145  0x91 VK_SCROLL_LOCK
+    127,    //   146  0x92 Not_Used
+    127,    //   147  0x93 Not_Used
+    127,    //   148  0x94 Not_Used
+    127,    //   149  0x95 Not_Used
+    127,    //   150  0x96 VK_AMPERSAND                      No_Equivalent
+    127,    //   151  0x97 VK_ASTERISK                       No_Equivalent
+    127,    //   152  0x98 VK_QUOTEDBL                       No_Equivalent
+    127,    //   153  0x99 VK_LESS                           No_Equivalent
+    105,    //   154  0x9a VK_PRINTSCREEN
+    127,    //   155  0x9b VK_INSERT                         No_Equivalent
+    114,    //   156  0x9c VK_HELP
+     55,    //   157  0x9d VK_META
+    127,    //   158  0x9e Not_Used
+    127,    //   159  0x9f Not_Used
+    127,    //   160  0xa0 VK_GREATER                        No_Equivalent
+    127,    //   161  0xa1 VK_BRACELEFT                      No_Equivalent
+    127,    //   162  0xa2 VK_BRACERIGHT                     No_Equivalent
+    127,    //   163  0xa3 Not_Used
+    127,    //   164  0xa4 Not_Used
+    127,    //   165  0xa5 Not_Used
+    127,    //   166  0xa6 Not_Used
+    127,    //   167  0xa7 Not_Used
+    127,    //   168  0xa8 Not_Used
+    127,    //   169  0xa9 Not_Used
+    127,    //   170  0xaa Not_Used
+    127,    //   171  0xab Not_Used
+    127,    //   172  0xac Not_Used
+    127,    //   173  0xad Not_Used
+    127,    //   174  0xae Not_Used
+    127,    //   175  0xaf Not_Used
+    127,    //   176  0xb0 Not_Used
+    127,    //   177  0xb1 Not_Used
+    127,    //   178  0xb2 Not_Used
+    127,    //   179  0xb3 Not_Used
+    127,    //   180  0xb4 Not_Used
+    127,    //   181  0xb5 Not_Used
+    127,    //   182  0xb6 Not_Used
+    127,    //   183  0xb7 Not_Used
+    127,    //   184  0xb8 Not_Used
+    127,    //   185  0xb9 Not_Used
+    127,    //   186  0xba Not_Used
+    127,    //   187  0xbb Not_Used
+    127,    //   188  0xbc Not_Used
+    127,    //   189  0xbd Not_Used
+    127,    //   190  0xbe Not_Used
+    127,    //   191  0xbf Not_Used
+     50,    //   192  0xc0 VK_BACK_QUOTE
+    127,    //   193  0xc1 Not_Used
+    127,    //   194  0xc2 Not_Used
+    127,    //   195  0xc3 Not_Used
+    127,    //   196  0xc4 Not_Used
+    127,    //   197  0xc5 Not_Used
+    127,    //   198  0xc6 Not_Used
+    127,    //   199  0xc7 Not_Used
+    127,    //   200  0xc8 Not_Used
+    127,    //   201  0xc9 Not_Used
+    127,    //   202  0xca Not_Used
+    127,    //   203  0xcb Not_Used
+    127,    //   204  0xcc Not_Used
+    127,    //   205  0xcd Not_Used
+    127,    //   206  0xce Not_Used
+    127,    //   207  0xcf Not_Used
+    127,    //   208  0xd0 Not_Used
+    127,    //   209  0xd1 Not_Used
+    127,    //   210  0xd2 Not_Used
+    127,    //   211  0xd3 Not_Used
+    127,    //   212  0xd4 Not_Used
+    127,    //   213  0xd5 Not_Used
+    127,    //   214  0xd6 Not_Used
+    127,    //   215  0xd7 Not_Used
+    127,    //   216  0xd8 Not_Used
+    127,    //   217  0xd9 Not_Used
+    127,    //   218  0xda Not_Used
+    127,    //   219  0xdb Not_Used
+    127,    //   220  0xdc Not_Used
+    127,    //   221  0xdd Not_Used
+     39     //   222  0xde VK_QUOTE
+};
+
+// NOTE: All values above 222 don't have an equivalent on MacOSX.
+static inline CGKeyCode GetCGKeyCode(jint javaKeyCode)
+{
+    if (javaKeyCode > 222) {
+        return 127;
+    } else {
+        return javaToMacKeyCode[javaKeyCode];
+    }
+}
+
+static int GetClickCount(BOOL isDown) {
+    NSTimeInterval now = [[NSDate date] timeIntervalSinceReferenceDate];
+    NSTimeInterval clickInterval = now - gsLastClickTime;
+    BOOL isWithinTreshold = clickInterval < [NSEvent doubleClickInterval];
+
+    if (isDown) {
+        if (isWithinTreshold) {
+            gsClickCount++;
+        } else {
+            gsClickCount = 1;
+        }
+
+        gsLastClickTime = now;
+    } else {
+        // In OS X, a mouse up has the click count of the last mouse down
+        // if an interval between up and down is within the double click
+        // threshold, and 0 otherwise.
+        if (!isWithinTreshold) {
+            gsClickCount = 0;
+        }
+    }
+
+    return gsClickCount;
+}