通过命令行将文件 URL 和参数发送到 (运行) macOS 应用程序
Send file URL and args to (running) macOS app via command line
我一直在尝试创建一种方法来告诉我的 (运行) macOS 应用程序打开一些文件并为命令提供一些额外的参数。
对于冷启动应用,使用
$ open MyApp.app fileA.txt --args --foo-arg
将启动该应用程序,我将能够通过 UserDefaults
/CommandLine
/ProcessInfo
检查 --foo-arg
。但是,如果应用程序已经是 运行,--foo-arg
从 UserDefaults
/ProcessInfo
/CommandLine
.
中丢失
我一直在努力寻找解决方案,因为我有一些要求让事情变得有点困难。
要求
- 发送到应用程序的文件路径必须opened/saved具有沙盒权限
- 参数和文件路径必须同时被app拦截。
可能的解决方案
XPC
有些人建议我使用 XPC,但在阅读之后,我不确定该解决方案的外观如何?
- 我是否必须创建一个始终 运行 的 Launch Agent app-companion,以便它可以检测命令行操作并将其传递到我的应用程序?
- 由于每个进程都有自己的权限,这如何与沙盒一起工作?
Apple 脚本
- 我是否应该使用 Apple 脚本告诉我的应用程序打开带有参数的这些文件,从而绕过沙盒功能?
- 通过 AppleScript 打开文件时,我可以保存这些文件吗?
URL 方案
我可以注册我的应用程序以拥有自己的 URL 方案,但 NSApplicationDelegate 处理传入 URLs 的方式分为两批。首先,它可以打开的URL,其次是URL方案或它无法打开的文件路径。即:
open -a MyApp.app myapp:foo; open -a MyApp.app file.txt
我可能可以完成这项工作,但它有点俗气,我真的想以正确的方式做到这一点。
一个命令行工具可以提取它的参数并将它们转换为 Apple Events 是可行的方法。通过安装 BBEdit 命令行工具,然后在终端 window.[=] 中 运行ning man bbedit
或 man bbdiff
,您可以从用户的角度了解这是如何工作的32=]
从命令行工具的角度来看,“有趣”的部分是:
确定应用程序是否 运行ning:+[NSRunningApplication runningApplicationsWithBundleIdentifier:]
将对此有所帮助。
如果应用是不是运行ning,那么使用-[NSWorkspaceURLForApplicationWithBundleIdentifier:]
先通过bundle ID定位应用,然后-[NSWorkspace launchApplicationAtURL:options:configuration:error:]
启动应用程序。这将 return 一个 NSRunningApplication
实例,或者 NIL 和一个错误。 (确保处理错误情况。)
使用从步骤 1 或步骤 2 获得的 NSRunningApplication
实例,您现在可以使用 NSAppleEventDescriptor
APIs 或低级 AppleEvent C APIs 构造一个事件。 (更高级别的 API 可能更容易使用。)
那会是这样的:
使用 运行ning 应用程序中的 processIdentifier
构建目标描述符:
targetDesc = [NSAppleEventDescriptor descriptorWithProcessIdentifier: myRunningApplication.processIdentifier;
构造一个“打开文档”事件,发送给您的目标应用程序:
event = [NSAppleEventDescriptor appleEventWithEventClass: kCoreEventClass eventID: kAEOpenDocuments targetDescriptor: targetDesc returnID: kAutoGenerateReturnID transactionID: kAnyTransactionID];
注意:我使用 kCoreEventClass
/kAEOpenDocuments
作为示例 - 如果您尝试打开一个或多个包含附加信息的文件,那很好。如果您正在做一些其他工作,那么您应该为特定于您的应用程序的事件 class 发明一个四字符代码,以及一个对于您请求的操作唯一的四字符事件 ID .
将命令参数添加到事件中。对于每个参数,这包括根据参数的内部类型(布尔值、整数、字符串、文件 URL)创建适当的描述符,然后使用关键字参数将其添加到事件中。
(Apple Event“关键字”是一个四字符代码。您可以发明自己的代码,但有限制(不要使用全小写字母,您可以使用 AEDataModel.h
或 AERegistry.h
满足您需求的地方)。
对于您创建的每个描述符,使用 -[setParamDescriptor: forKeyword:]
:
将其添加到事件中
myURLParamDesc = [NSAppleEventDescriptor descriptorWithFileURL: myFileURL];
[event setParamDescriptor: myURLParamDesc forKey: kMyFileParamKeyword];
将所有参数添加到事件后,发送它:
[event sendWithOptions: kAENoReply timeout: FLOAT_MAX error: &error];
在应用程序方面,您需要使用 -[NSAppleEventManager setEventHandler: andSelector: forEventClass: andID:]
。这将为您的自定义事件 class 和您在上面发明的 ID 调用,此时您可以使用描述符 APIs 将事件分开并 运行 您的操作。
沙盒会自行处理:您的应用程序会自动为通过 Apple Events 传递的文件获取沙盒扩展。
您的命令行工具未沙盒化 -- 不可能,因为它 运行 来自终端和(可能)其他非沙盒化应用程序。
但是,该工具必须使用强化的 运行时间、com.apple.security.automation.apple-events = YES
和 com.apple.security.temporary-exception.apple-events
命名您的应用程序的包标识符进行签名,以便该工具可以发送 Apple 事件到您的应用程序。
(并且该工具将需要带有 NSAppleEventsUsageDescription
字符串的 Info.plist。)
我留了相当多的钱作为 reader 的练习;但希望这能让你入门。
我一直在尝试创建一种方法来告诉我的 (运行) macOS 应用程序打开一些文件并为命令提供一些额外的参数。
对于冷启动应用,使用
$ open MyApp.app fileA.txt --args --foo-arg
将启动该应用程序,我将能够通过 UserDefaults
/CommandLine
/ProcessInfo
检查 --foo-arg
。但是,如果应用程序已经是 运行,--foo-arg
从 UserDefaults
/ProcessInfo
/CommandLine
.
我一直在努力寻找解决方案,因为我有一些要求让事情变得有点困难。
要求
- 发送到应用程序的文件路径必须opened/saved具有沙盒权限
- 参数和文件路径必须同时被app拦截。
可能的解决方案
XPC
有些人建议我使用 XPC,但在阅读之后,我不确定该解决方案的外观如何?
- 我是否必须创建一个始终 运行 的 Launch Agent app-companion,以便它可以检测命令行操作并将其传递到我的应用程序?
- 由于每个进程都有自己的权限,这如何与沙盒一起工作?
Apple 脚本
- 我是否应该使用 Apple 脚本告诉我的应用程序打开带有参数的这些文件,从而绕过沙盒功能?
- 通过 AppleScript 打开文件时,我可以保存这些文件吗?
URL 方案
我可以注册我的应用程序以拥有自己的 URL 方案,但 NSApplicationDelegate 处理传入 URLs 的方式分为两批。首先,它可以打开的URL,其次是URL方案或它无法打开的文件路径。即:
open -a MyApp.app myapp:foo; open -a MyApp.app file.txt
我可能可以完成这项工作,但它有点俗气,我真的想以正确的方式做到这一点。
一个命令行工具可以提取它的参数并将它们转换为 Apple Events 是可行的方法。通过安装 BBEdit 命令行工具,然后在终端 window.[=] 中 运行ning man bbedit
或 man bbdiff
,您可以从用户的角度了解这是如何工作的32=]
从命令行工具的角度来看,“有趣”的部分是:
确定应用程序是否 运行ning:
+[NSRunningApplication runningApplicationsWithBundleIdentifier:]
将对此有所帮助。如果应用是不是运行ning,那么使用
-[NSWorkspaceURLForApplicationWithBundleIdentifier:]
先通过bundle ID定位应用,然后-[NSWorkspace launchApplicationAtURL:options:configuration:error:]
启动应用程序。这将 return 一个NSRunningApplication
实例,或者 NIL 和一个错误。 (确保处理错误情况。)使用从步骤 1 或步骤 2 获得的
NSRunningApplication
实例,您现在可以使用NSAppleEventDescriptor
APIs 或低级 AppleEvent C APIs 构造一个事件。 (更高级别的 API 可能更容易使用。)
那会是这样的:
使用 运行ning 应用程序中的
processIdentifier
构建目标描述符:targetDesc = [NSAppleEventDescriptor descriptorWithProcessIdentifier: myRunningApplication.processIdentifier;
构造一个“打开文档”事件,发送给您的目标应用程序:
event = [NSAppleEventDescriptor appleEventWithEventClass: kCoreEventClass eventID: kAEOpenDocuments targetDescriptor: targetDesc returnID: kAutoGenerateReturnID transactionID: kAnyTransactionID];
注意:我使用
kCoreEventClass
/kAEOpenDocuments
作为示例 - 如果您尝试打开一个或多个包含附加信息的文件,那很好。如果您正在做一些其他工作,那么您应该为特定于您的应用程序的事件 class 发明一个四字符代码,以及一个对于您请求的操作唯一的四字符事件 ID .将命令参数添加到事件中。对于每个参数,这包括根据参数的内部类型(布尔值、整数、字符串、文件 URL)创建适当的描述符,然后使用关键字参数将其添加到事件中。
(Apple Event“关键字”是一个四字符代码。您可以发明自己的代码,但有限制(不要使用全小写字母,您可以使用
AEDataModel.h
或AERegistry.h
满足您需求的地方)。对于您创建的每个描述符,使用
将其添加到事件中-[setParamDescriptor: forKeyword:]
:myURLParamDesc = [NSAppleEventDescriptor descriptorWithFileURL: myFileURL]; [event setParamDescriptor: myURLParamDesc forKey: kMyFileParamKeyword];
将所有参数添加到事件后,发送它:
[event sendWithOptions: kAENoReply timeout: FLOAT_MAX error: &error];
在应用程序方面,您需要使用 -[NSAppleEventManager setEventHandler: andSelector: forEventClass: andID:]
。这将为您的自定义事件 class 和您在上面发明的 ID 调用,此时您可以使用描述符 APIs 将事件分开并 运行 您的操作。
沙盒会自行处理:您的应用程序会自动为通过 Apple Events 传递的文件获取沙盒扩展。
您的命令行工具未沙盒化 -- 不可能,因为它 运行 来自终端和(可能)其他非沙盒化应用程序。
但是,该工具必须使用强化的 运行时间、com.apple.security.automation.apple-events = YES
和 com.apple.security.temporary-exception.apple-events
命名您的应用程序的包标识符进行签名,以便该工具可以发送 Apple 事件到您的应用程序。
(并且该工具将需要带有 NSAppleEventsUsageDescription
字符串的 Info.plist。)
我留了相当多的钱作为 reader 的练习;但希望这能让你入门。