8

我一直在设置launchd.plist每次安装特定 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>
4

4 回答 4

9

我为此编写了一个教程,其中包含详细说明和示例文件,用于通过将外部设备(usb/thunderbolt)连接到 Mac 计算机来触发任意可执行文件或 shell 脚本,而不会出现重生问题。

与作者的方法一样,它依赖于 Apple 的IOKit设备检测库和运行所需可执行文件的守护进程。为了在连接设备后不会重复触发守护程序,使用特殊的流处理程序 ( xpc_set_event_stream_handler) 来“使用”com.apple.iokit.matching事件,正如 @ford 的帖子和他的github repo中所解释的那样。

特别是,本教程描述了如何编译 xpc 流处理程序,以及如何将它与守护进程 plist 文件中的可执行文件一起引用,以及在何处放置具有正确权限的所有相关文件。

对于文件,请到这里。为了完整起见,我还在下面粘贴了他们的内容。

在 Mac 上运行由设备检测触发的 shell 脚本或可执行文件

这里我使用连接到 Mac 时欺骗以太网适配器的 MAC 地址的示例。这可以推广到任意可执行文件和设备。

将您的 shell 脚本或可执行文件放置到位

适配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>

它包含用于识别您想要触发的设备的信息,例如idVendoridProductIOProviderClass。这些可以在System InformationMac 上的应用程序中计算出来。

截图系统信息

在插入 plist 文件之前将十六进制标识符转换为整数(例如int(0x8086)在 python 中使用)。

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.

于 2018-04-18T14:47:24.887 回答
4

AIUI 您的应用程序必须调用 xpc_set_event_stream_handler 以从队列中删除事件。您可能还必须添加<key>KeepAlive</key><false/>到 .plist,但我不确定。

于 2012-12-21T12:23:09.793 回答
1

我正在尝试使用这样的东西:

#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;
}

因此,一个使用事件并运行作为参数传递的脚本的脚本。

于 2016-02-01T11:21:08.260 回答
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);
        }
    }
}

完整的源代码在这里

于 2018-02-20T23:34:07.623 回答