/* * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ #import "jni_util.h" #import #import #import "CRobotKeyCode.h" #import "LWCToolkit.h" #import "sun_lwawt_macosx_CRobot.h" #import "java_awt_event_InputEvent.h" #import "java_awt_event_KeyEvent.h" #import "sizecalc.h" // Starting number for event numbers generated by Robot. // Apple docs don't mention at all what are the requirements // for these numbers. It seems that they must be higher // than event numbers from real events, which start at some // value close to zero. There is no API for obtaining current // event number, so we have to start from some random number. // 32000 as starting value works for me, let's hope that it will // work for others as well. #define ROBOT_EVENT_NUMBER_START 32000 #define k_JAVA_ROBOT_WHEEL_COUNT 1 #if !defined(kCGBitmapByteOrder32Host) #define kCGBitmapByteOrder32Host 0 #endif // In OS X, left and right mouse button share the same click count. // That is, if one starts clicking the left button rapidly and then // switches to the right button, then the click count will continue // increasing, without dropping to 1 in between. The middle button, // however, has its own click count. // For robot, we aren't going to emulate all that complexity. All our // synhtetic clicks share the same click count. static int gsClickCount; static NSTimeInterval gsLastClickTime; // Apparently, for mouse up/down events we have to set an event number // that is incremented on each button press. Otherwise, strange things // happen with z-order. static int gsEventNumber; static int* gsButtonEventNumber; static CRobotKeyCodeMapping *keyCodeMapping; static inline CGKeyCode GetCGKeyCode(jint javaKeyCode); static void PostMouseEvent(const CGPoint point, CGMouseButton button, CGEventType type, int clickCount, int eventNumber); static int GetClickCount(BOOL isDown); static void CreateJavaException(JNIEnv* env, CGError err) { // Throw a java exception indicating what is wrong. NSString* s = [NSString stringWithFormat:@"Robot: CGError: %d", err]; (*env)->ThrowNew(env, (*env)->FindClass(env, "java/awt/AWTException"), [s UTF8String]); } /* * Class: sun_lwawt_macosx_CRobot * Method: initRobot * Signature: (V)V */ JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CRobot_initRobot (JNIEnv *env, jobject peer) { // Set things up to let our app act like a synthetic keyboard and mouse. // Always set all states, in case Apple ever changes default behaviors. static int setupDone = 0; if (!setupDone) { int i; jint* tmp; jboolean copy = JNI_FALSE; setupDone = 1; // Don't block local events after posting ours CGSetLocalEventsSuppressionInterval(0.0); // Let our event's modifier key state blend with local hardware events CGEnableEventStateCombining(TRUE); // Don't let our events block local hardware events CGSetLocalEventsFilterDuringSupressionState( kCGEventFilterMaskPermitAllEvents, kCGEventSupressionStateSupressionInterval); CGSetLocalEventsFilterDuringSupressionState( kCGEventFilterMaskPermitAllEvents, kCGEventSupressionStateRemoteMouseDrag); gsClickCount = 0; gsLastClickTime = 0; gsEventNumber = ROBOT_EVENT_NUMBER_START; gsButtonEventNumber = (int*)SAFE_SIZE_ARRAY_ALLOC(malloc, sizeof(int), gNumberOfButtons); if (gsButtonEventNumber == NULL) { JNU_ThrowOutOfMemoryError(env, NULL); return; } for (i = 0; i < gNumberOfButtons; ++i) { gsButtonEventNumber[i] = ROBOT_EVENT_NUMBER_START; } } keyCodeMapping = [CRobotKeyCodeMapping sharedInstance]; } /* * Class: sun_lwawt_macosx_CRobot * Method: mouseEvent * Signature: (IIIIZZ)V */ JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CRobot_mouseEvent (JNIEnv *env, jobject peer, jint displayID, jint mouseLastX, jint mouseLastY, jint buttonsState, jboolean isButtonsDownState, jboolean isMouseMove) { JNF_COCOA_ENTER(env); // This is the native method called when Robot mouse events occur. // The CRobot tracks the mouse position, and which button was // pressed. The peer also tracks the mouse button desired state, // the appropriate key modifier state, and whether the mouse action // is simply a mouse move with no mouse button state changes. // volatile, otherwise it warns that it might be clobbered by 'longjmp' volatile CGPoint point; point.x = mouseLastX; point.y = mouseLastY; __block CGMouseButton button = kCGMouseButtonLeft; __block CGEventType type = kCGEventMouseMoved; void (^HandleRobotButton)(CGMouseButton, CGEventType, CGEventType, CGEventType) = ^(CGMouseButton cgButton, CGEventType cgButtonUp, CGEventType cgButtonDown, CGEventType cgButtonDragged) { button = cgButton; type = cgButtonUp; if (isButtonsDownState) { if (isMouseMove) { type = cgButtonDragged; } else { type = cgButtonDown; } } }; // Left if (buttonsState & java_awt_event_InputEvent_BUTTON1_MASK || buttonsState & java_awt_event_InputEvent_BUTTON1_DOWN_MASK ) { HandleRobotButton(kCGMouseButtonLeft, kCGEventLeftMouseUp, kCGEventLeftMouseDown, kCGEventLeftMouseDragged); } // Other if (buttonsState & java_awt_event_InputEvent_BUTTON2_MASK || buttonsState & java_awt_event_InputEvent_BUTTON2_DOWN_MASK ) { HandleRobotButton(kCGMouseButtonCenter, kCGEventOtherMouseUp, kCGEventOtherMouseDown, kCGEventOtherMouseDragged); } // Right if (buttonsState & java_awt_event_InputEvent_BUTTON3_MASK || buttonsState & java_awt_event_InputEvent_BUTTON3_DOWN_MASK ) { HandleRobotButton(kCGMouseButtonRight, kCGEventRightMouseUp, kCGEventRightMouseDown, kCGEventRightMouseDragged); } // Extra if (gNumberOfButtons > 3) { int extraButton; for (extraButton = 3; extraButton < gNumberOfButtons; ++extraButton) { if ((buttonsState & gButtonDownMasks[extraButton])) { HandleRobotButton(extraButton, kCGEventOtherMouseUp, kCGEventOtherMouseDown, kCGEventOtherMouseDragged); } } } int clickCount = 0; int eventNumber = gsEventNumber; if (isMouseMove) { // any mouse movement resets click count gsLastClickTime = 0; } else { clickCount = GetClickCount(isButtonsDownState); if (isButtonsDownState) { gsButtonEventNumber[button] = gsEventNumber++; } eventNumber = gsButtonEventNumber[button]; } PostMouseEvent(point, button, type, clickCount, eventNumber); JNF_COCOA_EXIT(env); } /* * Class: sun_lwawt_macosx_CRobot * Method: mouseWheel * Signature: (I)V */ JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CRobot_mouseWheel (JNIEnv *env, jobject peer, jint wheelAmt) { CGEventRef event = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, k_JAVA_ROBOT_WHEEL_COUNT, wheelAmt); if (event != NULL) { CGEventPost(kCGSessionEventTap, event); CFRelease(event); } } /* * Class: sun_lwawt_macosx_CRobot * Method: keyEvent * Signature: (IZ)V */ JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CRobot_keyEvent (JNIEnv *env, jobject peer, jint javaKeyCode, jboolean keyPressed) { CGKeyCode keyCode = GetCGKeyCode(javaKeyCode); CGEventRef event = CGEventCreateKeyboardEvent(NULL, keyCode, keyPressed); if (event != NULL) { CGEventPost(kCGSessionEventTap, event); CFRelease(event); } } /* * Class: sun_lwawt_macosx_CRobot * Method: nativeGetScreenPixels * Signature: (IIIII[I)V */ JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CRobot_nativeGetScreenPixels (JNIEnv *env, jobject peer, jint x, jint y, jint width, jint height, jintArray pixels) { JNF_COCOA_ENTER(env); jint picX = x; jint picY = y; jint picWidth = width; jint picHeight = height; CGRect screenRect = CGRectMake(picX, picY, picWidth, picHeight); CGImageRef screenPixelsImage = CGWindowListCreateImage(screenRect, kCGWindowListOptionOnScreenOnly, kCGNullWindowID, kCGWindowImageDefault); if (screenPixelsImage == NULL) { return; } // get a pointer to the Java int array void *jPixelData = (*env)->GetPrimitiveArrayCritical(env, pixels, 0); CHECK_NULL(jPixelData); // create a graphics context around the Java int array CGColorSpaceRef picColorSpace = CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB); CGContextRef jPicContextRef = CGBitmapContextCreate( jPixelData, picWidth, picHeight, 8, picWidth * sizeof(jint), picColorSpace, kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst); CGColorSpaceRelease(picColorSpace); // flip, scale, and color correct the screen image into the Java pixels CGRect bounds = { { 0, 0 }, { picWidth, picHeight } }; CGContextDrawImage(jPicContextRef, bounds, screenPixelsImage); CGContextFlush(jPicContextRef); // cleanup CGContextRelease(jPicContextRef); CGImageRelease(screenPixelsImage); // release the Java int array back up to the JVM (*env)->ReleasePrimitiveArrayCritical(env, pixels, jPixelData, 0); JNF_COCOA_EXIT(env); } /**************************************************** * Helper methods ****************************************************/ static void PostMouseEvent(const CGPoint point, CGMouseButton button, CGEventType type, int clickCount, int eventNumber) { CGEventRef mouseEvent = CGEventCreateMouseEvent(NULL, type, point, button); if (mouseEvent != NULL) { CGEventSetIntegerValueField(mouseEvent, kCGMouseEventClickState, clickCount); CGEventSetIntegerValueField(mouseEvent, kCGMouseEventNumber, eventNumber); CGEventPost(kCGSessionEventTap, mouseEvent); CFRelease(mouseEvent); } } static inline CGKeyCode GetCGKeyCode(jint javaKeyCode) { if (keyCodeMapping) { return [keyCodeMapping getOSXKeyCodeForJavaKey:javaKeyCode]; } return OSX_Undefined; } static int GetClickCount(BOOL isDown) { NSTimeInterval now = [[NSDate date] timeIntervalSinceReferenceDate]; NSTimeInterval clickInterval = now - gsLastClickTime; BOOL isWithinTreshold = clickInterval < [NSEvent doubleClickInterval]; if (isDown) { if (isWithinTreshold) { gsClickCount++; } else { gsClickCount = 1; } gsLastClickTime = now; } else { // In OS X, a mouse up has the click count of the last mouse down // if an interval between up and down is within the double click // threshold, and 0 otherwise. if (!isWithinTreshold) { gsClickCount = 0; } } return gsClickCount; }