Path: blob/master/src/java.desktop/macosx/native/libawt_lwawt/awt/CRobot.m
41152 views
/*1* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425#import "JNIUtilities.h"2627#import <ApplicationServices/ApplicationServices.h>2829#import "CRobotKeyCode.h"30#import "LWCToolkit.h"31#import "sun_lwawt_macosx_CRobot.h"32#import "java_awt_event_InputEvent.h"33#import "java_awt_event_KeyEvent.h"34#import "sizecalc.h"35#import "ThreadUtilities.h"3637// Starting number for event numbers generated by Robot.38// Apple docs don't mention at all what are the requirements39// for these numbers. It seems that they must be higher40// than event numbers from real events, which start at some41// value close to zero. There is no API for obtaining current42// event number, so we have to start from some random number.43// 32000 as starting value works for me, let's hope that it will44// work for others as well.45#define ROBOT_EVENT_NUMBER_START 320004647#define k_JAVA_ROBOT_WHEEL_COUNT 14849#if !defined(kCGBitmapByteOrder32Host)50#define kCGBitmapByteOrder32Host 051#endif5253// In OS X, left and right mouse button share the same click count.54// That is, if one starts clicking the left button rapidly and then55// switches to the right button, then the click count will continue56// increasing, without dropping to 1 in between. The middle button,57// however, has its own click count.58// For robot, we aren't going to emulate all that complexity. All our59// synhtetic clicks share the same click count.60static int gsClickCount;61static NSTimeInterval gsLastClickTime;6263// Apparently, for mouse up/down events we have to set an event number64// that is incremented on each button press. Otherwise, strange things65// happen with z-order.66static int gsEventNumber;67static int* gsButtonEventNumber;68static NSTimeInterval gNextKeyEventTime;6970static inline CGKeyCode GetCGKeyCode(jint javaKeyCode);7172static void PostMouseEvent(const CGPoint point, CGMouseButton button,73CGEventType type, int clickCount, int eventNumber);7475static int GetClickCount(BOOL isDown);7677static void78CreateJavaException(JNIEnv* env, CGError err)79{80// Throw a java exception indicating what is wrong.81NSString* s = [NSString stringWithFormat:@"Robot: CGError: %d", err];82(*env)->ThrowNew(env, (*env)->FindClass(env, "java/awt/AWTException"),83[s UTF8String]);84}8586/**87* Saves the "safe moment" when the NEXT event can be posted by the robot safely88* and sleeps for some time if the "safe moment" for the CURRENT event is not89* reached.90*91* We need to sleep to give time for the macOS to update the state.92*93* The "mouse move" events are skipped, because it is not a big issue if we lost94* some of them, the latest coordinates are saved in the peer and will be used95* for clicks.96*/97static inline void autoDelay(BOOL isMove) {98if (!isMove){99NSTimeInterval now = [[NSDate date] timeIntervalSinceReferenceDate];100NSTimeInterval delay = gNextKeyEventTime - now;101if (delay > 0) {102[NSThread sleepForTimeInterval:delay];103}104}105gNextKeyEventTime = [[NSDate date] timeIntervalSinceReferenceDate] + 0.050;106}107108/*109* Class: sun_lwawt_macosx_CRobot110* Method: initRobot111* Signature: (V)V112*/113JNIEXPORT void JNICALL114Java_sun_lwawt_macosx_CRobot_initRobot115(JNIEnv *env, jobject peer)116{117// Set things up to let our app act like a synthetic keyboard and mouse.118// Always set all states, in case Apple ever changes default behaviors.119static int setupDone = 0;120if (!setupDone) {121[ThreadUtilities performOnMainThreadWaiting:NO block:^(){122int i;123jint* tmp;124jboolean copy = JNI_FALSE;125126setupDone = 1;127// Don't block local events after posting ours128CGSetLocalEventsSuppressionInterval(0.0);129130// Let our event's modifier key state blend with local hardware events131CGEnableEventStateCombining(TRUE);132133// Don't let our events block local hardware events134CGSetLocalEventsFilterDuringSupressionState(135kCGEventFilterMaskPermitAllEvents,136kCGEventSupressionStateSupressionInterval);137CGSetLocalEventsFilterDuringSupressionState(138kCGEventFilterMaskPermitAllEvents,139kCGEventSupressionStateRemoteMouseDrag);140141gsClickCount = 0;142gsLastClickTime = 0;143gNextKeyEventTime = 0;144gsEventNumber = ROBOT_EVENT_NUMBER_START;145146gsButtonEventNumber = (int*)SAFE_SIZE_ARRAY_ALLOC(malloc, sizeof(int), gNumberOfButtons);147if (gsButtonEventNumber == NULL) {148JNU_ThrowOutOfMemoryError(env, NULL);149return;150}151152for (i = 0; i < gNumberOfButtons; ++i) {153gsButtonEventNumber[i] = ROBOT_EVENT_NUMBER_START;154}155}];156}157}158159/*160* Class: sun_lwawt_macosx_CRobot161* Method: mouseEvent162* Signature: (IIIIZZ)V163*/164JNIEXPORT void JNICALL165Java_sun_lwawt_macosx_CRobot_mouseEvent166(JNIEnv *env, jobject peer, jint mouseLastX, jint mouseLastY, jint buttonsState,167jboolean isButtonsDownState, jboolean isMouseMove)168{169JNI_COCOA_ENTER(env);170autoDelay(isMouseMove);171172// This is the native method called when Robot mouse events occur.173// The CRobot tracks the mouse position, and which button was174// pressed. The peer also tracks the mouse button desired state,175// the appropriate key modifier state, and whether the mouse action176// is simply a mouse move with no mouse button state changes.177178// volatile, otherwise it warns that it might be clobbered by 'longjmp'179volatile CGPoint point;180181point.x = mouseLastX;182point.y = mouseLastY;183184__block CGMouseButton button = kCGMouseButtonLeft;185__block CGEventType type = kCGEventMouseMoved;186187void (^HandleRobotButton)(CGMouseButton, CGEventType, CGEventType, CGEventType) =188^(CGMouseButton cgButton, CGEventType cgButtonUp, CGEventType cgButtonDown,189CGEventType cgButtonDragged) {190191button = cgButton;192type = cgButtonUp;193194if (isButtonsDownState) {195if (isMouseMove) {196type = cgButtonDragged;197} else {198type = cgButtonDown;199}200}201};202203// Left204if (buttonsState & java_awt_event_InputEvent_BUTTON1_MASK ||205buttonsState & java_awt_event_InputEvent_BUTTON1_DOWN_MASK ) {206207HandleRobotButton(kCGMouseButtonLeft, kCGEventLeftMouseUp,208kCGEventLeftMouseDown, kCGEventLeftMouseDragged);209}210211// Other212if (buttonsState & java_awt_event_InputEvent_BUTTON2_MASK ||213buttonsState & java_awt_event_InputEvent_BUTTON2_DOWN_MASK ) {214215HandleRobotButton(kCGMouseButtonCenter, kCGEventOtherMouseUp,216kCGEventOtherMouseDown, kCGEventOtherMouseDragged);217}218219// Right220if (buttonsState & java_awt_event_InputEvent_BUTTON3_MASK ||221buttonsState & java_awt_event_InputEvent_BUTTON3_DOWN_MASK ) {222223HandleRobotButton(kCGMouseButtonRight, kCGEventRightMouseUp,224kCGEventRightMouseDown, kCGEventRightMouseDragged);225}226227// Extra228if (gNumberOfButtons > 3) {229int extraButton;230for (extraButton = 3; extraButton < gNumberOfButtons; ++extraButton) {231if ((buttonsState & gButtonDownMasks[extraButton])) {232HandleRobotButton(extraButton, kCGEventOtherMouseUp,233kCGEventOtherMouseDown, kCGEventOtherMouseDragged);234}235}236}237238int clickCount = 0;239int eventNumber = gsEventNumber;240241if (isMouseMove) {242// any mouse movement resets click count243gsLastClickTime = 0;244} else {245clickCount = GetClickCount(isButtonsDownState);246247if (isButtonsDownState) {248gsButtonEventNumber[button] = gsEventNumber++;249}250eventNumber = gsButtonEventNumber[button];251}252253PostMouseEvent(point, button, type, clickCount, eventNumber);254255JNI_COCOA_EXIT(env);256}257258/*259* Class: sun_lwawt_macosx_CRobot260* Method: mouseWheel261* Signature: (I)V262*/263JNIEXPORT void JNICALL264Java_sun_lwawt_macosx_CRobot_mouseWheel265(JNIEnv *env, jobject peer, jint wheelAmt)266{267autoDelay(NO);268[ThreadUtilities performOnMainThreadWaiting:YES block:^(){269CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);270CGEventRef event = CGEventCreateScrollWheelEvent(source,271kCGScrollEventUnitLine,272k_JAVA_ROBOT_WHEEL_COUNT, wheelAmt);273if (event != NULL) {274CGEventPost(kCGHIDEventTap, event);275CFRelease(event);276}277if (source != NULL) {278CFRelease(source);279}280}];281}282283/*284* Class: sun_lwawt_macosx_CRobot285* Method: keyEvent286* Signature: (IZ)V287*/288JNIEXPORT void JNICALL289Java_sun_lwawt_macosx_CRobot_keyEvent290(JNIEnv *env, jobject peer, jint javaKeyCode, jboolean keyPressed)291{292autoDelay(NO);293[ThreadUtilities performOnMainThreadWaiting:YES block:^(){294CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);295CGKeyCode keyCode = GetCGKeyCode(javaKeyCode);296CGEventRef event = CGEventCreateKeyboardEvent(source, keyCode, keyPressed);297if (event != NULL) {298CGEventPost(kCGHIDEventTap, event);299CFRelease(event);300}301if (source != NULL) {302CFRelease(source);303}304}];305}306307/*308* Class: sun_lwawt_macosx_CRobot309* Method: nativeGetScreenPixels310* Signature: (IIIII[I)V311*/312JNIEXPORT void JNICALL313Java_sun_lwawt_macosx_CRobot_nativeGetScreenPixels314(JNIEnv *env, jobject peer,315jint x, jint y, jint width, jint height, jdouble scale, jintArray pixels)316{317JNI_COCOA_ENTER(env);318319jint picX = x;320jint picY = y;321jint picWidth = width;322jint picHeight = height;323324CGRect screenRect = CGRectMake(picX / scale, picY / scale,325picWidth / scale, picHeight / scale);326CGImageRef screenPixelsImage = CGWindowListCreateImage(screenRect,327kCGWindowListOptionOnScreenOnly,328kCGNullWindowID, kCGWindowImageBestResolution);329330if (screenPixelsImage == NULL) {331return;332}333334// get a pointer to the Java int array335void *jPixelData = (*env)->GetPrimitiveArrayCritical(env, pixels, 0);336CHECK_NULL(jPixelData);337338// create a graphics context around the Java int array339CGColorSpaceRef picColorSpace = CGColorSpaceCreateWithName(340kCGColorSpaceSRGB);341CGContextRef jPicContextRef = CGBitmapContextCreate(342jPixelData,343picWidth, picHeight,3448, picWidth * sizeof(jint),345picColorSpace,346kCGBitmapByteOrder32Host |347kCGImageAlphaPremultipliedFirst);348349CGColorSpaceRelease(picColorSpace);350351// flip, scale, and color correct the screen image into the Java pixels352CGRect bounds = { { 0, 0 }, { picWidth, picHeight } };353CGContextDrawImage(jPicContextRef, bounds, screenPixelsImage);354CGContextFlush(jPicContextRef);355356// cleanup357CGContextRelease(jPicContextRef);358CGImageRelease(screenPixelsImage);359360// release the Java int array back up to the JVM361(*env)->ReleasePrimitiveArrayCritical(env, pixels, jPixelData, 0);362363JNI_COCOA_EXIT(env);364}365366/****************************************************367* Helper methods368****************************************************/369370static void PostMouseEvent(const CGPoint point, CGMouseButton button,371CGEventType type, int clickCount, int eventNumber)372{373[ThreadUtilities performOnMainThreadWaiting:YES block:^(){374CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);375CGEventRef mouseEvent = CGEventCreateMouseEvent(source, type, point, button);376if (mouseEvent != NULL) {377CGEventSetIntegerValueField(mouseEvent, kCGMouseEventClickState, clickCount);378CGEventSetIntegerValueField(mouseEvent, kCGMouseEventNumber, eventNumber);379CGEventPost(kCGHIDEventTap, mouseEvent);380CFRelease(mouseEvent);381}382if (source != NULL) {383CFRelease(source);384}385}];386}387388static inline CGKeyCode GetCGKeyCode(jint javaKeyCode)389{390CRobotKeyCodeMapping *keyCodeMapping = [CRobotKeyCodeMapping sharedInstance];391return [keyCodeMapping getOSXKeyCodeForJavaKey:javaKeyCode];392}393394static int GetClickCount(BOOL isDown) {395NSTimeInterval now = [[NSDate date] timeIntervalSinceReferenceDate];396NSTimeInterval clickInterval = now - gsLastClickTime;397BOOL isWithinTreshold = clickInterval < [NSEvent doubleClickInterval];398399if (isDown) {400if (isWithinTreshold) {401gsClickCount++;402} else {403gsClickCount = 1;404}405406gsLastClickTime = now;407} else {408// In OS X, a mouse up has the click count of the last mouse down409// if an interval between up and down is within the double click410// threshold, and 0 otherwise.411if (!isWithinTreshold) {412gsClickCount = 0;413}414}415416return gsClickCount;417}418419420