Path: blob/master/src/java.desktop/macosx/native/libawt_lwawt/awt/CRobot.m
66646 views
/*1* Copyright (c) 2016, 2022, 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) {298CGEventFlags flags = CGEventSourceFlagsState(kCGEventSourceStateHIDSystemState);299if ((flags & kCGEventFlagMaskSecondaryFn) != 0) {300flags ^= kCGEventFlagMaskSecondaryFn;301CGEventSetFlags(event, flags);302}303CGEventPost(kCGHIDEventTap, event);304CFRelease(event);305}306if (source != NULL) {307CFRelease(source);308}309}];310}311312/*313* Class: sun_lwawt_macosx_CRobot314* Method: nativeGetScreenPixels315* Signature: (IIIII[I)V316*/317JNIEXPORT void JNICALL318Java_sun_lwawt_macosx_CRobot_nativeGetScreenPixels319(JNIEnv *env, jobject peer,320jint x, jint y, jint width, jint height, jdouble scale, jintArray pixels)321{322JNI_COCOA_ENTER(env);323324jint picX = x;325jint picY = y;326jint picWidth = width;327jint picHeight = height;328jsize size = (*env)->GetArrayLength(env, pixels);329if (size < (long) picWidth * picHeight || picWidth < 0 || picHeight < 0) {330JNU_ThrowInternalError(env, "Invalid arguments to get screen pixels");331return;332}333334CGRect screenRect = CGRectMake(picX / scale, picY / scale,335picWidth / scale, picHeight / scale);336CGImageRef screenPixelsImage = CGWindowListCreateImage(screenRect,337kCGWindowListOptionOnScreenOnly,338kCGNullWindowID, kCGWindowImageBestResolution);339340if (screenPixelsImage == NULL) {341return;342}343344// get a pointer to the Java int array345void *jPixelData = (*env)->GetPrimitiveArrayCritical(env, pixels, 0);346CHECK_NULL(jPixelData);347348// create a graphics context around the Java int array349CGColorSpaceRef picColorSpace = CGColorSpaceCreateWithName(350kCGColorSpaceSRGB);351CGContextRef jPicContextRef = CGBitmapContextCreate(352jPixelData,353picWidth, picHeight,3548, picWidth * sizeof(jint),355picColorSpace,356kCGBitmapByteOrder32Host |357kCGImageAlphaPremultipliedFirst);358359CGColorSpaceRelease(picColorSpace);360361// flip, scale, and color correct the screen image into the Java pixels362CGRect bounds = { { 0, 0 }, { picWidth, picHeight } };363CGContextDrawImage(jPicContextRef, bounds, screenPixelsImage);364CGContextFlush(jPicContextRef);365366// cleanup367CGContextRelease(jPicContextRef);368CGImageRelease(screenPixelsImage);369370// release the Java int array back up to the JVM371(*env)->ReleasePrimitiveArrayCritical(env, pixels, jPixelData, 0);372373JNI_COCOA_EXIT(env);374}375376/****************************************************377* Helper methods378****************************************************/379380static void PostMouseEvent(const CGPoint point, CGMouseButton button,381CGEventType type, int clickCount, int eventNumber)382{383[ThreadUtilities performOnMainThreadWaiting:YES block:^(){384CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);385CGEventRef mouseEvent = CGEventCreateMouseEvent(source, type, point, button);386if (mouseEvent != NULL) {387CGEventSetIntegerValueField(mouseEvent, kCGMouseEventClickState, clickCount);388CGEventSetIntegerValueField(mouseEvent, kCGMouseEventNumber, eventNumber);389CGEventPost(kCGHIDEventTap, mouseEvent);390CFRelease(mouseEvent);391}392if (source != NULL) {393CFRelease(source);394}395}];396}397398static inline CGKeyCode GetCGKeyCode(jint javaKeyCode)399{400CRobotKeyCodeMapping *keyCodeMapping = [CRobotKeyCodeMapping sharedInstance];401return [keyCodeMapping getOSXKeyCodeForJavaKey:javaKeyCode];402}403404static int GetClickCount(BOOL isDown) {405NSTimeInterval now = [[NSDate date] timeIntervalSinceReferenceDate];406NSTimeInterval clickInterval = now - gsLastClickTime;407BOOL isWithinTreshold = clickInterval < [NSEvent doubleClickInterval];408409if (isDown) {410if (isWithinTreshold) {411gsClickCount++;412} else {413gsClickCount = 1;414}415416gsLastClickTime = now;417} else {418// In OS X, a mouse up has the click count of the last mouse down419// if an interval between up and down is within the double click420// threshold, and 0 otherwise.421if (!isWithinTreshold) {422gsClickCount = 0;423}424}425426return gsClickCount;427}428429430