How to detect and identify mounted and unmounted USB device on Mac using Cocoa

There could be a situation in which we have to notify the application about mounting and un-mounting the USB and also to identify the USB i.e on which path it is mounted, what is the type of USB e.g I-Pod, I-Phone , Pen Drive etc.

1. To get the notification for mounting and un-mounting  of the USB device we can create 2 notifications:

// Notification for Mountingthe USB device

[[[NSWorkspacesharedWorkspace] notificationCenter]addObserver:selfselector:@selector(deviceMounted:)  name: NSWorkspaceDidMountNotificationobject: nil];

 // Notification for Un-Mountingthe USB device

[[[NSWorkspacesharedWorkspace] notificationCenter]addObserver:selfselector:@selector(deviceUnmounted:)  name: NSWorkspaceDidUnmountNotificationobject: nil];

  2. Once we get the notifications, now is the task to get the volume path on which the USB gets mounted.

// This code will give an array of volume paths for each mounted USB

NSArray* devices = [[NSWorkspacesharedWorkspace] mountedRemovableMedia];

  3. After getting volume paths for all mounted USBs, Now the task is to identify the mounted USB.

Every device has a VendorId, ProductId, ReleaseID and Name configured by the manufacturer. For ex. if the mounted USB is a I-Pod then it can be identified using the combination of it’s VendorId and ProductId. To get these attributes, Add IOKit framework to the project and import following headers in .m file.

#import 

#import 

 

// The following code will return an array having configured Ids and Name of all the mounted USB devices.

 

-(NSArray *) deviceAttributes

{

mach_port_t masterPort;

    CFMutableDictionaryRef matchingDict;

 

NSMutableArray * devicesAttributes = [NSMutableArray array];

 

    kern_return_t kr;

 

    //Create a master port for communication with the I/O Kit

    kr = IOMasterPort (MACH_PORT_NULL, &masterPort);

    if (kr || !masterPort)

    {

        NSLog (@"Error: Couldn't create a master I/O Kit port(%08x)", kr);

        return devicesAttributes;

    }

 

    //Set up matching dictionary for class IOUSBDevice and its subclasses

    matchingDict = IOServiceMatching (kIOUSBDeviceClassName);

    if (!matchingDict)

    {

        NSLog (@"Error: Couldn't create a USB matching dictionary");

        mach_port_deallocate(mach_task_self(), masterPort);

        return devicesAttributes;

    }

 

io_iterator_t iterator;

IOServiceGetMatchingServices (kIOMasterPortDefault, matchingDict, &iterator);

 

io_service_t usbDevice;

 

//Iterate for USB devices

    while (usbDevice = IOIteratorNext (iterator))

    {

IOCFPlugInInterface**plugInInterface = NULL;

SInt32 theScore;

 

//Create an intermediate plug-in

        kr = IOCreatePlugInInterfaceForService(usbDevice, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &theScore);

 

        if ((kIOReturnSuccess != kr) || !plugInInterface)

            printf("Unable to create a plug-in (%08x)\n", kr);

 

IOUSBDeviceInterface182 **dev = NULL;

 

        //Create the device interface

HRESULT result = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), (LPVOID)&dev);

 

        if (result || !dev)

            printf("Couldn't create a device interface (%08x)\n", (int) result);

 

UInt16 vendorId;

UInt16 productId;

UInt16 releaseId;

 

        //Get configuration Ids of the device

        (*dev)->GetDeviceVendor(dev, &vendorId);

        (*dev)->GetDeviceProduct(dev, &productId);

        (*dev)->GetDeviceReleaseNumber(dev, &releaseId);

 

 

UInt8 stringIndex;

       

(*dev)->USBGetProductStringIndex(dev, &stringIndex);

 

IOUSBConfigurationDescriptorPtr descriptor;

 

        (*dev)->GetConfigurationDescriptorPtr(dev, stringIndex, &descriptor);

 

//Get Device name

io_name_t deviceName;

kr = IORegistryEntryGetName (usbDevice, deviceName);

if (kr != KERN_SUCCESS)

{

NSLog (@"fail 0x%8x", kr);

deviceName[0] = '\0';

}

 

NSString * name = [NSStringstringWithCString:deviceName encoding:NSASCIIStringEncoding];

 

//data will be initialized only for USB storage devices.

//bsdName can be converted to mounted path of the device and vice-versa using DiskArbitration framework, hence we can identify the device through it's mounted path

CFTypeRef data = IORegistryEntrySearchCFProperty(usbDevice, kIOServicePlane, CFSTR("BSD Name"), kCFAllocatorDefault, kIORegistryIterateRecursively);

NSString* bsdName = [(NSString*)data substringToIndex:5];

 

NSString* attributeString = @"";

if(bsdName)

attributeString = [NSString stringWithFormat:@"%@,%@,0x%x,0x%x,0x%x", name, bsdName, vendorId, productId, releaseId];

else

attributeString = [NSString stringWithFormat:@"%@,0x%x,0x%x,0x%x", name, vendorId, productId, releaseId];

 

[devicesAttributes addObject:attributeString];

 

        IOObjectRelease(usbDevice);

        (*plugInInterface)->Release(plugInInterface);

        (*dev)->Release(dev);

    }

 

    //Finished with master port

    mach_port_deallocate(mach_task_self(), masterPort);

    masterPort = 0;

 

return devicesAttributes;

}

4. As the bsdName accessed in above function can be converted to the mounted volume path and vice-versa using DiskArbitration Framework, Hence we can identify the device completely.

150 150 Burnignorance | Where Minds Meet And Sparks Fly!