1 /* 2 * Copyright (c) 2016, 2018, 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 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, 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. 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 CGKeyCode keyCode = GetCGKeyCode(javaKeyCode); 262 263 CGEventRef event = CGEventCreateKeyboardEvent(NULL, keyCode, keyPressed); 264 if (event != NULL) { 265 CGEventPost(kCGSessionEventTap, event); 266 CFRelease(event); 267 } 268 } 269 270 /* 271 * Class: sun_lwawt_macosx_CRobot 272 * Method: nativeGetScreenPixels 273 * Signature: (IIIII[I)V 274 */ 275 JNIEXPORT void JNICALL 276 Java_sun_lwawt_macosx_CRobot_nativeGetScreenPixels 277 (JNIEnv *env, jobject peer, 278 jint x, jint y, jint width, jint height, jdouble scale, jintArray pixels) 279 { 280 JNF_COCOA_ENTER(env); 281 282 jint picX = x; 283 jint picY = y; 284 jint picWidth = width; 285 jint picHeight = height; 286 287 CGRect screenRect = CGRectMake(picX / scale, picY / scale, 316 CGContextDrawImage(jPicContextRef, bounds, screenPixelsImage); 317 CGContextFlush(jPicContextRef); 318 319 // cleanup 320 CGContextRelease(jPicContextRef); 321 CGImageRelease(screenPixelsImage); 322 323 // release the Java int array back up to the JVM 324 (*env)->ReleasePrimitiveArrayCritical(env, pixels, jPixelData, 0); 325 326 JNF_COCOA_EXIT(env); 327 } 328 329 /**************************************************** 330 * Helper methods 331 ****************************************************/ 332 333 static void PostMouseEvent(const CGPoint point, CGMouseButton button, 334 CGEventType type, int clickCount, int eventNumber) 335 { 336 CGEventRef mouseEvent = CGEventCreateMouseEvent(NULL, type, point, button); 337 if (mouseEvent != NULL) { 338 CGEventSetIntegerValueField(mouseEvent, kCGMouseEventClickState, clickCount); 339 CGEventSetIntegerValueField(mouseEvent, kCGMouseEventNumber, eventNumber); 340 CGEventPost(kCGSessionEventTap, mouseEvent); 341 CFRelease(mouseEvent); 342 } 343 } 344 345 static inline CGKeyCode GetCGKeyCode(jint javaKeyCode) 346 { 347 CRobotKeyCodeMapping *keyCodeMapping = [CRobotKeyCodeMapping sharedInstance]; 348 return [keyCodeMapping getOSXKeyCodeForJavaKey:javaKeyCode]; 349 } 350 351 static int GetClickCount(BOOL isDown) { 352 NSTimeInterval now = [[NSDate date] timeIntervalSinceReferenceDate]; 353 NSTimeInterval clickInterval = now - gsLastClickTime; 354 BOOL isWithinTreshold = clickInterval < [NSEvent doubleClickInterval]; 355 356 if (isDown) { 357 if (isWithinTreshold) { 358 gsClickCount++; 359 } else { 360 gsClickCount = 1; 361 } 362 | 1 /* 2 * Copyright (c) 2016, 2020, 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 "jni_util.h" 27 28 #import <JavaNativeFoundation/JavaNativeFoundation.h> 29 #import <ApplicationServices/ApplicationServices.h> 30 31 #import "CRobotKeyCode.h" 32 #import "LWCToolkit.h" 33 #import "sun_lwawt_macosx_CRobot.h" 34 #import "java_awt_event_InputEvent.h" 35 #import "java_awt_event_KeyEvent.h" 36 #import "sizecalc.h" 37 #import "ThreadUtilities.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 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 [ThreadUtilities performOnMainThreadWaiting:NO block:^(){ 101 int i; 102 jint* tmp; 103 jboolean copy = JNI_FALSE; 104 105 setupDone = 1; 106 // Don't block local events after posting ours 107 CGSetLocalEventsSuppressionInterval(0.0); 108 109 // Let our event's modifier key state blend with local hardware events 110 CGEnableEventStateCombining(TRUE); 111 112 // Don't let our events block local hardware events 113 CGSetLocalEventsFilterDuringSupressionState( 114 kCGEventFilterMaskPermitAllEvents, 115 kCGEventSupressionStateSupressionInterval); 116 CGSetLocalEventsFilterDuringSupressionState( 117 kCGEventFilterMaskPermitAllEvents, 118 kCGEventSupressionStateRemoteMouseDrag); 119 120 gsClickCount = 0; 121 gsLastClickTime = 0; 122 gsEventNumber = ROBOT_EVENT_NUMBER_START; 123 124 gsButtonEventNumber = (int*)SAFE_SIZE_ARRAY_ALLOC(malloc, sizeof(int), gNumberOfButtons); 125 if (gsButtonEventNumber == NULL) { 126 JNU_ThrowOutOfMemoryError(env, NULL); 127 return; 128 } 129 130 for (i = 0; i < gNumberOfButtons; ++i) { 131 gsButtonEventNumber[i] = ROBOT_EVENT_NUMBER_START; 132 } 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, jint mouseLastX, jint mouseLastY, jint buttonsState, 145 jboolean isButtonsDownState, jboolean isMouseMove) 146 { 147 JNF_COCOA_ENTER(env); 148 149 // This is the native method called when Robot mouse events occur. 150 // The CRobot tracks the mouse position, and which button was 151 // pressed. The peer also tracks the mouse button desired state, 152 // the appropriate key modifier state, and whether the mouse action 153 // is simply a mouse move with no mouse button state changes. 224 if (isButtonsDownState) { 225 gsButtonEventNumber[button] = gsEventNumber++; 226 } 227 eventNumber = gsButtonEventNumber[button]; 228 } 229 230 PostMouseEvent(point, button, type, clickCount, eventNumber); 231 232 JNF_COCOA_EXIT(env); 233 } 234 235 /* 236 * Class: sun_lwawt_macosx_CRobot 237 * Method: mouseWheel 238 * Signature: (I)V 239 */ 240 JNIEXPORT void JNICALL 241 Java_sun_lwawt_macosx_CRobot_mouseWheel 242 (JNIEnv *env, jobject peer, jint wheelAmt) 243 { 244 [ThreadUtilities performOnMainThreadWaiting:YES block:^(){ 245 CGEventRef event = CGEventCreateScrollWheelEvent(NULL, 246 kCGScrollEventUnitLine, 247 k_JAVA_ROBOT_WHEEL_COUNT, wheelAmt); 248 if (event != NULL) { 249 CGEventPost(kCGSessionEventTap, event); 250 CFRelease(event); 251 } 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 [ThreadUtilities performOnMainThreadWaiting:YES block:^(){ 265 CGKeyCode keyCode = GetCGKeyCode(javaKeyCode); 266 CGEventRef event = CGEventCreateKeyboardEvent(NULL, keyCode, keyPressed); 267 if (event != NULL) { 268 CGEventPost(kCGSessionEventTap, event); 269 CFRelease(event); 270 } 271 }]; 272 } 273 274 /* 275 * Class: sun_lwawt_macosx_CRobot 276 * Method: nativeGetScreenPixels 277 * Signature: (IIIII[I)V 278 */ 279 JNIEXPORT void JNICALL 280 Java_sun_lwawt_macosx_CRobot_nativeGetScreenPixels 281 (JNIEnv *env, jobject peer, 282 jint x, jint y, jint width, jint height, jdouble scale, jintArray pixels) 283 { 284 JNF_COCOA_ENTER(env); 285 286 jint picX = x; 287 jint picY = y; 288 jint picWidth = width; 289 jint picHeight = height; 290 291 CGRect screenRect = CGRectMake(picX / scale, picY / scale, 320 CGContextDrawImage(jPicContextRef, bounds, screenPixelsImage); 321 CGContextFlush(jPicContextRef); 322 323 // cleanup 324 CGContextRelease(jPicContextRef); 325 CGImageRelease(screenPixelsImage); 326 327 // release the Java int array back up to the JVM 328 (*env)->ReleasePrimitiveArrayCritical(env, pixels, jPixelData, 0); 329 330 JNF_COCOA_EXIT(env); 331 } 332 333 /**************************************************** 334 * Helper methods 335 ****************************************************/ 336 337 static void PostMouseEvent(const CGPoint point, CGMouseButton button, 338 CGEventType type, int clickCount, int eventNumber) 339 { 340 [ThreadUtilities performOnMainThreadWaiting:YES block:^(){ 341 CGEventRef mouseEvent = CGEventCreateMouseEvent(NULL, type, point, button); 342 if (mouseEvent != NULL) { 343 CGEventSetIntegerValueField(mouseEvent, kCGMouseEventClickState, clickCount); 344 CGEventSetIntegerValueField(mouseEvent, kCGMouseEventNumber, eventNumber); 345 CGEventPost(kCGSessionEventTap, mouseEvent); 346 CFRelease(mouseEvent); 347 } 348 }]; 349 } 350 351 static inline CGKeyCode GetCGKeyCode(jint javaKeyCode) 352 { 353 CRobotKeyCodeMapping *keyCodeMapping = [CRobotKeyCodeMapping sharedInstance]; 354 return [keyCodeMapping getOSXKeyCodeForJavaKey:javaKeyCode]; 355 } 356 357 static int GetClickCount(BOOL isDown) { 358 NSTimeInterval now = [[NSDate date] timeIntervalSinceReferenceDate]; 359 NSTimeInterval clickInterval = now - gsLastClickTime; 360 BOOL isWithinTreshold = clickInterval < [NSEvent doubleClickInterval]; 361 362 if (isDown) { 363 if (isWithinTreshold) { 364 gsClickCount++; 365 } else { 366 gsClickCount = 1; 367 } 368 |