我一直在设置一个
launchd.plist
XML,每次安装特定的 USB 设备时都会运行该 XML。我按照 xpc_events(3) 手册页 上的说明进行操作,只要安装设备,它就会运行应用程序。
我遇到的问题是,只要设备仍处于安装状态,应用程序就会每 10 秒一次又一次地运行。我如何设置它以便在设备插入 USB 端口时它只运行一次?
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.myapp.agent</string>
<key>Program</key>
<string>/Applications/MyApp.app/Contents/MacOS/MyAgent</string>
<key>LaunchEvents</key>
<dict>
<key>com.apple.iokit.matching</key>
<dict>
<key>com.apple.device-attach</key>
<dict>
<key>idVendor</key>
<integer>2316</integer>
<key>idProduct</key>
<integer>4096</integer>
<key>IOProviderClass</key>
<string>IOUSBDevice</string>
<key>IOMatchLaunchStream</key>
<true/>
</dict>
</dict>
<key>com.apple.notifyd.matching</key>
<dict>
<key>com.apple.interesting-notification</key>
<dict>
<key>Notification</key>
<string>com.apple.interesting-notification</string>
</dict>
</dict>
</dict>
</dict>
</plist>
我写了一个tutorial on this,其中包含详细的说明和示例文件,用于通过将外部设备(usb/thunderbolt)连接到 Mac 计算机来触发任意可执行文件或 shell 脚本,而不会出现重生问题。
与作者的方法一样,它依赖于 Apple 的
IOKit
库来检测设备和运行所需可执行文件的守护进程。为了在连接设备后不重复触发守护程序,使用特殊的流处理程序 (xpc_set_event_stream_handler
) 来“消耗”com.apple.iokit.matching
事件,如@ford 的帖子和他的 github 存储库 中所述。
本教程特别介绍了如何编译 xpc 流处理程序以及如何将其与守护进程 plist 文件中的可执行文件一起引用,以及将所有相关文件放置在何处具有正确的权限。
有关文件,请到这里。为了完整起见,我还在下面粘贴了他们的内容。
这里我以在连接到Mac时欺骗以太网适配器的MAC地址为例。这可以推广到任意可执行文件和设备。
适配shell脚本
spoof-mac.sh
#!/bin/bash
ifconfig en12 ether 12:34:56:78:9A:BC
根据您的需要和 使其可执行:
sudo chmod 755 spoof-mac.sh
然后将其移动到
/usr/local/bin
,或其他目录:
cp spoof-mac.sh /usr/local/bin/
流处理程序
xpc_set_event_stream_handler.m
// Created by Ford Parsons on 10/23/17.
// Copyright © 2017 Ford Parsons. All rights reserved.
//
#import <Foundation/Foundation.h>
#include <xpc/xpc.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
xpc_set_event_stream_handler("com.apple.iokit.matching", NULL, ^(xpc_object_t _Nonnull object) {
const char *event = xpc_dictionary_get_string(object, XPC_EVENT_KEY_NAME);
NSLog(@"%s", event);
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if(argc >= 2) {
execv(argv[1], (char **)argv+1);
}
}
}
是通用的(无需适配),可以在 mac 命令行上构建(安装了 xcode):
gcc -framework Foundation -o xpc_set_event_stream_handler xpc_set_event_stream_handler.m
让我们将它放入
/usr/local/bin
,就像守护进程的主要可执行文件一样。
cp xpc_set_event_stream_handler /usr/local/bin/
plist 文件
com.spoofmac.plist
包含将在设备连接触发器上运行可执行文件的守护进程的属性。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UserName</key>
<string>root</string>
<key>StandardErrorPath</key>
<string>/tmp/spoofmac.stderr</string>
<key>StandardOutPath</key>
<string>/tmp/spoofmac.stdout</string>
<key>Label</key>
<string>com.spoofmac.program</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/xpc_set_event_stream_handler</string>
<string>/usr/local/bin/spoofmac.sh</string>
</array>
<key>LaunchEvents</key>
<dict>
<key>com.apple.iokit.matching</key>
<dict>
<key>com.apple.device-attach</key>
<dict>
<key>idVendor</key>
<integer>32902</integer>
<key>idProduct</key>
<integer>5427</integer>
<key>IOProviderClass</key>
<string>IOPCIDevice</string>
<key>IOMatchLaunchStream</key>
<true/>
<key>IOMatchStream</key>
<true/>
</dict>
</dict>
</dict>
</dict>
</plist>
它包含用于识别您想要触发的设备的信息,如
idVendor
、idProduct
、IOProviderClass
。这些可以在你的 mac 上的System Information
应用程序中找到。
在插入 plist 文件之前将十六进制标识符转换为整数(例如在 python 中使用
int(0x8086)
)。
IOProviderClass
应该是 IOPCIDevice
(Thunderbolt)或 IOUSBDevice
(USB)。
plist 文件中的其他相关条目是
xpc_set_event_stream_handler
和可执行文件的位置。
其他条目包括标准输出(日志)文件的位置和执行用户。
由于MAC欺骗需要root权限,我们将
com.spoofmac.plist
放入/Library/LaunchDaemons
:
cp com.spoofmac.plist /Library/LaunchDaemons/
不进入
LaunchAgents
文件夹。启动代理忽略UserName
参数。
确保文件的所有者是
root
:
sudo chown root:wheel /Library/LaunchDaemons/com.spoofmac.plist
激活守护进程:
launchctl load /Library/LaunchDaemons/com.spoofmac.plist
你很高兴。
卸载是使用
launchctl unload
完成的。
我已经将上述 Launch Daemon 的功能包装在一个 更加用户友好的 Mac 应用程序中,称为 “Stecker”。除了设备附件,该应用程序还可以检测设备分离。 它触发从 macOS 快捷方式应用程序中选择的快捷方式的执行。 该应用程序非常轻巧,仅依赖
IOKit
通知。
与启动方法相比,直接使用IOKit
框架允许过滤其他类型的com.apple.iokit.matching
事件,例如设备分离。
AIUI 你的应用程序必须调用 xpc_set_event_stream_handler 来从队列中移除事件。您可能还必须将
<key>KeepAlive</key><false/>
添加到 .plist,但我不确定。
我正在尝试使用这样的东西:
#include <xpc/xpc.h>
#include <unistd.h>
#include <asl.h>
int main(int argc, char *argv[]) {
if (argc < 2) {
return 1;
}
asl_log(NULL, NULL, ASL_LEVEL_DEBUG, "event_stream_handler: starting");
xpc_set_event_stream_handler("com.apple.iokit.matching", dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(xpc_object_t event) {
const char *name = xpc_dictionary_get_string(event, XPC_EVENT_KEY_NAME);
uint64_t id = xpc_dictionary_get_uint64(event, "IOMatchLaunchServiceID");
asl_log(NULL, NULL, ASL_LEVEL_DEBUG, "event_stream_handler: received event: %s: %llu", name, id);
execv(argv[1], argv + 1);
});
dispatch_main();
return 0;
}
因此一个脚本使用事件并运行作为参数传递的脚本。
这对我有用:
int main(int argc, const char * argv[]) {
@autoreleasepool {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
xpc_set_event_stream_handler("com.apple.iokit.matching", NULL, ^(xpc_object_t _Nonnull object) {
const char *event = xpc_dictionary_get_string(object, XPC_EVENT_KEY_NAME);
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if(argc >= 2) {
execv(argv[1], (char **)argv+1);
}
}
}