Programmatically enter text into any application

2019-07-21 05:49发布

问题:

Is there a proof-of-concept Objective-C executable that enters some text into an application and then clicks the mouse, using Apple events and not AppleScript?

e.g. the AppleScript equivalent of

tell application "System Events"
 tell process "Safari"
  keystroke "Hello World"
  click
 end tell 
end tell

It should work on Mac OS X 10.9, preferably be future oriented (backwards compatibility doesn't matter). The context is that I will be calling the Objective-C code from another language.

I'm saying this because I read that:

As of Mac OS X 10.7, the low-level Cocoa API (NSAppleEventDescriptor) still lacks essential functionality (e.g. the ability to send Apple events), while the high-level Cocoa API (Scripting Bridge) is too flawed and limited to be a viable foundation for an appscript-style wrapper.

and:

NSAppleScript can safely be used only on the main thread

so, my goals are:

  1. any application (by name or if current)
  2. any keyboard input or mouse
  3. from C or Objective-C
  4. within a few hundred milliseconds

thanks!

回答1:

Rather than using AppleEvents, the CGEvent API in CoreGraphics framework <https://developer.apple.com/library/mac/documentation/Carbon/Reference/QuartzEventServicesRef/Reference/reference.html> lets you post low-level mouse and keyboard events to the window server.

#include <CoreGraphics/CoreGraphics.h>

NSArray *launchedApplications = [[NSWorkspace sharedWorkspace] launchedApplications]; // depreciated but I couldn't find a modern way to get the Carbon PSN
NSPredicate *filter = [NSPredicate predicateWithFormat:@"NSApplicationName = \"TextEdit\""];
NSDictionary *appInfo = [[launchedApplications filteredArrayUsingPredicate:filter] firstObject];
ProcessSerialNumber psn;
psn.highLongOfPSN = [[appInfo objectForKey:@"NSApplicationProcessSerialNumberHigh"] unsignedIntValue];
psn.lowLongOfPSN = [[appInfo objectForKey:@"NSApplicationProcessSerialNumberLow"] unsignedIntValue];

CGEventRef event1 = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)6, true); // 'z' key down
CGEventRef event2 = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)6, false); // 'z' key up

CGEventPostToPSN(&psn, event1);
CGEventPostToPSN(&psn, event2);

You might also consider writing a Service <https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/SysServices/introduction.html>, which lets you provide functionality to other applications through the Service menu in the application menu. Note that you can even assign keyboard shortcuts to Service menu items. Services work via the system pasteboard; this approach may be easier than dealing with raw window server events if you simply need to be able to paste some canned or generated data into another application.



回答2:

I'm not sure if this is what you're looking for, but you may be interested in setting up an NSInvocation object:

- (void)invokeWithTarget:(id)anObject

If you're looking to run some code and 'simulate' a UX environment, it may be valuable to save an invocation and run it.

(Automator?)



回答3:

The best way for achieving your result is using Automator,

See https://developer.apple.com/library/mac/documentation/AppleApplications/Conceptual/AutomatorConcepts/AutomatorConcepts.pdf

If you want to achieve this through ObjectiveC, you need to understand "Distributed Objects Architecture". By pairing NSPort and NSInvocation, you can do amazing things, like cross-process and cross-machine method calling.

Here is a guide for that
https://developer.apple.com/librarY/prerelease/mac/documentation/Cocoa/Conceptual/DistrObjects/Concepts/architecture.html