Swift macOS:如何粘贴到另一个应用程序中

时间:2021-09-24 21:17:40

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.

我正在使用swift为Mac OS 10.10+编写一个辅助应用程序。我需要能够将通用NSPasteboard中的内容粘贴到之前处于活动状态的应用程序中。

Just to make it extra clear: I need to paste into another application.

只是为了说清楚:我需要粘贴到另一个应用程序中。

It should work like this:

它应该像这样工作:

  1. The user is using some random application

    用户正在使用一些随机应用程序

  2. 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)

    因为用户因残疾而无法按下cmd + v,所以他们会做出一个手势来激活我的应用程序(此部分已完成并且超出了此问题的范围)

  3. 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.

    我的应用程序变为活动状态,现在我需要在用户事先使用的应用程序中模拟粘贴操作。这是我不知道该怎么办。

  4. Finally the previously active app needs to become active again.

    最后,以前活跃的应用程序需要再次激活。

Please bare in mind the app is to be submitted to the AppStore.

请记住,该应用程序将提交给AppStore。

3 个解决方案

#1


4  

At its simplest you need to do two things:

最简单的你需要做两件事:

  1. Subscribe to notifications of the current application changing so you know what application you should send events to.

    订阅当前应用程序更改的通知,以便您知道应将事件发送到哪个应用程序。

  2. Simulate a Cmd+V keypress using System Events.

    使用系统事件模拟Cmd + V按键。

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:

然而,坏消息是,正如您所说,您需要在App Store提交的应用中执行此操作,您的应用需要位于沙箱内,并且需要临时授权com.apple.security.temporary-exception.apple-events发送系统事件直接引用Apple文档的事件:

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.

请求Finder和系统事件的必要Apple事件临时例外权利可能会导致在应用审核过程中被拒绝,因为授予对这些进程的访问权限可以让您的应用程序免费控制大部分操作系统。对于系统级任务,请使用其他方法,如上所述。

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.

还有一种方法可以使用辅助技术完成第2步,但这也不适用于沙箱。从根本上说,你想要做的事情(激活一个外部的任意应用程序,并控制它)几乎是沙盒的设计阻止你做什么。

Fortunately, since macOS 10.8 there is now NSUserAppleScriptTask, which is executed outside of the sandbox.

幸运的是,因为macOS 10.8现在有NSUserAppleScriptTask,它在沙箱之外执行。

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.

NSUserAppleScriptTask有一个变化:使用NSUserAppleScriptTask执行的脚本需要放在应用程序的脚本目录中,并且您的应用程序无法写入它,只能从中读取。

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.

这篇objc.io文章展示了一个示例,说明如何请求对脚本目录进行安全范围写入访问,以便能够将脚本编写到脚本目录中;对于你自己和你的用户来说,你需要第一次打开一个打开的对话框,因此,你需要存储安全范围的书签,这样你就不需要重复练习了,但那是你能做到的最好的在沙箱内做。

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).

根据我的知识跳过NSUserAppleScriptTask的唯一其他途径就是说服Apple,这是一个非常好的主意,接受你的应用程序的临时权利,允许它编写系统事件脚本(我不会屏住呼吸)。

#2


2  

I found these commands to simulate cut/copy/paste using Foundation:

我发现这些命令可以使用Foundation模拟剪切/复制/粘贴:

func pastematchstyle () {

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);

// event2?.flags = [CGEventFlags.maskCommand,CGEventFlags.maskShift,CGEventFlags.maskAlternate] event2?.post(tap:CGEventTapLocation.cghidEventTap);

}

func paste () {

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)

// event2?.flags = CGEventFlags.maskCommand event2?.post(tap:CGEventTapLocation.cghidEventTap)

}

func pasteresults () {

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);

// event2?.flags = [CGEventFlags.maskCommand,CGEventFlags.maskShift]; event2?.post(tap:CGEventTapLocation.cghidEventTap);

}

func cut() {

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);

