1 /* 2 * Copyright (c) 2016, 2017, 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 27 #import "jni_util.h" 28 29 #import <JavaNativeFoundation/JavaNativeFoundation.h> 30 #import <ApplicationServices/ApplicationServices.h> 31 32 #import "CRobotKeyCode.h" 33 #import "LWCToolkit.h" 34 #import "sun_lwawt_macosx_CRobot.h" 35 #import "java_awt_event_InputEvent.h" 36 #import "java_awt_event_KeyEvent.h" 37 #import "sizecalc.h" 38 39 // Starting number for event numbers generated by Robot. 40 // Apple docs don't mention at all what are the requirements 41 // for these numbers. It seems that they must be higher 42 // than event numbers from real events, which start at some 43 // value close to zero. There is no API for obtaining current 44 // event number, so we have to start from some random number. 45 // 32000 as starting value works for me, let's hope that it will 46 // work for others as well. 47 #define ROBOT_EVENT_NUMBER_START 32000 48 49 #define k_JAVA_ROBOT_WHEEL_COUNT 1 50 51 #if !defined(kCGBitmapByteOrder32Host) 52 #define kCGBitmapByteOrder32Host 0 53 #endif 54 55 // In OS X, left and right mouse button share the same click count. 56 // That is, if one starts clicking the left button rapidly and then 57 // switches to the right button, then the click count will continue 58 // increasing, without dropping to 1 in between. The middle button, 59 // however, has its own click count. 60 // For robot, we aren't going to emulate all that complexity. All our 61 // synhtetic clicks share the same click count. 62 static int gsClickCount; 63 static NSTimeInterval gsLastClickTime; 64 65 // Apparently, for mouse up/down events we have to set an event number 66 // that is incremented on each button press. Otherwise, strange things 67 // happen with z-order. 68 static int gsEventNumber; 69 static int* gsButtonEventNumber; 70 71 static inline CGKeyCode GetCGKeyCode(jint javaKeyCode); 72 73 static void PostMouseEvent(const CGPoint point, CGMouseButton button, 74 CGEventType type, int clickCount, int eventNumber); 75 76 static int GetClickCount(BOOL isDown); 77 78 static void 79 CreateJavaException(JNIEnv* env, CGError err) 80 { 81 // Throw a java exception indicating what is wrong. 82 NSString* s = [NSString stringWithFormat:@"Robot: CGError: %d", err]; 83 (*env)->ThrowNew(env, (*env)->FindClass(env, "java/awt/AWTException"), 84 [s UTF8String]); 85 } 86 87 /* 88 * Class: sun_lwawt_macosx_CRobot 89 * Method: initRobot 90 * Signature: (V)V 91 */ 92 JNIEXPORT void JNICALL 93 Java_sun_lwawt_macosx_CRobot_initRobot 94 (JNIEnv *env, jobject peer) 95 { 96 // Set things up to let our app act like a synthetic keyboard and mouse. 97 // Always set all states, in case Apple ever changes default behaviors. 98 static int setupDone = 0; 99 if (!setupDone) { 100 int i; 101 jint* tmp; 102 jboolean copy = JNI_FALSE; 103 104 setupDone = 1; 105 // Don't block local events after posting ours 106 CGSetLocalEventsSuppressionInterval(0.0); 107 108 // Let our event's modifier key state blend with local hardware events 109 CGEnableEventStateCombining(TRUE); 110 111 // Don't let our events block local hardware events 112 CGSetLocalEventsFilterDuringSupressionState( 113 kCGEventFilterMaskPermitAllEvents, 114 kCGEventSupressionStateSupressionInterval); 115 CGSetLocalEventsFilterDuringSupressionState( 116 kCGEventFilterMaskPermitAllEvents, 117 kCGEventSupressionStateRemoteMouseDrag); 118 119 gsClickCount = 0; 120 gsLastClickTime = 0; 121 gsEventNumber = ROBOT_EVENT_NUMBER_START; 122 123 gsButtonEventNumber = (int*)SAFE_SIZE_ARRAY_ALLOC(malloc, sizeof(int), gNumberOfButtons); 124 if (gsButtonEventNumber == NULL) { 125 JNU_ThrowOutOfMemoryError(env, NULL); 126 return; 127 } 128 129 for (i = 0; i < gNumberOfButtons; ++i) { 130 gsButtonEventNumber[i] = ROBOT_EVENT_NUMBER_START; 131 } 132 } 133 } 134 135 /* 136 * Class: sun_lwawt_macosx_CRobot 137 * Method: mouseEvent 138 * Signature: (IIIIZZ)V 139 */ 140 JNIEXPORT void JNICALL 141 Java_sun_lwawt_macosx_CRobot_mouseEvent 142 (JNIEnv *env, jobject peer, 143 jint displayID, jint mouseLastX, jint mouseLastY, jint buttonsState, 144 jboolean isButtonsDownState, jboolean isMouseMove) 145 { 146 JNF_COCOA_ENTER(env); 147 148 // This is the native method called when Robot mouse events occur. 149 // The CRobot tracks the mouse position, and which button was 150 // pressed. The peer also tracks the mouse button desired state, 151 // the appropriate key modifier state, and whether the mouse action 152 // is simply a mouse move with no mouse button state changes. 153 154 // volatile, otherwise it warns that it might be clobbered by 'longjmp' 155 volatile CGPoint point; 156 157 point.x = mouseLastX; 158 point.y = mouseLastY; 159 160 __block CGMouseButton button = kCGMouseButtonLeft; 161 __block CGEventType type = kCGEventMouseMoved; 162 163 void (^HandleRobotButton)(CGMouseButton, CGEventType, CGEventType, CGEventType) = 164 ^(CGMouseButton cgButton, CGEventType cgButtonUp, CGEventType cgButtonDown, 165 CGEventType cgButtonDragged) { 166 167 button = cgButton; 168 type = cgButtonUp; 169 170 if (isButtonsDownState) { 171 if (isMouseMove) { 172 type = cgButtonDragged; 173 } else { 174 type = cgButtonDown; 175 } 176 } 177 }; 178 179 // Left 180 if (buttonsState & java_awt_event_InputEvent_BUTTON1_MASK || 181 buttonsState & java_awt_event_InputEvent_BUTTON1_DOWN_MASK ) { 182 183 HandleRobotButton(kCGMouseButtonLeft, kCGEventLeftMouseUp, 184 kCGEventLeftMouseDown, kCGEventLeftMouseDragged); 185 } 186 187 // Other 188 if (buttonsState & java_awt_event_InputEvent_BUTTON2_MASK || 189 buttonsState & java_awt_event_InputEvent_BUTTON2_DOWN_MASK ) { 190 191 HandleRobotButton(kCGMouseButtonCenter, kCGEventOtherMouseUp, 192 kCGEventOtherMouseDown, kCGEventOtherMouseDragged); 193 } 194 195 // Right 196 if (buttonsState & java_awt_event_InputEvent_BUTTON3_MASK || 197 buttonsState & java_awt_event_InputEvent_BUTTON3_DOWN_MASK ) { 198 199 HandleRobotButton(kCGMouseButtonRight, kCGEventRightMouseUp, 200 kCGEventRightMouseDown, kCGEventRightMouseDragged); 201 } 202 203 // Extra 204 if (gNumberOfButtons > 3) { 205 int extraButton; 206 for (extraButton = 3; extraButton < gNumberOfButtons; ++extraButton) { 207 if ((buttonsState & gButtonDownMasks[extraButton])) { 208 HandleRobotButton(extraButton, kCGEventOtherMouseUp, 209 kCGEventOtherMouseDown, kCGEventOtherMouseDragged); 210 } 211 } 212 } 213 214 int clickCount = 0; 215 int eventNumber = gsEventNumber; 216 217 if (isMouseMove) { 218 // any mouse movement resets click count 219 gsLastClickTime = 0; 220 } else { 221 clickCount = GetClickCount(isButtonsDownState); 222 223 if (isButtonsDownState) { 224 gsButtonEventNumber[button] = gsEventNumber++; 225 } 226 eventNumber = gsButtonEventNumber[button]; 227 } 228 229 PostMouseEvent(point, button, type, clickCount, eventNumber); 230 231 JNF_COCOA_EXIT(env); 232 } 233 234 /* 235 * Class: sun_lwawt_macosx_CRobot 236 * Method: mouseWheel 237 * Signature: (I)V 238 */ 239 JNIEXPORT void JNICALL 240 Java_sun_lwawt_macosx_CRobot_mouseWheel 241 (JNIEnv *env, jobject peer, jint wheelAmt) 242 { 243 CGEventRef event = CGEventCreateScrollWheelEvent(NULL, 244 kCGScrollEventUnitLine, 245 k_JAVA_ROBOT_WHEEL_COUNT, wheelAmt); 246 247 if (event != NULL) { 248 CGEventPost(kCGSessionEventTap, event); 249 CFRelease(event); 250 } 251 } 252 253 /* 254 * Class: sun_lwawt_macosx_CRobot 255 * Method: keyEvent 256 * Signature: (IZ)V 257 */ 258 JNIEXPORT void JNICALL 259 Java_sun_lwawt_macosx_CRobot_keyEvent 260 (JNIEnv *env, jobject peer, jint javaKeyCode, jboolean keyPressed) 261 { 262 CGKeyCode keyCode = GetCGKeyCode(javaKeyCode); 263 CGEventRef event = CGEventCreateKeyboardEvent(NULL, keyCode, keyPressed); 264 265 if (event != NULL) { 266 CGEventPost(kCGSessionEventTap, event); 267 CFRelease(event); 268 } 269 } 270 271 /* 272 * Class: sun_lwawt_macosx_CRobot 273 * Method: keyEventUnicode 274 * Signature: (IZ)V 275 */ 276 JNIEXPORT void JNICALL 277 Java_sun_lwawt_macosx_CRobot_keyEventUnicode 278 (JNIEnv *env, jobject peer, jint key, jboolean keyPressed) 279 { 280 CGEventRef event; 281 UniChar uCh = key; 282 283 // create a null keyboard event 284 event = CGEventCreateKeyboardEvent(NULL, 0, keyPressed); 285 286 if (event != NULL) { 287 /* Note that application frameworks may ignore the Unicode string in a 288 keyboard event and do their own translation based on the 289 virtual keycode and perceived event state. 290 */ 291 CGEventKeyboardSetUnicodeString(event, 1, &uCh); 292 CGEventPost(kCGSessionEventTap, event); 293 CFRelease(event); 294 } 295 } 296 297 /* 298 * Class: sun_lwawt_macosx_CRobot 299 * Method: nativeGetScreenPixels 300 * Signature: (IIIII[I)V 301 */ 302 JNIEXPORT void JNICALL 303 Java_sun_lwawt_macosx_CRobot_nativeGetScreenPixels 304 (JNIEnv *env, jobject peer, 305 jint x, jint y, jint width, jint height, jdouble scale, jintArray pixels) 306 { 307 JNF_COCOA_ENTER(env); 308 309 jint picX = x; 310 jint picY = y; 311 jint picWidth = width; 312 jint picHeight = height; 313 314 CGRect screenRect = CGRectMake(picX / scale, picY / scale, 315 picWidth / scale, picHeight / scale); 316 CGImageRef screenPixelsImage = CGWindowListCreateImage(screenRect, 317 kCGWindowListOptionOnScreenOnly, 318 kCGNullWindowID, kCGWindowImageBestResolution); 319 320 if (screenPixelsImage == NULL) { 321 return; 322 } 323 324 // get a pointer to the Java int array 325 void *jPixelData = (*env)->GetPrimitiveArrayCritical(env, pixels, 0); 326 CHECK_NULL(jPixelData); 327 328 // create a graphics context around the Java int array 329 CGColorSpaceRef picColorSpace = CGColorSpaceCreateWithName( 330 kCGColorSpaceGenericRGB); 331 CGContextRef jPicContextRef = CGBitmapContextCreate( 332 jPixelData, 333 picWidth, picHeight, 334 8, picWidth * sizeof(jint), 335 picColorSpace, 336 kCGBitmapByteOrder32Host | 337 kCGImageAlphaPremultipliedFirst); 338 339 CGColorSpaceRelease(picColorSpace); 340 341 // flip, scale, and color correct the screen image into the Java pixels 342 CGRect bounds = { { 0, 0 }, { picWidth, picHeight } }; 343 CGContextDrawImage(jPicContextRef, bounds, screenPixelsImage); 344 CGContextFlush(jPicContextRef); 345 346 // cleanup 347 CGContextRelease(jPicContextRef); 348 CGImageRelease(screenPixelsImage); 349 350 // release the Java int array back up to the JVM 351 (*env)->ReleasePrimitiveArrayCritical(env, pixels, jPixelData, 0); 352 353 JNF_COCOA_EXIT(env); 354 } 355 356 /**************************************************** 357 * Helper methods 358 ****************************************************/ 359 360 static void PostMouseEvent(const CGPoint point, CGMouseButton button, 361 CGEventType type, int clickCount, int eventNumber) 362 { 363 CGEventRef mouseEvent = CGEventCreateMouseEvent(NULL, type, point, button); 364 if (mouseEvent != NULL) { 365 CGEventSetIntegerValueField(mouseEvent, kCGMouseEventClickState, clickCount); 366 CGEventSetIntegerValueField(mouseEvent, kCGMouseEventNumber, eventNumber); 367 CGEventPost(kCGSessionEventTap, mouseEvent); 368 CFRelease(mouseEvent); 369 } 370 } 371 372 static inline CGKeyCode GetCGKeyCode(jint javaKeyCode) 373 { 374 CRobotKeyCodeMapping *keyCodeMapping = [CRobotKeyCodeMapping sharedInstance]; 375 return [keyCodeMapping getOSXKeyCodeForJavaKey:javaKeyCode]; 376 } 377 378 static int GetClickCount(BOOL isDown) { 379 NSTimeInterval now = [[NSDate date] timeIntervalSinceReferenceDate]; 380 NSTimeInterval clickInterval = now - gsLastClickTime; 381 BOOL isWithinTreshold = clickInterval < [NSEvent doubleClickInterval]; 382 383 if (isDown) { 384 if (isWithinTreshold) { 385 gsClickCount++; 386 } else { 387 gsClickCount = 1; 388 } 389 390 gsLastClickTime = now; 391 } else { 392 // In OS X, a mouse up has the click count of the last mouse down 393 // if an interval between up and down is within the double click 394 // threshold, and 0 otherwise. 395 if (!isWithinTreshold) { 396 gsClickCount = 0; 397 } 398 } 399 400 return gsClickCount; 401 }