I'm writing an assistive application for Mac OS 10.10+, using swift. I need to be able to paste the content from the general NSPasteboard into the application which had previously been active.
Just to make it extra clear: I need to paste into another application.
It should work like this:
The user is using some random application
Because the user can't press cmd+v due to disability, they make a gesture which activates my app (this part is done and it's outside the scope of this question)
My app becomes active and now I need to simulate a Paste action in the app the user has been using beforehand. This is the bit I don't know how to do.
Finally the previously active app needs to become active again.
Please bare in mind the app is to be submitted to the AppStore.
At its simplest you need to do two things:
Subscribe to notifications of the current application changing so you know what application you should send events to.
Simulate a Cmd+V keypress using System Events.
However, the bad news is, as you say you need to do this in an App Store submitted app, your app needs to be inside the sandbox and requires the temporary entitlement com.apple.security.temporary-exception.apple-events
to send events to System Events directly quoting the Apple documentation:
requesting the necessary apple-events temporary exception entitlements for the Finder and System Events will likely result in rejection during the app review process, because granting access to these processes gives your app free rein over much of the operating system. For system-level tasks, use other methods, as discussed above.
There is also a way to do the step #2 using assistive technologies, but that too will not work inside the sandbox. Fundamentally, what you are trying to do (to activate an external arbitrary app, and to control it) is pretty much bang on what the sandbox is designed to prevent you from doing.
Fortunately, since macOS 10.8 there is now NSUserAppleScriptTask, which is executed outside of the sandbox.
There is a twist to NSUserAppleScriptTask
: scripts executed with NSUserAppleScriptTask
need to be placed inside the application's script directory, and your application cannot write to it, only read from it.
This objc.io article shows an example of how you can request for security scoped writing access to the scripting directory to be able to write your script to it; Annoyingly to yourself and your user you need to bring up an open dialog on the first time, therefore, and you need to store the security scoped bookmark so you don't need to repeat the exercise, but that's the best you'll be able to do inside the sandbox.
Your only other route beside jumping the NSUserAppleScriptTask
hoops to my knowledge is to convince Apple that it's a really good idea to accept your app with the temporary entitlement that allows it to script System Events (I would not hold my breath).
I found these commands to simulate cut/copy/paste using Foundation:
func pastematchstyle () {
let event1 = CGEvent(keyboardEventSource: nil, virtualKey: 0x09, keyDown: true); // opt-shft-cmd-v down
event1?.flags = [CGEventFlags.maskCommand, CGEventFlags.maskShift, CGEventFlags.maskAlternate]
event1?.post(tap: CGEventTapLocation.cghidEventTap);
let event2 = CGEvent(keyboardEventSource: nil, virtualKey: 0x09, keyDown: false); // opt-shf-cmd-v up
// event2?.flags = [CGEventFlags.maskCommand, CGEventFlags.maskShift, CGEventFlags.maskAlternate]
event2?.post(tap: CGEventTapLocation.cghidEventTap);
}
func paste () {
let event1 = CGEvent(keyboardEventSource: nil, virtualKey: 0x09, keyDown: true); // cmd-v down
event1?.flags = CGEventFlags.maskCommand;
event1?.post(tap: CGEventTapLocation.cghidEventTap);
let event2 = CGEvent(keyboardEventSource: nil, virtualKey: 0x09, keyDown: false) // cmd-v up
// event2?.flags = CGEventFlags.maskCommand
event2?.post(tap: CGEventTapLocation.cghidEventTap)
}
func pasteresults () {
let event1 = CGEvent(keyboardEventSource: nil, virtualKey: 0x09, keyDown: true); // shft-cmd-v down
event1?.flags = [CGEventFlags.maskCommand, CGEventFlags.maskShift]
event1?.post(tap: CGEventTapLocation.cghidEventTap);
let event2 = CGEvent(keyboardEventSource: nil, virtualKey: 0x09, keyDown: false); // shf-cmd-v up
// event2?.flags = [CGEventFlags.maskCommand, CGEventFlags.maskShift];
event2?.post(tap: CGEventTapLocation.cghidEventTap);
}
func cut() {
let event1 = CGEvent(keyboardEventSource: nil, virtualKey: 0x07, keyDown: true); // cmd-x down
event1?.flags = CGEventFlags.maskCommand;
event1?.post(tap: CGEventTapLocation.cghidEventTap);
let event2 = CGEvent(keyboardEventSource: nil, virtualKey: 0x07, keyDown: false); // cmd-x up
// event2?.flags = CGEventFlags.maskCommand;
event2?.post(tap: CGEventTapLocation.cghidEventTap);
}
func copy() {
let event1 = CGEvent(keyboardEventSource: nil, virtualKey: 0x08, keyDown: true); // cmd-c down
event1?.flags = CGEventFlags.maskCommand;
event1?.post(tap: CGEventTapLocation.cghidEventTap);
let event2 = CGEvent(keyboardEventSource: nil, virtualKey: 0x08, keyDown: false); // cmd-c up
// event2?.flags = CGEventFlags.maskCommand;
event2?.post(tap: CGEventTapLocation.cghidEventTap);
}
func copystyle() {
let event1 = CGEvent(keyboardEventSource: nil, virtualKey: 0x08, keyDown: true); // opt-cmd-c down
event1?.flags = [CGEventFlags.maskCommand, CGEventFlags.maskAlternate];
event1?.post(tap: CGEventTapLocation.cghidEventTap);
let event2 = CGEvent(keyboardEventSource: nil, virtualKey: 0x08, keyDown: false); // opt-cmd-c up
// event2?.flags = CGEventFlags.maskCommand;
event2?.post(tap: CGEventTapLocation.cghidEventTap);
}
func pastestyle() {
let event1 = CGEvent(keyboardEventSource: nil, virtualKey: 0x07, keyDown: true); // opt-cmd-v down
event1?.flags = [CGEventFlags.maskCommand, CGEventFlags.maskAlternate];
event1?.post(tap: CGEventTapLocation.cghidEventTap);
let event2 = CGEvent(keyboardEventSource: nil, virtualKey: 0x07, keyDown: false); // opt-cmd-v up
// event2?.flags = CGEventFlags.maskCommand;
event2?.post(tap: CGEventTapLocation.cghidEventTap);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With