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