1 /*
   2  * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 #import <JavaNativeFoundation/JavaNativeFoundation.h>
  27 #import <ApplicationServices/ApplicationServices.h>
  28 
  29 #import "LWCToolkit.h"
  30 #import "sun_lwawt_macosx_CRobot.h"
  31 #import "java_awt_event_InputEvent.h"
  32 
  33 
  34 // Starting number for event numbers generated by Robot.
  35 // Apple docs don't mention at all what are the requirements
  36 // for these numbers. It seems that they must be higher
  37 // than event numbers from real events, which start at some
  38 // value close to zero. There is no API for obtaining current
  39 // event number, so we have to start from some random number.
  40 // 32000 as starting value works for me, let's hope that it will
  41 // work for others as well.
  42 #define ROBOT_EVENT_NUMBER_START 32000
  43 
  44 #define k_JAVA_ROBOT_WHEEL_COUNT 1
  45 
  46 #if !defined(kCGBitmapByteOrder32Host)
  47 #define kCGBitmapByteOrder32Host 0
  48 #endif
  49 
  50 // In OS X, left and right mouse button share the same click count.
  51 // That is, if one starts clicking the left button rapidly and then
  52 // switches to the right button, then the click count will continue
  53 // increasing, without dropping to 1 in between. The middle button,
  54 // however, has its own click count.
  55 // For robot, we aren't going to emulate all that complexity. All our
  56 // synhtetic clicks share the same click count.
  57 static int gsClickCount;
  58 static NSTimeInterval gsLastClickTime;
  59 
  60 // Apparently, for mouse up/down events we have to set an event number
  61 // that is incremented on each button press. Otherwise, strange things
  62 // happen with z-order.
  63 static int gsEventNumber;
  64 static int* gsButtonEventNumber;
  65 
  66 static inline CGKeyCode GetCGKeyCode(jint javaKeyCode);
  67 
  68 static void PostMouseEvent(const CGPoint point, CGMouseButton button,
  69                            CGEventType type, int clickCount, int eventNumber);
  70 
  71 static int GetClickCount(BOOL isDown);
  72 
  73 static void
  74 CreateJavaException(JNIEnv* env, CGError err)
  75 {
  76     // Throw a java exception indicating what is wrong.
  77     NSString* s = [NSString stringWithFormat:@"Robot: CGError: %d", err];
  78     (*env)->ThrowNew(env, (*env)->FindClass(env, "java/awt/AWTException"),
  79                      [s UTF8String]);
  80 }
  81 
  82 /*
  83  * Class:     sun_lwawt_macosx_CRobot
  84  * Method:    initRobot
  85  * Signature: (V)V
  86  */
  87 JNIEXPORT void JNICALL
  88 Java_sun_lwawt_macosx_CRobot_initRobot
  89 (JNIEnv *env, jobject peer)
  90 {
  91     // Set things up to let our app act like a synthetic keyboard and mouse.
  92     // Always set all states, in case Apple ever changes default behaviors.
  93     static int setupDone = 0;
  94     if (!setupDone) {
  95         int i;
  96         jint* tmp;
  97         jboolean copy = JNI_FALSE;
  98 
  99         setupDone = 1;
 100         // Don't block local events after posting ours
 101         CGSetLocalEventsSuppressionInterval(0.0);
 102 
 103         // Let our event's modifier key state blend with local hardware events
 104         CGEnableEventStateCombining(TRUE);
 105 
 106         // Don't let our events block local hardware events
 107         CGSetLocalEventsFilterDuringSupressionState(
 108                                     kCGEventFilterMaskPermitAllEvents,
 109                                     kCGEventSupressionStateSupressionInterval);
 110         CGSetLocalEventsFilterDuringSupressionState(
 111                                     kCGEventFilterMaskPermitAllEvents,
 112                                     kCGEventSupressionStateRemoteMouseDrag);
 113 
 114         gsClickCount = 0;
 115         gsLastClickTime = 0;
 116         gsEventNumber = ROBOT_EVENT_NUMBER_START;
 117 
 118         gsButtonEventNumber = (int*)malloc(sizeof(int) * gNumberOfButtons);
 119         if (gsButtonEventNumber == NULL) {
 120             JNU_ThrowOutOfMemoryError(env, NULL);
 121             return;
 122         }
 123 
 124         for (i = 0; i < gNumberOfButtons; ++i) {
 125             gsButtonEventNumber[i] = ROBOT_EVENT_NUMBER_START;
 126         }
 127     }
 128 }
 129 
 130 /*
 131  * Class:     sun_lwawt_macosx_CRobot
 132  * Method:    mouseEvent
 133  * Signature: (IIIIZZ)V
 134  */
 135 JNIEXPORT void JNICALL
 136 Java_sun_lwawt_macosx_CRobot_mouseEvent
 137 (JNIEnv *env, jobject peer,
 138  jint screenIndex, jint mouseLastX, jint mouseLastY, jint buttonsState,
 139  jboolean isButtonsDownState, jboolean isMouseMove)
 140 {
 141     JNF_COCOA_ENTER(env);
 142 
 143     // This is the native method called when Robot mouse events occur.
 144     // The CRobot tracks the mouse position, and which button was
 145     // pressed. If the mouse position is unknown it is obtained from
 146     // CGEvents. The peer also tracks the mouse button desired state,
 147     // the appropriate key modifier state, and whether the mouse action
 148     // is simply a mouse move with no mouse button state changes.
 149 
 150     CGError err = kCGErrorSuccess;
 151 
 152     CGDirectDisplayID displayID =
 153     FindCGDirectDisplayIDForScreenIndex(screenIndex);
 154     CGRect globalDeviceBounds = CGDisplayBounds(displayID);
 155 
 156     // Set unknown mouse location, if needed.
 157     if ((mouseLastX == sun_lwawt_macosx_CRobot_MOUSE_LOCATION_UNKNOWN) ||
 158         (mouseLastY == sun_lwawt_macosx_CRobot_MOUSE_LOCATION_UNKNOWN))
 159     {
 160         CGEventRef event = CGEventCreate(NULL);
 161         if (event == NULL) {
 162             return;
 163         }
 164 
 165         CGPoint globalPos = CGEventGetLocation(event);
 166         CFRelease(event);
 167 
 168         // Normalize the coords within this display device, as
 169         // per Robot rules.
 170         if (globalPos.x < CGRectGetMinX(globalDeviceBounds)) {
 171             globalPos.x = CGRectGetMinX(globalDeviceBounds);
 172         }
 173         else if (globalPos.x > CGRectGetMaxX(globalDeviceBounds)) {
 174             globalPos.x = CGRectGetMaxX(globalDeviceBounds);
 175         }
 176 
 177         if (globalPos.y < CGRectGetMinY(globalDeviceBounds)) {
 178             globalPos.y = CGRectGetMinY(globalDeviceBounds);
 179         }
 180         else if (globalPos.y > CGRectGetMaxY(globalDeviceBounds)) {
 181             globalPos.y = CGRectGetMaxY(globalDeviceBounds);
 182         }
 183 
 184         mouseLastX = (jint)globalPos.x;
 185         mouseLastY = (jint)globalPos.y;
 186     }
 187 
 188     // volatile, otherwise it warns that it might be clobbered by 'longjmp'
 189     volatile CGPoint point;
 190 
 191     // Translate the device relative point into a valid global CGPoint.
 192     point.x = mouseLastX + globalDeviceBounds.origin.x;
 193     point.y = mouseLastY + globalDeviceBounds.origin.y;
 194 
 195     __block CGMouseButton button = kCGMouseButtonLeft;
 196     __block CGEventType type = kCGEventMouseMoved;
 197 
 198     void (^HandleRobotButton)(CGMouseButton, CGEventType, CGEventType, CGEventType) =
 199         ^(CGMouseButton cgButton, CGEventType cgButtonUp, CGEventType cgButtonDown,
 200           CGEventType cgButtonDragged) {
 201 
 202             button = cgButton;
 203             type = cgButtonUp;
 204 
 205             if (isButtonsDownState) {
 206                 if (isMouseMove) {
 207                     type = cgButtonDragged;
 208                 } else {
 209                     type = cgButtonDown;
 210                 }
 211             }
 212         };
 213 
 214     // Left
 215     if (buttonsState & java_awt_event_InputEvent_BUTTON1_MASK ||
 216         buttonsState & java_awt_event_InputEvent_BUTTON1_DOWN_MASK ) {
 217 
 218         HandleRobotButton(kCGMouseButtonLeft, kCGEventLeftMouseUp,
 219                           kCGEventLeftMouseDown, kCGEventLeftMouseDragged);
 220     }
 221 
 222     // Other
 223     if (buttonsState & java_awt_event_InputEvent_BUTTON2_MASK ||
 224         buttonsState & java_awt_event_InputEvent_BUTTON2_DOWN_MASK ) {
 225 
 226         HandleRobotButton(kCGMouseButtonCenter, kCGEventOtherMouseUp,
 227                           kCGEventOtherMouseDown, kCGEventOtherMouseDragged);
 228     }
 229 
 230     // Right
 231     if (buttonsState & java_awt_event_InputEvent_BUTTON3_MASK ||
 232         buttonsState & java_awt_event_InputEvent_BUTTON3_DOWN_MASK ) {
 233 
 234         HandleRobotButton(kCGMouseButtonRight, kCGEventRightMouseUp,
 235                           kCGEventRightMouseDown, kCGEventRightMouseDragged);
 236     }
 237 
 238     // Extra
 239     if (gNumberOfButtons > 3) {
 240         int extraButton;
 241         for (extraButton = 3; extraButton < gNumberOfButtons; ++extraButton) {
 242             if ((buttonsState & gButtonDownMasks[extraButton])) {
 243                 HandleRobotButton(extraButton, kCGEventOtherMouseUp,
 244                             kCGEventOtherMouseDown, kCGEventOtherMouseDragged);
 245             }
 246         }
 247     }
 248 
 249     int clickCount = 0;
 250     int eventNumber = gsEventNumber;
 251 
 252     if (isMouseMove) {
 253         // any mouse movement resets click count
 254         gsLastClickTime = 0;
 255     } else {
 256         clickCount = GetClickCount(isButtonsDownState);
 257 
 258         if (isButtonsDownState) {
 259             gsButtonEventNumber[button] = gsEventNumber++;
 260         }
 261         eventNumber = gsButtonEventNumber[button];
 262     }
 263 
 264     PostMouseEvent(point, button, type, clickCount, eventNumber);
 265 
 266     JNF_COCOA_EXIT(env);
 267 }
 268 
 269 /*
 270  * Class:     sun_lwawt_macosx_CRobot
 271  * Method:    mouseWheel
 272  * Signature: (I)V
 273  */
 274 JNIEXPORT void JNICALL
 275 Java_sun_lwawt_macosx_CRobot_mouseWheel
 276 (JNIEnv *env, jobject peer, jint wheelAmt)
 277 {
 278     CGEventRef event = CGEventCreateScrollWheelEvent(NULL,
 279                                             kCGScrollEventUnitLine,
 280                                             k_JAVA_ROBOT_WHEEL_COUNT, wheelAmt);
 281 
 282     if (event != NULL) {
 283         CGEventPost(kCGSessionEventTap, event);
 284         CFRelease(event);
 285     }
 286 }
 287 
 288 /*
 289  * Class:     sun_lwawt_macosx_CRobot
 290  * Method:    keyEvent
 291  * Signature: (IZ)V
 292  */
 293 JNIEXPORT void JNICALL
 294 Java_sun_lwawt_macosx_CRobot_keyEvent
 295 (JNIEnv *env, jobject peer, jint javaKeyCode, jboolean keyPressed)
 296 {
 297     /*
 298      * Well, using CGEventCreateKeyboardEvent/CGEventPost would have been
 299      * a better solution, however, it gives me all kinds of trouble and I have
 300      * no idea how to solve them without inserting delays between simulated
 301      * events. So, I've ended up disabling it and opted for another approach
 302      * that uses Accessibility API instead.
 303      */
 304     CGKeyCode keyCode = GetCGKeyCode(javaKeyCode);
 305     AXUIElementRef elem = AXUIElementCreateSystemWide();
 306     AXUIElementPostKeyboardEvent(elem, (CGCharCode)0, keyCode, keyPressed);
 307     CFRelease(elem);
 308 
 309 
 310 #if 0
 311     CGEventRef event = CGEventCreateKeyboardEvent(NULL, keyCode, keyPressed);
 312     if (event != NULL) {
 313         CGEventPost(kCGSessionEventTap, event);
 314         CFRelease(event);
 315     }
 316 #endif
 317 }
 318 
 319 /*
 320  * Class:     sun_lwawt_macosx_CRobot
 321  * Method:    nativeGetScreenPixels
 322  * Signature: (IIIII[I)V
 323  */
 324 JNIEXPORT void JNICALL
 325 Java_sun_lwawt_macosx_CRobot_nativeGetScreenPixels
 326 (JNIEnv *env, jobject peer,
 327  jint x, jint y, jint width, jint height, jintArray pixels)
 328 {
 329     JNF_COCOA_ENTER(env);
 330 
 331     jint picX = x;
 332     jint picY = y;
 333     jint picWidth = width;
 334     jint picHeight = height;
 335 
 336     CGRect screenRect = CGRectMake(picX, picY, picWidth, picHeight);
 337     CGImageRef screenPixelsImage = CGWindowListCreateImage(screenRect,
 338                                         kCGWindowListOptionOnScreenOnly,
 339                                         kCGNullWindowID, kCGWindowImageDefault);
 340 
 341     if (screenPixelsImage == NULL) {
 342         return;
 343     }
 344 
 345     // get a pointer to the Java int array
 346     void *jPixelData = (*env)->GetPrimitiveArrayCritical(env, pixels, 0);
 347 
 348     // create a graphics context around the Java int array
 349     CGColorSpaceRef picColorSpace = CGColorSpaceCreateWithName(
 350                                             kCGColorSpaceGenericRGB);
 351     CGContextRef jPicContextRef = CGBitmapContextCreate(
 352                                             jPixelData,
 353                                             picWidth, picHeight,
 354                                             8, picWidth * sizeof(jint),
 355                                             picColorSpace,
 356                                             kCGBitmapByteOrder32Host |
 357                                             kCGImageAlphaPremultipliedFirst);
 358 
 359     CGColorSpaceRelease(picColorSpace);
 360 
 361     // flip, scale, and color correct the screen image into the Java pixels
 362     CGRect bounds = { { 0, 0 }, { picWidth, picHeight } };
 363     CGContextDrawImage(jPicContextRef, bounds, screenPixelsImage);
 364     CGContextFlush(jPicContextRef);
 365 
 366     // cleanup
 367     CGContextRelease(jPicContextRef);
 368     CGImageRelease(screenPixelsImage);
 369 
 370     // release the Java int array back up to the JVM
 371     (*env)->ReleasePrimitiveArrayCritical(env, pixels, jPixelData, 0);
 372 
 373     JNF_COCOA_EXIT(env);
 374 }
 375 
 376 /****************************************************
 377  * Helper methods
 378  ****************************************************/
 379 
 380 static void PostMouseEvent(const CGPoint point, CGMouseButton button,
 381                            CGEventType type, int clickCount, int eventNumber)
 382 {
 383     CGEventRef mouseEvent = CGEventCreateMouseEvent(NULL, type, point, button);
 384     if (mouseEvent != NULL) {
 385         CGEventSetIntegerValueField(mouseEvent, kCGMouseEventClickState, clickCount);
 386         CGEventSetIntegerValueField(mouseEvent, kCGMouseEventNumber, eventNumber);
 387         CGEventPost(kCGSessionEventTap, mouseEvent);
 388         CFRelease(mouseEvent);
 389     }
 390 }
 391 
 392 // NOTE: Don't modify this table directly. It is machine generated. See below.
 393 static const unsigned char javaToMacKeyCode[] = {
 394     127,    //     0     0 VK_UNDEFINED                      No_Equivalent
 395     127,    //     1   0x1 Not_Used
 396     127,    //     2   0x2 Not_Used
 397     127,    //     3   0x3 VK_CANCEL                         No_Equivalent
 398     127,    //     4   0x4 Not_Used
 399     127,    //     5   0x5 Not_Used
 400     127,    //     6   0x6 Not_Used
 401     127,    //     7   0x7 Not_Used
 402      51,    //     8   0x8 VK_BACK_SPACE
 403      48,    //     9   0x9 VK_TAB
 404      36,    //    10   0xa VK_ENTER
 405     127,    //    11   0xb Not_Used
 406      71,    //    12   0xc VK_CLEAR
 407     127,    //    13   0xd Not_Used
 408     127,    //    14   0xe Not_Used
 409     127,    //    15   0xf Not_Used
 410      56,    //    16  0x10 VK_SHIFT
 411      59,    //    17  0x11 VK_CONTROL
 412      58,    //    18  0x12 VK_ALT
 413     113,    //    19  0x13 VK_PAUSE
 414      57,    //    20  0x14 VK_CAPS_LOCK
 415     127,    //    21  0x15 VK_KANA                           No_Equivalent
 416     127,    //    22  0x16 Not_Used
 417     127,    //    23  0x17 Not_Used
 418     127,    //    24  0x18 VK_FINAL                          No_Equivalent
 419     127,    //    25  0x19 VK_KANJI                          No_Equivalent
 420     127,    //    26  0x1a Not_Used
 421      53,    //    27  0x1b VK_ESCAPE
 422     127,    //    28  0x1c VK_CONVERT                        No_Equivalent
 423     127,    //    29  0x1d VK_NONCONVERT                     No_Equivalent
 424     127,    //    30  0x1e VK_ACCEPT                         No_Equivalent
 425     127,    //    31  0x1f VK_MODECHANGE                     No_Equivalent
 426      49,    //    32  0x20 VK_SPACE
 427     116,    //    33  0x21 VK_PAGE_UP
 428     121,    //    34  0x22 VK_PAGE_DOWN
 429     119,    //    35  0x23 VK_END
 430     115,    //    36  0x24 VK_HOME
 431     123,    //    37  0x25 VK_LEFT
 432     126,    //    38  0x26 VK_UP
 433     124,    //    39  0x27 VK_RIGHT
 434     125,    //    40  0x28 VK_DOWN
 435     127,    //    41  0x29 Not_Used
 436     127,    //    42  0x2a Not_Used
 437     127,    //    43  0x2b Not_Used
 438      43,    //    44  0x2c VK_COMMA
 439      27,    //    45  0x2d VK_MINUS
 440      47,    //    46  0x2e VK_PERIOD
 441      44,    //    47  0x2f VK_SLASH
 442      29,    //    48  0x30 VK_0
 443      18,    //    49  0x31 VK_1
 444      19,    //    50  0x32 VK_2
 445      20,    //    51  0x33 VK_3
 446      21,    //    52  0x34 VK_4
 447      23,    //    53  0x35 VK_5
 448      22,    //    54  0x36 VK_6
 449      26,    //    55  0x37 VK_7
 450      28,    //    56  0x38 VK_8
 451      25,    //    57  0x39 VK_9
 452     127,    //    58  0x3a Not_Used
 453      41,    //    59  0x3b VK_SEMICOLON
 454     127,    //    60  0x3c Not_Used
 455      24,    //    61  0x3d VK_EQUALS
 456     127,    //    62  0x3e Not_Used
 457     127,    //    63  0x3f Not_Used
 458     127,    //    64  0x40 Not_Used
 459       0,    //    65  0x41 VK_A
 460      11,    //    66  0x42 VK_B
 461       8,    //    67  0x43 VK_C
 462       2,    //    68  0x44 VK_D
 463      14,    //    69  0x45 VK_E
 464       3,    //    70  0x46 VK_F
 465       5,    //    71  0x47 VK_G
 466       4,    //    72  0x48 VK_H
 467      34,    //    73  0x49 VK_I
 468      38,    //    74  0x4a VK_J
 469      40,    //    75  0x4b VK_K
 470      37,    //    76  0x4c VK_L
 471      46,    //    77  0x4d VK_M
 472      45,    //    78  0x4e VK_N
 473      31,    //    79  0x4f VK_O
 474      35,    //    80  0x50 VK_P
 475      12,    //    81  0x51 VK_Q
 476      15,    //    82  0x52 VK_R
 477       1,    //    83  0x53 VK_S
 478      17,    //    84  0x54 VK_T
 479      32,    //    85  0x55 VK_U
 480       9,    //    86  0x56 VK_V
 481      13,    //    87  0x57 VK_W
 482       7,    //    88  0x58 VK_X
 483      16,    //    89  0x59 VK_Y
 484       6,    //    90  0x5a VK_Z
 485      33,    //    91  0x5b VK_OPEN_BRACKET
 486      42,    //    92  0x5c VK_BACK_SLASH
 487      30,    //    93  0x5d VK_CLOSE_BRACKET
 488     127,    //    94  0x5e Not_Used
 489     127,    //    95  0x5f Not_Used
 490      82,    //    96  0x60 VK_NUMPAD0
 491      83,    //    97  0x61 VK_NUMPAD1
 492      84,    //    98  0x62 VK_NUMPAD2
 493      85,    //    99  0x63 VK_NUMPAD3
 494      86,    //   100  0x64 VK_NUMPAD4
 495      87,    //   101  0x65 VK_NUMPAD5
 496      88,    //   102  0x66 VK_NUMPAD6
 497      89,    //   103  0x67 VK_NUMPAD7
 498      91,    //   104  0x68 VK_NUMPAD8
 499      92,    //   105  0x69 VK_NUMPAD9
 500      67,    //   106  0x6a VK_MULTIPLY
 501      69,    //   107  0x6b VK_ADD
 502     127,    //   108  0x6c VK_SEPARATER                      No_Equivalent
 503      78,    //   109  0x6d VK_SUBTRACT
 504      65,    //   110  0x6e VK_DECIMAL
 505      75,    //   111  0x6f VK_DIVIDE
 506     122,    //   112  0x70 VK_F1
 507     120,    //   113  0x71 VK_F2
 508      99,    //   114  0x72 VK_F3
 509     118,    //   115  0x73 VK_F4
 510      96,    //   116  0x74 VK_F5
 511      97,    //   117  0x75 VK_F6
 512      98,    //   118  0x76 VK_F7
 513     100,    //   119  0x77 VK_F8
 514     101,    //   120  0x78 VK_F9
 515     109,    //   121  0x79 VK_F10
 516     103,    //   122  0x7a VK_F11
 517     111,    //   123  0x7b VK_F12
 518     127,    //   124  0x7c Not_Used
 519     127,    //   125  0x7d Not_Used
 520     127,    //   126  0x7e Not_Used
 521     117,    //   127  0x7f VK_DELETE
 522     127,    //   128  0x80 VK_DEAD_GRAVE                     No_Equivalent
 523     127,    //   129  0x81 VK_DEAD_ACUTE                     No_Equivalent
 524     127,    //   130  0x82 VK_DEAD_CIRCUMFLEX                No_Equivalent
 525     127,    //   131  0x83 VK_DEAD_TILDE                     No_Equivalent
 526     127,    //   132  0x84 VK_DEAD_MACRON                    No_Equivalent
 527     127,    //   133  0x85 VK_DEAD_BREVE                     No_Equivalent
 528     127,    //   134  0x86 VK_DEAD_ABOVEDOT                  No_Equivalent
 529     127,    //   135  0x87 VK_DEAD_DIAERESIS                 No_Equivalent
 530     127,    //   136  0x88 VK_DEAD_ABOVERING                 No_Equivalent
 531     127,    //   137  0x89 VK_DEAD_DOUBLEACUTE               No_Equivalent
 532     127,    //   138  0x8a VK_DEAD_CARON                     No_Equivalent
 533     127,    //   139  0x8b VK_DEAD_CEDILLA                   No_Equivalent
 534     127,    //   140  0x8c VK_DEAD_OGONEK                    No_Equivalent
 535     127,    //   141  0x8d VK_DEAD_IOTA                      No_Equivalent
 536     127,    //   142  0x8e VK_DEAD_VOICED_SOUND              No_Equivalent
 537     127,    //   143  0x8f VK_DEAD_SEMIVOICED_SOUND          No_Equivalent
 538     127,    //   144  0x90 VK_NUM_LOCK                       No_Equivalent
 539     107,    //   145  0x91 VK_SCROLL_LOCK
 540     127,    //   146  0x92 Not_Used
 541     127,    //   147  0x93 Not_Used
 542     127,    //   148  0x94 Not_Used
 543     127,    //   149  0x95 Not_Used
 544     127,    //   150  0x96 VK_AMPERSAND                      No_Equivalent
 545     127,    //   151  0x97 VK_ASTERISK                       No_Equivalent
 546     127,    //   152  0x98 VK_QUOTEDBL                       No_Equivalent
 547     127,    //   153  0x99 VK_LESS                           No_Equivalent
 548     105,    //   154  0x9a VK_PRINTSCREEN
 549     127,    //   155  0x9b VK_INSERT                         No_Equivalent
 550     114,    //   156  0x9c VK_HELP
 551      55,    //   157  0x9d VK_META
 552     127,    //   158  0x9e Not_Used
 553     127,    //   159  0x9f Not_Used
 554     127,    //   160  0xa0 VK_GREATER                        No_Equivalent
 555     127,    //   161  0xa1 VK_BRACELEFT                      No_Equivalent
 556     127,    //   162  0xa2 VK_BRACERIGHT                     No_Equivalent
 557     127,    //   163  0xa3 Not_Used
 558     127,    //   164  0xa4 Not_Used
 559     127,    //   165  0xa5 Not_Used
 560     127,    //   166  0xa6 Not_Used
 561     127,    //   167  0xa7 Not_Used
 562     127,    //   168  0xa8 Not_Used
 563     127,    //   169  0xa9 Not_Used
 564     127,    //   170  0xaa Not_Used
 565     127,    //   171  0xab Not_Used
 566     127,    //   172  0xac Not_Used
 567     127,    //   173  0xad Not_Used
 568     127,    //   174  0xae Not_Used
 569     127,    //   175  0xaf Not_Used
 570     127,    //   176  0xb0 Not_Used
 571     127,    //   177  0xb1 Not_Used
 572     127,    //   178  0xb2 Not_Used
 573     127,    //   179  0xb3 Not_Used
 574     127,    //   180  0xb4 Not_Used
 575     127,    //   181  0xb5 Not_Used
 576     127,    //   182  0xb6 Not_Used
 577     127,    //   183  0xb7 Not_Used
 578     127,    //   184  0xb8 Not_Used
 579     127,    //   185  0xb9 Not_Used
 580     127,    //   186  0xba Not_Used
 581     127,    //   187  0xbb Not_Used
 582     127,    //   188  0xbc Not_Used
 583     127,    //   189  0xbd Not_Used
 584     127,    //   190  0xbe Not_Used
 585     127,    //   191  0xbf Not_Used
 586      50,    //   192  0xc0 VK_BACK_QUOTE
 587     127,    //   193  0xc1 Not_Used
 588     127,    //   194  0xc2 Not_Used
 589     127,    //   195  0xc3 Not_Used
 590     127,    //   196  0xc4 Not_Used
 591     127,    //   197  0xc5 Not_Used
 592     127,    //   198  0xc6 Not_Used
 593     127,    //   199  0xc7 Not_Used
 594     127,    //   200  0xc8 Not_Used
 595     127,    //   201  0xc9 Not_Used
 596     127,    //   202  0xca Not_Used
 597     127,    //   203  0xcb Not_Used
 598     127,    //   204  0xcc Not_Used
 599     127,    //   205  0xcd Not_Used
 600     127,    //   206  0xce Not_Used
 601     127,    //   207  0xcf Not_Used
 602     127,    //   208  0xd0 Not_Used
 603     127,    //   209  0xd1 Not_Used
 604     127,    //   210  0xd2 Not_Used
 605     127,    //   211  0xd3 Not_Used
 606     127,    //   212  0xd4 Not_Used
 607     127,    //   213  0xd5 Not_Used
 608     127,    //   214  0xd6 Not_Used
 609     127,    //   215  0xd7 Not_Used
 610     127,    //   216  0xd8 Not_Used
 611     127,    //   217  0xd9 Not_Used
 612     127,    //   218  0xda Not_Used
 613     127,    //   219  0xdb Not_Used
 614     127,    //   220  0xdc Not_Used
 615     127,    //   221  0xdd Not_Used
 616      39     //   222  0xde VK_QUOTE
 617 };
 618 
 619 // NOTE: All values above 222 don't have an equivalent on MacOSX.
 620 static inline CGKeyCode GetCGKeyCode(jint javaKeyCode)
 621 {
 622     if (javaKeyCode > 222) {
 623         return 127;
 624     } else {
 625         return javaToMacKeyCode[javaKeyCode];
 626     }
 627 }
 628 
 629 static int GetClickCount(BOOL isDown) {
 630     NSTimeInterval now = [[NSDate date] timeIntervalSinceReferenceDate];
 631     NSTimeInterval clickInterval = now - gsLastClickTime;
 632     BOOL isWithinTreshold = clickInterval < [NSEvent doubleClickInterval];
 633 
 634     if (isDown) {
 635         if (isWithinTreshold) {
 636             gsClickCount++;
 637         } else {
 638             gsClickCount = 1;
 639         }
 640 
 641         gsLastClickTime = now;
 642     } else {
 643         // In OS X, a mouse up has the click count of the last mouse down
 644         // if an interval between up and down is within the double click
 645         // threshold, and 0 otherwise.
 646         if (!isWithinTreshold) {
 647             gsClickCount = 0;
 648         }
 649     }
 650 
 651     return gsClickCount;
 652 }