// event2?.flags = CGEventFlags.maskCommand; event2?.post(tap:CGEventTapLocation.cghidEventTap);

}

func copy() {

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);

// event2?.flags = CGEventFlags.maskCommand; event2?.post(tap:CGEventTapLocation.cghidEventTap);

}

func copystyle() {

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() {

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);

}

#3


-1  

I'm pretty sure you can do this with Apple Events, but don't forget to set up a temporary exception entitlement to be able to message another app.

我很确定你可以用Apple Events做到这一点,但不要忘记设置一个临时的例外权利,以便能够发送另一个应用程序。

#1


4  

At its simplest you need to do two things:

最简单的你需要做两件事:

  1. Subscribe to notifications of the current application changing so you know what application you should send events to.

    订阅当前应用程序更改的通知,以便您知道应将事件发送到哪个应用程序。

  2. Simulate a Cmd+V keypress using System Events.

    使用系统事件模拟Cmd + V按键。

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:

然而,坏消息是,正如您所说,您需要在App Store提交的应用中执行此操作,您的应用需要位于沙箱内,并且需要临时授权com.apple.security.temporary-exception.apple-events发送系统事件直接引用Apple文档的事件:

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.

请求Finder和系统事件的必要Apple事件临时例外权利可能会导致在应用审核过程中被拒绝,因为授予对这些进程的访问权限可以让您的应用程序免费控制大部分操作系统。对于系统级任务,请使用其他方法,如上所述。

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.

还有一种方法可以使用辅助技术完成第2步,但这也不适用于沙箱。从根本上说,你想要做的事情(激活一个外部的任意应用程序,并控制它)几乎是沙盒的设计阻止你做什么。

Fortunately, since macOS 10.8 there is now NSUserAppleScriptTask, which is executed outside of the sandbox.

幸运的是,因为macOS 10.8现在有NSUserAppleScriptTask,它在沙箱之外执行。

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.

NSUserAppleScriptTask有一个变化:使用NSUserAppleScriptTask执行的脚本需要放在应用程序的脚本目录中,并且您的应用程序无法写入它,只能从中读取。

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.

这篇objc.io文章展示了一个示例,说明如何请求对脚本目录进行安全范围写入访问,以便能够将脚本编写到脚本目录中;对于你自己和你的用户来说,你需要第一次打开一个打开的对话框,因此,你需要存储安全范围的书签,这样你就不需要重复练习了,但那是你能做到的最好的在沙箱内做。

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).

根据我的知识跳过NSUserAppleScriptTask的唯一其他途径就是说服Apple,这是一个非常好的主意,接受你的应用程序的临时权利,允许它编写系统事件脚本(我不会屏住呼吸)。

#2


2  

I found these commands to simulate cut/copy/paste using Foundation:

我发现这些命令可以使用Foundation模拟剪切/复制/粘贴:

func pastematchstyle () {

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);

// event2?.flags = [CGEventFlags.maskCommand,CGEventFlags.maskShift,CGEventFlags.maskAlternate] event2?.post(tap:CGEventTapLocation.cghidEventTap);

}

func paste () {

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)

// event2?.flags = CGEventFlags.maskCommand event2?.post(tap:CGEventTapLocation.cghidEventTap)

}

func pasteresults () {

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);

// event2?.flags = [CGEventFlags.maskCommand,CGEventFlags.maskShift]; event2?.post(tap:CGEventTapLocation.cghidEventTap);

}

func cut() {

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);

// event2?.flags = CGEventFlags.maskCommand; event2?.post(tap:CGEventTapLocation.cghidEventTap);

}

func copy() {

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);

// event2?.flags = CGEventFlags.maskCommand; event2?.post(tap:CGEventTapLocation.cghidEventTap);

}

func copystyle() {

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() {

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);

}

#3


-1  

I'm pretty sure you can do this with Apple Events, but don't forget to set up a temporary exception entitlement to be able to message another app.

我很确定你可以用Apple Events做到这一点,但不要忘记设置一个临时的例外权利,以便能够发送另一个应用程序。