Accessing the pasteboard directly looks to be a no-go. First, launchd won't register the processes1 with the pasteboard server's mach port. You'd first need find a way to get the pasteboard server's mach port (mach_port_names
?). Also, direct communication between user sessions is prohibited2, and other communication is limited. I'm not sure if your program will have the rights to connect to the pasteboard server.
Here's a first shot at an illustrative example on using Apple events to get & set the clipboard as a string. Error handling is minimal to nonexistent (I'm not certain how I feel about require_noerr
). If you're going to get/set clipboard data multiple times during a run, you can save the Apple events and, when copying to the clipboard, use AECreateDesc
& AEPutParamDesc
or (maybe) AEBuildParameters
. AEVTBuilder might be of use.
NSString* paste() {
NSString *content;
AppleEvent paste, reply = { typeNull, 0L };
AEBuildError buildError = { typeNull, 0L };
AEDesc clipDesc = { typeNull, 0L };
OSErr err;
err = AEBuildAppleEvent(kAEJons, kAEGetClipboard,
typeApplicationBundleID, "com.apple.finder", strlen("com.apple.finder"),
kAutoGenerateReturnID, kAnyTransactionID,
&paste, &buildError,
""
);
require_noerr(err, paste_end);
err = AESendMessage(&paste, &reply, kAEWaitReply, kAEDefaultTimeout);
err = AEGetParamDesc(&reply, keyDirectObject, typeUTF8Text, &clipDesc);
require_noerr(err, pastErr_getReply);
Size dataSize = AEGetDescDataSize(&clipDesc);
char* clipData = malloc(dataSize);
if (clipData) {
err = AEGetDescData(&clipDesc, clipData, dataSize);
if (noErr == err) {
content = [NSString stringWithCString:clipData encoding:NSUTF8StringEncoding];
} else {}
free(clipData);
}
AEDisposeDesc(&clipDesc);
pastErr_getReply:
AEDisposeDesc(&reply);
pasteErr_sending:
AEDisposeDesc(&paste);
paste_end:
return content;
}
OSStatus copy(NSString* clip) {
AppleEvent copy, reply = { typeNull, 0L };
AEBuildError buildError = { typeNull, 0L };
OSErr err = AEBuildAppleEvent(kAEJons, kAESetClipboard,
typeApplicationBundleID, "com.apple.finder", strlen("com.apple.finder"),
kAutoGenerateReturnID, kAnyTransactionID,
©, &buildError,
"'----':utf8(@)",
AEPARAMSTR([clip UTF8String])
/*
"'----':obj {form: enum(prop), want: type(@), seld: type(@), from: null()}"
"data:utf8(@)",
AEPARAM(typeUTF8Text),
AEPARAM(pClipboard),
AEPARAMSTR([clip UTF8String])
*/
);
if (aeBuildSyntaxNoErr != buildError.fError) {
return err;
}
AESendMessage(©, &reply, kAENoReply, kAEDefaultTimeout);
AEDisposeDesc(&reply);
AEDisposeDesc(©);
return noErr;
}
I'm leaving the Core Foundation approach above, but you'll probably want to use NSAppleEventDescriptor
to extract the clipboard contents from the Apple Event reply.
err = AESendMessage(&paste, &reply, kAEWaitReply, kAEDefaultTimeout);
require_noerr(err, pasteErr_sending);
// nsReply takes ownership of reply
NSAppleEventDescriptor *nsReply = [[NSAppleEventDescriptor alloc] initWithAEDescNoCopy:&reply];
content = [[nsReply descriptorAtIndex:1] stringValue];
[nsReply release];
pasteErr_sending:
AEDisposeDesc(&paste);
paste_end:
return content;
}
An NSAppleEventDescriptor
is also easier to examine in a debugger than an AEDesc
. To examine replies, you can also to set the AEDebugReceives
environment variable when using osascript or Script Editor.app:
AEDebugReceives=1 osascript -e 'tell application "Finder" to get the clipboard'
References:
- "Configuring User Sessions"
- "Communicating Across Login Sessions"
- Mach Kernel Interface, especially:
- CFMessagePort Reference (mach port wrapper):
- Apple Events Programming Guide
- Apple Event Manager Reference
- AEBuild*, AEPrint* and Friends
- AEBuildAppleEvent on CocoaDev
- Mac OS X Debugging Magic (for AEDebugSends and other AEDebug* environment variables)