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 "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 BOOL IsModifierKey(jint javaKeyCode); 79 80 static void 81 CreateJavaException(JNIEnv* env, CGError err) 82 { 83 // Throw a java exception indicating what is wrong. 84 NSString* s = [NSString stringWithFormat:@"Robot: CGError: %d", err]; 85 (*env)->ThrowNew(env, (*env)->FindClass(env, "java/awt/AWTException"), 86 [s UTF8String]); 87 } 88 89 /* 90 * Class: sun_lwawt_macosx_CRobot 91 * Method: initRobot 92 * Signature: (V)V 93 */ 94 JNIEXPORT void JNICALL 95 Java_sun_lwawt_macosx_CRobot_initRobot 96 (JNIEnv *env, jobject peer) 97 { 98 // Set things up to let our app act like a synthetic keyboard and mouse. 99 // Always set all states, in case Apple ever changes default behaviors. 100 static int setupDone = 0; 101 if (!setupDone) { 102 int i; 103 jint* tmp; 104 jboolean copy = JNI_FALSE; 105 106 setupDone = 1; 107 // Don't block local events after posting ours 108 CGSetLocalEventsSuppressionInterval(0.0); 109 110 // Let our event's modifier key state blend with local hardware events 111 CGEnableEventStateCombining(TRUE); 112 113 // Don't let our events block local hardware events 114 CGSetLocalEventsFilterDuringSupressionState( 115 kCGEventFilterMaskPermitAllEvents, 116 kCGEventSupressionStateSupressionInterval); 117 CGSetLocalEventsFilterDuringSupressionState( 118 kCGEventFilterMaskPermitAllEvents, 119 kCGEventSupressionStateRemoteMouseDrag); 120 121 gsClickCount = 0; 122 gsLastClickTime = 0; 123 gsEventNumber = ROBOT_EVENT_NUMBER_START; 124 125 gsButtonEventNumber = (int*)SAFE_SIZE_ARRAY_ALLOC(malloc, sizeof(int), gNumberOfButtons); 126 if (gsButtonEventNumber == NULL) { 127 JNU_ThrowOutOfMemoryError(env, NULL); 128 return; 129 } 130 131 for (i = 0; i < gNumberOfButtons; ++i) { 132 gsButtonEventNumber[i] = ROBOT_EVENT_NUMBER_START; 133 } 134 } 135 } 136 137 /* 138 * Class: sun_lwawt_macosx_CRobot 139 * Method: mouseEvent 140 * Signature: (IIIIZZ)V 141 */ 142 JNIEXPORT void JNICALL 143 Java_sun_lwawt_macosx_CRobot_mouseEvent 144 (JNIEnv *env, jobject peer, 145 jint displayID, jint mouseLastX, jint mouseLastY, jint buttonsState, 146 jboolean isButtonsDownState, jboolean isMouseMove) 147 { 148 JNF_COCOA_ENTER(env); 149 150 // This is the native method called when Robot mouse events occur. 151 // The CRobot tracks the mouse position, and which button was 152 // pressed. The peer also tracks the mouse button desired state, 153 // the appropriate key modifier state, and whether the mouse action 154 // is simply a mouse move with no mouse button state changes. 155 156 // volatile, otherwise it warns that it might be clobbered by 'longjmp' 157 volatile CGPoint point; 158 159 point.x = mouseLastX; 160 point.y = mouseLastY; 161 162 __block CGMouseButton button = kCGMouseButtonLeft; 163 __block CGEventType type = kCGEventMouseMoved; 164 165 void (^HandleRobotButton)(CGMouseButton, CGEventType, CGEventType, CGEventType) = 166 ^(CGMouseButton cgButton, CGEventType cgButtonUp, CGEventType cgButtonDown, 167 CGEventType cgButtonDragged) { 168 169 button = cgButton; 170 type = cgButtonUp; 171 172 if (isButtonsDownState) { 173 if (isMouseMove) { 174 type = cgButtonDragged; 175 } else { 176 type = cgButtonDown; 177 } 178 } 179 }; 180 181 // Left 182 if (buttonsState & java_awt_event_InputEvent_BUTTON1_MASK || 183 buttonsState & java_awt_event_InputEvent_BUTTON1_DOWN_MASK ) { 184 185 HandleRobotButton(kCGMouseButtonLeft, kCGEventLeftMouseUp, 186 kCGEventLeftMouseDown, kCGEventLeftMouseDragged); 187 } 188 189 // Other 190 if (buttonsState & java_awt_event_InputEvent_BUTTON2_MASK || 191 buttonsState & java_awt_event_InputEvent_BUTTON2_DOWN_MASK ) { 192 193 HandleRobotButton(kCGMouseButtonCenter, kCGEventOtherMouseUp, 194 kCGEventOtherMouseDown, kCGEventOtherMouseDragged); 195 } 196 197 // Right 198 if (buttonsState & java_awt_event_InputEvent_BUTTON3_MASK || 199 buttonsState & java_awt_event_InputEvent_BUTTON3_DOWN_MASK ) { 200 201 HandleRobotButton(kCGMouseButtonRight, kCGEventRightMouseUp, 202 kCGEventRightMouseDown, kCGEventRightMouseDragged); 203 } 204 205 // Extra 206 if (gNumberOfButtons > 3) { 207 int extraButton; 208 for (extraButton = 3; extraButton < gNumberOfButtons; ++extraButton) { 209 if ((buttonsState & gButtonDownMasks[extraButton])) { 210 HandleRobotButton(extraButton, kCGEventOtherMouseUp, 211 kCGEventOtherMouseDown, kCGEventOtherMouseDragged); 212 } 213 } 214 } 215 216 int clickCount = 0; 217 int eventNumber = gsEventNumber; 218 219 if (isMouseMove) { 220 // any mouse movement resets click count 221 gsLastClickTime = 0; 222 } else { 223 clickCount = GetClickCount(isButtonsDownState); 224 225 if (isButtonsDownState) { 226 gsButtonEventNumber[button] = gsEventNumber++; 227 } 228 eventNumber = gsButtonEventNumber[button]; 229 } 230 231 PostMouseEvent(point, button, type, clickCount, eventNumber); 232 233 JNF_COCOA_EXIT(env); 234 } 235 236 /* 237 * Class: sun_lwawt_macosx_CRobot 238 * Method: mouseWheel 239 * Signature: (I)V 240 */ 241 JNIEXPORT void JNICALL 242 Java_sun_lwawt_macosx_CRobot_mouseWheel 243 (JNIEnv *env, jobject peer, jint wheelAmt) 244 { 245 CGEventRef event = CGEventCreateScrollWheelEvent(NULL, 246 kCGScrollEventUnitLine, 247 k_JAVA_ROBOT_WHEEL_COUNT, wheelAmt); 248 249 if (event != NULL) { 250 CGEventPost(kCGSessionEventTap, event); 251 CFRelease(event); 252 } 253 } 254 255 /* 256 * Class: sun_lwawt_macosx_CRobot 257 * Method: keyEvent 258 * Signature: (IZ)V 259 */ 260 JNIEXPORT void JNICALL 261 Java_sun_lwawt_macosx_CRobot_keyEvent 262 (JNIEnv *env, jobject peer, jint javaKeyCode, jboolean keyPressed) 263 { 264 /* 265 * Well, using CGEventCreateKeyboardEvent/CGEventPost would have been 266 * a better solution, however, it gives me all kinds of trouble and I have 267 * no idea how to solve them without inserting delays between simulated 268 * events. So, I've ended up disabling it and opted for another approach 269 * that uses Accessibility API instead. 270 */ 271 272 CGKeyCode keyCode = GetCGKeyCode(javaKeyCode); 273 274 /* 275 * JDK-8155740: AXUIElementPostKeyboardEvent posts 0 key code for all 276 * the modifier keys with key codes (16, 17,18, 20, 157) and also for 277 * newly added modifier key VK_ALT_GRAPH. But it posts correct key code 278 * for all the other keys. On the other hand CGEventCreateKeyboardEvent 279 * posts correct key code for all the modifier keys and hence it is used 280 * to post modifier key events and AXUIElementPostKeyboardEvent is used to 281 * post all the remaining key events. 282 */ 283 if (IsModifierKey(javaKeyCode)) { 284 CGEventRef event = CGEventCreateKeyboardEvent(NULL, keyCode, keyPressed); 285 if (event != NULL) { 286 CGEventPost(kCGSessionEventTap, event); 287 CFRelease(event); 288 } 289 } else { 290 AXUIElementRef elem = AXUIElementCreateSystemWide(); 291 AXUIElementPostKeyboardEvent(elem, (CGCharCode)0, keyCode, keyPressed); 292 CFRelease(elem); 293 } 294 } 295 296 /* 297 * Class: sun_lwawt_macosx_CRobot 298 * Method: nativeGetScreenPixels 299 * Signature: (IIIII[I)V 300 */ 301 JNIEXPORT void JNICALL 302 Java_sun_lwawt_macosx_CRobot_nativeGetScreenPixels 303 (JNIEnv *env, jobject peer, 304 jint x, jint y, jint width, jint height, jintArray pixels) 305 { 306 JNF_COCOA_ENTER(env); 307 308 jint picX = x; 309 jint picY = y; 310 jint picWidth = width; 311 jint picHeight = height; 312 313 CGRect screenRect = CGRectMake(picX, picY, picWidth, picHeight); 314 CGImageRef screenPixelsImage = CGWindowListCreateImage(screenRect, 315 kCGWindowListOptionOnScreenOnly, 316 kCGNullWindowID, kCGWindowImageDefault); 317 318 if (screenPixelsImage == NULL) { 319 return; 320 } 321 322 // get a pointer to the Java int array 323 void *jPixelData = (*env)->GetPrimitiveArrayCritical(env, pixels, 0); 324 CHECK_NULL(jPixelData); 325 326 // create a graphics context around the Java int array 327 CGColorSpaceRef picColorSpace = CGColorSpaceCreateWithName( 328 kCGColorSpaceGenericRGB); 329 CGContextRef jPicContextRef = CGBitmapContextCreate( 330 jPixelData, 331 picWidth, picHeight, 332 8, picWidth * sizeof(jint), 333 picColorSpace, 334 kCGBitmapByteOrder32Host | 335 kCGImageAlphaPremultipliedFirst); 336 337 CGColorSpaceRelease(picColorSpace); 338 339 // flip, scale, and color correct the screen image into the Java pixels 340 CGRect bounds = { { 0, 0 }, { picWidth, picHeight } }; 341 CGContextDrawImage(jPicContextRef, bounds, screenPixelsImage); 342 CGContextFlush(jPicContextRef); 343 344 // cleanup 345 CGContextRelease(jPicContextRef); 346 CGImageRelease(screenPixelsImage); 347 348 // release the Java int array back up to the JVM 349 (*env)->ReleasePrimitiveArrayCritical(env, pixels, jPixelData, 0); 350 351 JNF_COCOA_EXIT(env); 352 } 353 354 /**************************************************** 355 * Helper methods 356 ****************************************************/ 357 358 static void PostMouseEvent(const CGPoint point, CGMouseButton button, 359 CGEventType type, int clickCount, int eventNumber) 360 { 361 CGEventRef mouseEvent = CGEventCreateMouseEvent(NULL, type, point, button); 362 if (mouseEvent != NULL) { 363 CGEventSetIntegerValueField(mouseEvent, kCGMouseEventClickState, clickCount); 364 CGEventSetIntegerValueField(mouseEvent, kCGMouseEventNumber, eventNumber); 365 CGEventPost(kCGSessionEventTap, mouseEvent); 366 CFRelease(mouseEvent); 367 } 368 } 369 370 static inline CGKeyCode GetCGKeyCode(jint javaKeyCode) 371 { 372 CRobotKeyCodeMapping *keyCodeMapping = [CRobotKeyCodeMapping sharedInstance]; 373 return [keyCodeMapping getOSXKeyCodeForJavaKey:javaKeyCode]; 374 } 375 376 static int GetClickCount(BOOL isDown) { 377 NSTimeInterval now = [[NSDate date] timeIntervalSinceReferenceDate]; 378 NSTimeInterval clickInterval = now - gsLastClickTime; 379 BOOL isWithinTreshold = clickInterval < [NSEvent doubleClickInterval]; 380 381 if (isDown) { 382 if (isWithinTreshold) { 383 gsClickCount++; 384 } else { 385 gsClickCount = 1; 386 } 387 388 gsLastClickTime = now; 389 } else { 390 // In OS X, a mouse up has the click count of the last mouse down 391 // if an interval between up and down is within the double click 392 // threshold, and 0 otherwise. 393 if (!isWithinTreshold) { 394 gsClickCount = 0; 395 } 396 } 397 398 return gsClickCount; 399 } 400 401 static BOOL IsModifierKey(jint javaKeyCode) { 402 403 switch (javaKeyCode) { 404 case java_awt_event_KeyEvent_VK_SHIFT: 405 case java_awt_event_KeyEvent_VK_CONTROL: 406 case java_awt_event_KeyEvent_VK_ALT: 407 case java_awt_event_KeyEvent_VK_CAPS_LOCK: 408 case java_awt_event_KeyEvent_VK_META: 409 case java_awt_event_KeyEvent_VK_ALT_GRAPH: 410 return YES; 411 } 412 413 return NO; 414 }