triggering a sound when a usb or thunderbolt device connects

similar to windows, is there a way to trigger 2 different scripts, one for when a device is inserted, and one for when a device is ejected? I would like this to occur not only for volumes (such as hard drives, etc.) but for other devices, such as a thunderbolt display, a usb audio interface, etc.
Any help would be greatly appreciated

thanks so much,

Rocco

There most certainly is.

They are called launch daemons. On the Mac, there are two types:

Launch agents are run in a user context and have no elevated privileges
Lauch Daemons are run in a root context with elevated privileges

details available here:

https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html

Now, the script itself is the easiest bit. Just write a .sh that contains a block of applescript wrapped in an osascript bundle, such as:

#!/bin/bash
osascript <<'END'
set bob to "hello"
say bob
END

You can, of course, call a script also with osascript scriptname.scpt

Now, and this is where it gets complex, you need to decide how to use your launch daemon. If you wanted to do this properly, you’d create an XPC service to listen for devices. As this is an AppleScript forum, that’s not what I’m going to propose:

Firstly, the plist needed for the Launch Agent

<?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.acme.yourlaunchagentname</string>
	<key>Program</key>
	<string>/Library/YourScriptLocation/com.acme.yourscript.sh</string>
	<key>RunAtLoad</key>
	<true/>
	<key>StartInterval</key>
	<integer>10</integer>
</dict>
</plist>

This will run every 20 seconds

Your com.acme.yourscript.sh will contain:

#!/bin/bash
osascript /Library/YourScriptLocation/yourscriptname.scpt

And your applescript will contain:

do shell script "touch ~/Devices_list.txt"
set old_device_number to do shell script "cat ~/Devices_list.txt"
if old_device_number = "" then
	-- first time script run, write value and exit
	do shell script "echo " & getDevicesNumber() & " > ~/Devices_list.txt"
else if old_device_number > getDevicesNumber() then
	-- insert your stuff here when a device has been removed
	do shell script "echo " & getDevicesNumber() & " > ~/Devices_list.txt"
	log "something removed"
else if old_device_number < getDevicesNumber() then
	-- insert your stuff here when a device has been added
	do shell script "echo " & getDevicesNumber() & " > ~/Devices_list.txt"
	log "something added"
else
	-- no changes
	log "no changes"
end if

on getDevicesNumber()
	set devices_list_1 to do shell script "system_profiler SPUSBDataType | grep 'Product ID'"
	set devices_list_2 to do shell script "system_profiler SPUSBDataType | grep 'Mount Point'"
	set devices_number to (count of paragraphs of devices_list_1) + (count of paragraphs of devices_list_2)
	return devices_number
end getDevicesNumber


Try running the AppleScript on it’s own, plugging devices in and removing them between runs, just to get a feel of how it works

Get your launch agents running and you should be good.

How about a attaching a folder action to folder “/Volumes/”?

Edit: Still I would prefer the LaunchEvent (to listen to IO registry changes) key in the launchd.plist file over a periodically check.

That’s what I initially thought, but he did say other peripherals

Hi everyone,

thanks so much for all of this. I’ve set up everything and put all files in there respective places, but the script now gives me a message saying the command exited without 0 status, referencing the shell script system_usb type…
What if I made the script a stay-open applet with an idle handler? Would this be easier then dealing with launchd? like this…


on idle
	do shell script "touch ~/Devices_list.txt"
	set old_device_number to do shell script "cat ~/Devices_list.txt"
	if old_device_number = "" then
		-- first time script run, write value and exit
		do shell script "echo " & getDevicesNumber() & " > ~/Devices_list.txt"
	else if old_device_number > getDevicesNumber() then
		do shell script "afplay '/System/Library/Sounds/device removed.wav'"
		do shell script "echo " & getDevicesNumber() & " > ~/Devices_list.txt"
		log "something removed"
	else if old_device_number < getDevicesNumber() then
		do shell script "afplay '/System/Library/Sounds/device added.wav'"
		do shell script "echo " & getDevicesNumber() & " > ~/Devices_list.txt"
		log "something added"
	else
		-- no changes
		log "no changes"
	end if
	return 2
end idle
on getDevicesNumber()
	set devices_list_1 to do shell script "system_profiler SPUSBDataType | grep 'Product ID'"
	set devices_list_2 to do shell script "system_profiler SPUSBDataType | grep 'Mount Point'"
	set devices_number to (count of paragraphs of devices_list_1) + (count of paragraphs of devices_list_2)
	return devices_number
end getDevicesNumber



Thank you again,

Rocco

Hello.

I can’t find any reference to the shell script system_usb, so I’ll just tell you about the general solution, to remove an error from a do shell script, when you know how to handle the return value anyways, (like testing for any return value, before you use/operate on it.)

One trick, is to assign an empty string to the variable that are supposed to get the result of the do shell script, and then embed the do shell script in a “try” block.

set devices_list_1 to ""
try
	set devices_list_1 to do shell script "system_profiler SPUSBDataType | grep 'Product ID'"
on error
	-- you could of course also assign the value "" here 
end try
if devices_list_1 is not "" then
	-- do something 
end if

The other way around is to see to, that the command always exit with a zero statement, by appending " ; exit 0 " to your command string, as it is the latest command that gives do shell script the exit status. You’ll still have to test for an unempty string to operate on.

set devices_list_1 to do shell script "system_profiler SPUSBDataType | grep 'Product ID' ; exit 0"

if devices_list_1 is not "" then
	-- do something 
end if