Skip to content

Commit

Permalink
[coregraphics] make about event emulation work
Browse files Browse the repository at this point in the history
  • Loading branch information
umjammer committed Mar 24, 2024
1 parent d68c91d commit a63e97c
Show file tree
Hide file tree
Showing 4 changed files with 266 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
import com.sun.jna.Native;
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer;
import com.sun.jna.platform.mac.CoreFoundation.CFArrayRef;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.NativeLongByReference;
import com.sun.jna.ptr.ShortByReference;
import org.rococoa.carbon.CarbonCoreLibrary;
import org.rococoa.cocoa.CFIndex;
import org.rococoa.cocoa.CGFloat;
Expand Down Expand Up @@ -190,6 +190,9 @@ interface CGRectEdge {

// enum CGEventFlags
long kCGEventFlagMaskCommand = NX_COMMANDMASK;
long kCGEventFlagMaskAlternate = NX_ALTERNATEMASK;
long kCGEventFlagMaskControl = NX_CONTROLMASK;
long kCGEventFlagMaskShift = NX_SHIFTMASK;

long kCGEventMaskForAllEvents = 0xFFFF_FFFF_FFFF_FFFFL;

Expand Down Expand Up @@ -264,7 +267,19 @@ interface CGEventTapCallBack extends Callback {
/** Returns a Quartz event source created with a specified source state. */
Pointer /* CGEventSourceRef */ CGEventSourceCreate(int /* CGEventSourceStateID */ stateID);

// CGKeyCode
int kCGMouseEventDeltaX = 4;
int kCGMouseEventDeltaY = 5;

/* Sets the integer value of a field in a Quartz event. */
void CGEventSetIntegerValueField(Pointer /* CGEventRef */ event, int /* CGEventField */ field, long value);

int kCGWindowListOptionOnScreenOnly = 1 << 0;
int kCGNullWindowID = 0;

/** Generates and returns information about the selected windows in the current user session. */
CFArrayRef CGWindowListCopyWindowInfo(int /* CGWindowListOption */ option, int /* CGWindowID */ relativeToWindow);

//#region CGKeyCode

/**
* Returns string representation of key, if it is printable.
Expand All @@ -273,10 +288,10 @@ interface CGEventTapCallBack extends Callback {
* @see "https://stackoverflow.com/a/1971027"
*/
private static CFStringRef createStringForKey(char /* CGKeyCode */ keyCode) {
Pointer /* TISInputSourceRef */ currentKeyboard = CarbonCoreLibrary.library.TISCopyCurrentKeyboardInputSource();
logger.fine("currentKeyboard: " + currentKeyboard);// + ", " + kTISPropertyUnicodeKeyLayoutData);
Pointer /* TISInputSourceRef */ currentKeyboard = CarbonCoreLibrary.library.TISCopyCurrentKeyboardLayoutInputSource(); // must be *Layout*
logger.finest("currentKeyboard: " + currentKeyboard);// + ", " + kTISPropertyUnicodeKeyLayoutData);
Pointer /* CFDataRef */ layoutData = CarbonCoreLibrary.library.TISGetInputSourceProperty(currentKeyboard, CFStringRef.toCFString("TISPropertyUnicodeKeyLayoutData"));
logger.finer("layoutData: " + layoutData);
logger.finest("layoutData: " + layoutData);
Pointer /* UCKeyboardLayout */ keyboardLayout = CoreFoundation.library.CFDataGetBytePtr(layoutData);

IntByReference keysDown = new IntByReference(0);
Expand Down Expand Up @@ -344,6 +359,9 @@ private static CFStringRef createStringForKey(char /* CGKeyCode */ keyCode) {
// /** Returns a point with the specified coordinates. */
// /* inline */ CGPoint CGPointMake(CGFloat x, CGFloat y);

/** Sets the event type of a Quartz event (left mouse down, for example). */
void CGEventSetType(Pointer /* CGEventRef */ event, int /* CGEventType */ type);

// CGMouseButton
int kCGMouseButtonLeft = 0;
int kCGMouseButtonRight = 1;
Expand All @@ -364,4 +382,10 @@ private static CFStringRef createStringForKey(char /* CGKeyCode */ keyCode) {

/** Returns a new Quartz mouse event. */
Pointer /* CGEventRef */ CGEventCreateMouseEvent(Pointer /* CGEventSourceRef */ source, int /* CGEventType */ mouseType, CGPoint mouseCursorPosition, int /* CGMouseButton */ mouseButton);

int kCGScrollEventUnitPixel = 0;
int kCGScrollEventUnitLine = 1;

/** Returns a new Quartz scrolling event. */
Pointer /* CGEventRef */ CGEventCreateScrollWheelEvent2(Pointer /* CGEventSourceRef */ source, int /* CGScrollEventUnit */ units, int wheelCount, int wheel1, int wheel2, int wheel3);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,86 +6,258 @@

package org.rococoa.cocoa.coregraphics;

import java.awt.MouseInfo;
import java.awt.Point;

import com.sun.jna.Pointer;
import org.rococoa.cocoa.corefoundation.CoreFoundation;

import static org.rococoa.carbon.CarbonCoreLibrary.kVK_Command;
import static org.rococoa.carbon.CarbonCoreLibrary.kVK_Control;
import static org.rococoa.carbon.CarbonCoreLibrary.kVK_Option;
import static org.rococoa.carbon.CarbonCoreLibrary.kVK_Shift;
import static org.rococoa.cocoa.coregraphics.CoreGraphicsLibrary.kCGEventFlagMaskAlternate;
import static org.rococoa.cocoa.coregraphics.CoreGraphicsLibrary.kCGEventFlagMaskCommand;
import static org.rococoa.cocoa.coregraphics.CoreGraphicsLibrary.kCGEventFlagMaskControl;
import static org.rococoa.cocoa.coregraphics.CoreGraphicsLibrary.kCGEventFlagMaskShift;
import static org.rococoa.cocoa.coregraphics.CoreGraphicsLibrary.kCGEventLeftMouseDown;
import static org.rococoa.cocoa.coregraphics.CoreGraphicsLibrary.kCGEventLeftMouseUp;
import static org.rococoa.cocoa.coregraphics.CoreGraphicsLibrary.kCGEventMouseMoved;
import static org.rococoa.cocoa.coregraphics.CoreGraphicsLibrary.kCGEventRightMouseDown;
import static org.rococoa.cocoa.coregraphics.CoreGraphicsLibrary.kCGEventRightMouseUp;
import static org.rococoa.cocoa.coregraphics.CoreGraphicsLibrary.kCGEventScrollWheel;
import static org.rococoa.cocoa.coregraphics.CoreGraphicsLibrary.kCGEventSourceStateHIDSystemState;
import static org.rococoa.cocoa.coregraphics.CoreGraphicsLibrary.kCGHIDEventTap;
import static org.rococoa.cocoa.coregraphics.CoreGraphicsLibrary.kCGMouseButtonLeft;
import static org.rococoa.cocoa.coregraphics.CoreGraphicsLibrary.kCGMouseEventDeltaX;
import static org.rococoa.cocoa.coregraphics.CoreGraphicsLibrary.kCGMouseEventDeltaY;
import static org.rococoa.cocoa.coregraphics.CoreGraphicsLibrary.kCGScrollEventUnitPixel;
import static org.rococoa.cocoa.coregraphics.CoreGraphicsLibrary.library;


/** */
/**
* RococaRobot. like {@link java.awt.Robot}.
*
* @author <a href="mailto:umjammer@gmail.com">Naohide Sano</a> (nsano)
* @version 0.00 2024-03-22 nsano initial version <br>
*/
public class RococaRobot {

/** HID event source */
private final Pointer /* CGEventSourceRef */ src = library.CGEventSourceCreate(kCGEventSourceStateHIDSystemState);

/** Constructs a Robot object. */
public RococaRobot() {
Runtime.getRuntime().addShutdownHook(new Thread(() -> CoreFoundation.library.CFRelease(src)));

Point p = MouseInfo.getPointerInfo().getLocation();
prev = new CGPoint(p.x, p.y);
}

/** */
/** */
private boolean command;
/** */
private boolean option;
/** */
private boolean control;
/** */
private boolean shift;

/**
* Presses a given key. The key should be released using the keyRelease method.
* @param code get by karabiner-eventviewer etc.
*/
public void keyPress(int code) {
Pointer /* CGEventRef */ d = library.CGEventCreateKeyboardEvent(src, (char) code, true);
if (code == kVK_Command) {
command = true;
} else if (code == kVK_Option) {
option = true;
} else if (code == kVK_Control) {
control = true;
} else if (code == kVK_Shift) {
shift = true;
} else {
Pointer /* CGEventRef */ event = library.CGEventCreateKeyboardEvent(src, (char) code, true);

library.CGEventSetFlags(d, kCGEventFlagMaskCommand);
long flags = 0;
if (command) flags |= kCGEventFlagMaskCommand;
if (option) flags |= kCGEventFlagMaskAlternate;
if (control) flags |= kCGEventFlagMaskControl;
if (shift) flags |= kCGEventFlagMaskShift;
library.CGEventSetFlags(event, flags);

int /* CGEventTapLocation */ loc = kCGHIDEventTap; // kCGSessionEventTap also works
library.CGEventPost(loc, d);
library.CGEventPost(kCGHIDEventTap, event);

CoreFoundation.library.CFRelease(d);
CoreFoundation.library.CFRelease(event);
}
}

/** */
/**
* Presses a given key. The key should be released using the keyRelease method.
* @param code get by karabiner-eventviewer etc.
*/
public void keyPress2(int code) {
Pointer /* CGEventRef */ event = library.CGEventCreateKeyboardEvent(src, (char) code, true);

library.CGEventPost(kCGHIDEventTap, event);

CoreFoundation.library.CFRelease(event);
}

/**
* Releases a given key.
* @param code get by karabiner-eventviewer etc.
*/
public void keyRelease(int code) {
Pointer /* CGEventRef */ u = library.CGEventCreateKeyboardEvent(src, (char) code, false);
if (code == kVK_Command) {
command = false;
} else if (code == kVK_Option) {
option = false;
} else if (code == kVK_Control) {
control = false;
} else if (code == kVK_Shift) {
command = false;
} else {
Pointer /* CGEventRef */ event = library.CGEventCreateKeyboardEvent(src, (char) code, false);

library.CGEventSetFlags(u, kCGEventFlagMaskCommand);
long flags = 0;
if (command) flags |= kCGEventFlagMaskCommand;
if (option) flags |= kCGEventFlagMaskAlternate;
if (control) flags |= kCGEventFlagMaskControl;
if (shift) flags |= kCGEventFlagMaskShift;
library.CGEventSetFlags(event, flags);

int /* CGEventTapLocation */ loc = kCGHIDEventTap; // kCGSessionEventTap also works
library.CGEventPost(loc, u);
library.CGEventPost(kCGHIDEventTap, event);

CoreFoundation.library.CFRelease(u);
CoreFoundation.library.CFRelease(event);
}
}

/**
* Releases a given key.
* @param code get by karabiner-eventviewer etc.
*/
public void keyRelease2(int code) {
Pointer /* CGEventRef */ event = library.CGEventCreateKeyboardEvent(src, (char) code, false);

library.CGEventPost(kCGHIDEventTap, event);

CoreFoundation.library.CFRelease(event);
}

/** the previous pointer */
private CGPoint prev;

/** */
/** Moves mouse pointer to given screen coordinates with moving motion. */
public void mouseMove(int x, int y) {
int dx = x - prev.x.intValue();
int dy = y - prev.y.intValue();
prev = new CGPoint(x, y);
Pointer /* CGEventRef */ move = library.CGEventCreateMouseEvent(
Pointer /* CGEventRef */ event = library.CGEventCreateMouseEvent(
null, kCGEventMouseMoved,
prev,
kCGMouseButtonLeft // ignored
0 // ignored
);
// Now, execute these events with an interval to make them noticeable
library.CGEventPost(kCGHIDEventTap, move);
CoreFoundation.library.CFRelease(move);
library.CGEventSetIntegerValueField(event, kCGMouseEventDeltaX, dx);
library.CGEventSetIntegerValueField(event, kCGMouseEventDeltaY, dy);
library.CGEventPost(kCGHIDEventTap, event);
CoreFoundation.library.CFRelease(event);
}

/** */
/** Moves mouse pointer to given screen coordinates w/o moving motion means like teleportation. */
public void mouseMove0(int x, int y) {
prev = new CGPoint(x, y);
Pointer /* CGEventRef */ event = library.CGEventCreateMouseEvent(
null, kCGEventMouseMoved,
prev,
0 // ignored
);
// Now, execute these events with an interval to make them noticeable
library.CGEventPost(kCGHIDEventTap, event);
CoreFoundation.library.CFRelease(event);
}

/** Moves mouse pointer to given deltas. */
public void mouseMove2(int dx, int dy) {
Pointer /* CGEventRef */ event = library.CGEventCreateMouseEvent(
null, kCGEventMouseMoved,
prev,
0 // ignored
);
// Now, execute these events with an interval to make them noticeable
library.CGEventSetIntegerValueField(event, kCGMouseEventDeltaX, dx);
library.CGEventSetIntegerValueField(event, kCGMouseEventDeltaY, dy);
library.CGEventPost(kCGHIDEventTap, event);
CoreFoundation.library.CFRelease(event);
}

/**
* Presses one or more mouse buttons. The mouse buttons should be released using the mouseRelease(int) method.
* @param buttons kCGMouseButtonLeft, kCGMouseButtonRight
*/
public void mousePress(int buttons) {
Pointer /* CGEventRef */ click_down = library.CGEventCreateMouseEvent(
null, kCGEventLeftMouseDown,
int[] events = {kCGEventLeftMouseDown, kCGEventRightMouseDown};
Pointer /* CGEventRef */ event = library.CGEventCreateMouseEvent(
null, events[buttons],
prev,
buttons
);
library.CGEventPost(kCGHIDEventTap, event);
CoreFoundation.library.CFRelease(event);
}

/**
* Presses one or more mouse buttons. The mouse buttons should be released using the mouseRelease(int) method.
* @param buttons kCGMouseButtonLeft, kCGMouseButtonRight
*/
public void mousePress2(int buttons, int x, int y) {
prev = new CGPoint(x, y);
int[] events = {kCGEventLeftMouseDown, kCGEventRightMouseDown};
Pointer /* CGEventRef */ event = library.CGEventCreateMouseEvent(
null, events[buttons],
prev,
kCGMouseButtonLeft
buttons
);
library.CGEventPost(kCGHIDEventTap, click_down);
CoreFoundation.library.CFRelease(click_down);
library.CGEventPost(kCGHIDEventTap, event);
CoreFoundation.library.CFRelease(event);
}

/** */
/**
* Releases one or more mouse buttons using previous point.
* @param buttons kCGMouseButtonLeft, kCGMouseButtonRight
*/
public void mouseRelease(int buttons) {
Pointer /* CGEventRef */ click_up = library.CGEventCreateMouseEvent(
null, kCGEventLeftMouseUp,
int[] events = {kCGEventLeftMouseUp, kCGEventRightMouseUp};
Pointer /* CGEventRef */ event = library.CGEventCreateMouseEvent(
null, events[buttons],
prev,
kCGMouseButtonLeft
buttons
);
library.CGEventPost(kCGHIDEventTap, click_up);
CoreFoundation.library.CFRelease(click_up);
library.CGEventPost(kCGHIDEventTap, event);
CoreFoundation.library.CFRelease(event);
}
/**
* Releases one or more mouse buttons.
* @param buttons kCGMouseButtonLeft, kCGMouseButtonRight
*/
public void mouseRelease2(int buttons, int x, int y) {
prev = new CGPoint(x, y);
int[] events = {kCGEventLeftMouseUp, kCGEventRightMouseUp};
Pointer /* CGEventRef */ event = library.CGEventCreateMouseEvent(
null, events[buttons],
prev,
buttons
);
library.CGEventPost(kCGHIDEventTap, event);
CoreFoundation.library.CFRelease(event);
}

/** Rotates the scroll wheel on wheel-equipped mice. */
public void mouseWheel(int wheelAmt) {
Pointer /* CGEventRef */ event = library.CGEventCreateScrollWheelEvent2(
null, kCGScrollEventUnitPixel, 1, wheelAmt, 0, 0);
library.CGEventSetType(event, kCGEventScrollWheel);
library.CGEventPost(kCGHIDEventTap, event);
CoreFoundation.library.CFRelease(event);
}
}
Loading

0 comments on commit a63e97c

Please sign in to comment.