21

I need to implement a Mac OS X application. In my application I need to do two things:

  1. Execute / Open an application when a particular type of USB device is connected to the system.
  2. Read the data from USB and upload it to a web server.

I do not have much experience in Mac OS X development. Can anyone please suggest the best documents to reach my goals?

s4y
  • 50,525
  • 12
  • 70
  • 98
Sekhar Bhetalam
  • 4,501
  • 6
  • 33
  • 52

5 Answers5

33

You can use launchd. Try man launchd and man launchd.plist.

It seems that launchd can work with USB events, even though this feature is poorly documented. I found it on: man xpc_set_event_stream_handler

Here's an example. If you put the following into: ~/Library/LaunchAgents/com.example.plist, your program should start when a USB device is connected.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN http://www.apple.com/DTDs/PropertyList-1.0.dtd >
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.example.program</string>
    <key>ProgramArguments</key>
    <array>
    <string>/path/to/program</string>
    </array>
    <key>LaunchEvents</key>
    <dict>
            <key>com.apple.iokit.matching</key>
            <dict>
                    <key>com.apple.device-attach</key>
                    <dict>
                            <key>idProduct</key>
                            <integer>1234</integer>
                            <key>idVendor</key>
                            <integer>1337</integer>
                            <key>IOProviderClass</key>
                            <string>IOUSBDevice</string>
                            <key>IOMatchLaunchStream</key>
                            <true/>
                    </dict>
            </dict>
    </dict>
</dict>
</plist>
Mitar
  • 6,756
  • 5
  • 54
  • 86
Julien Pilet
  • 504
  • 4
  • 5
  • 2
    just make sure that if you go this route and do not want your launched process to stay running (i.e. KeepAlive = false) that your code calls xpc_set_event_stream_handler() otherwise you will find your app being respawned every 10 seconds in response to the same matching event over and over again. – rudy Sep 07 '12 at 00:28
  • Does this work under OS X 10.6? I'm getting "LaunchEvents key not recognized". (See http://stackoverflow.com/q/15397304/558639) – fearless_fool Mar 13 '13 at 22:27
  • 6
    Is there a way to use this with shell scripts being launched from `launchd` instead of an Xcode app (eg. `/path/to/program` is a shell script). The shell script is being relaunched every 10 seconds and I can't find a command line alternative form `xpc_set_event_stream_handler()` or any way to remove the event from the stream. – romeovs Jul 31 '14 at 11:43
  • @romeovs Found this amazing utility that will call xpc_set_event_stream_handler to remove the event from the queue, and then call your desired application. Allowed me to use launchd instead of an Xcode app. Check out github.com/snosrap/xpc_set_event_stream_handler – zen Jul 01 '19 at 20:37
  • 1
    For everyone else that finds this post first and wonders what the field values should be (productID, vendorID, IOProviderClass) and wonders how to fix the script being rerun every 10 seconds, [this answer](https://stackoverflow.com/a/49902760/2386851) covers it all. – charlespwd Dec 22 '20 at 15:16
  • The link from @charlespwd didn’t work for providing the info I needed with my SteelSeries Headset. However I was able to use `system_profiler -xml SPUSBDataType > out.xml` to get a list then I just searched for `SteelSeries` and then converted the `product_id` and `vendor_id` from hex to decimal. I use this along with [`SwitchAudioSource`](https://github.com/deweller/switchaudio-osx) to set my default sound to my headphones whenever they’re plugged in/detected – Jens Bodal Jun 01 '22 at 17:51
  • Upgraded to an M1 mac and can't seem to get this working any longer – Jens Bodal Sep 02 '22 at 21:06
4

Julien Pilet's answer worked for me. However, to get it to not constantly relaunch the app when the device is still connected when closing the app, I had to:

  • call xpc_set_event_stream_handler() in my app delegate applicationDidFinishLaunching:
    xpc_set_event_stream_handler("com.apple.iokit.matching", NULL, ^(xpc_object_t event) {     
        // Every event has the key XPC_EVENT_KEY_NAME set to a string that
        // is the name you gave the event in your launchd.plist.
        const char *name = xpc_dictionary_get_string(event, XPC_EVENT_KEY_NAME);

        // IOKit events have the IORegistryEntryNumber as a payload.
        uint64_t id = xpc_dictionary_get_uint64(event, "IOMatchLaunchServiceID");
        // Reconstruct the node you were interested in here using the IOKit
        // APIs.
        NSLog(@"Received event: %s: %llu",name,id);
    });
  • add KeepAlive/false key/value pair to the plist
  • add IOMatchLaunchStream/true key/value pair to the com.apple.device-attach dict in the plist. This is in addition to the IOMatchStream key already there. Not sure why that has to be there, I found a reference to it here: http://asciiwwdc.com/2013/sessions/702

Also don't forget to register the plist with the system using

launchctl load <path to your plist>

Note that this seems to work, but I never get the NSLog message from the xpc stream handler.

muhqu
  • 12,329
  • 6
  • 28
  • 30
Andrew Aarestad
  • 1,120
  • 2
  • 11
  • 15
  • From [documentation](https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/xpc_set_event_stream_handler.3.html): The IOMatchLaunchStream key is required to be present and be a Boolean set to true for use with XPC Events. It will be filtered out of the rest of the dictionary when given to IOKit to match. The reasons for this are historical and not applicable to other event streams. – Mitar Feb 01 '16 at 09:30
4

Depending on the type of device you might able to set an application to open automatically via the iPhoto/Image Capture preferences. That will work only for a limited class of devices, for an application already present on the computer and will require changing the preferences on the computer manually.

In general, there's no way to automatically run arbitrary applications on CD/DVD/USB insert because it's a security problem.

blahdiblah
  • 33,069
  • 21
  • 98
  • 152
1

You may be able to set Folder Actions to run a command on mount. This would assume that the device always mounts in the same place, i.e. /Volumes/My\ Device/ - if peripherals were added or removed in between mounts, the mount point may change. You can learn more about Folder Actions by right clicking a directory and clicking "Folder Actions Setup". The trick would be to make sure that the device always mounts in the same place.

Alternatively, you may be able to use launchd to run a command on mount. This link may help. Lingon is a great app to edit daemons.

Either way, you could use the Folder Action or daemon to call a simple script to grab the contents of the device and upload them to wherever you please.

rick
  • 1,075
  • 4
  • 20
  • 29
1

It really depends on what sort of application you are looking at.

It does look like there is no way to do it in a similar fashion to udev for example.

Two possible solutions are:

  • Write a custom wrapper driver for your device
  • Use libusb and have a daemon to wait for certain device.

And in fact one could write a program with libusb which will handle this sort of tasks according to a given config file, that would be also cross-platform since libusb supports quite a few platforms.

Z4-tier
  • 7,287
  • 3
  • 26
  • 42
errordeveloper
  • 6,716
  • 6
  • 41
  • 54