4 * OSX support functions for handling the IOKit registry.
5 * Currently this manages access to CD/DVD drives + media, plus HID devices.
7 * The HID part is much simpler...
9 * Copyright (c) 2008 Nathan Keynes.
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
22 #include <sys/param.h>
27 #include <IOKit/IOMessage.h>
28 #include <IOKit/hid/IOHIDLib.h>
29 #include "osx_iokit.h"
31 static IONotificationPortRef notify_port = 0;
32 static io_iterator_t iokit_iterators[4] = {0,0,0,0};
34 struct osx_cdrom_drive {
35 io_string_t ioservice_path;
36 io_string_t vendor_name;
37 io_string_t product_name;
38 char media_path[MAXPATHLEN]; // BSD device path if media present, otherwise the empty string.
39 io_iterator_t media_load_iterator;
40 io_iterator_t media_unload_iterator;
41 int media_fh; // BSD device handle if open, otherwise -1
42 media_changed_callback_t media_changed;
43 void *media_changed_user_data;
46 static gboolean get_bsdname_for_iomedia( io_object_t iomedia, char *buf, int buflen );
48 /***************** IOKit Callbacks ******************/
51 * Called from IOKit for any IOMessages on an IOMedia. Currently the only message
52 * we're interested in is service termination.
54 static void osx_cdrom_media_notify( void *ref, io_service_t service, uint32_t msgType,
57 if( msgType == kIOMessageServiceIsTerminated ) {
58 osx_cdrom_drive_t drive = (osx_cdrom_drive_t)ref;
59 if( drive->media_changed != NULL ) {
60 drive->media_changed( drive, FALSE, drive->media_changed_user_data );
62 if( drive->media_fh != -1 ) {
63 close(drive->media_fh);
66 drive->media_path[0] = '\0';
67 IOObjectRelease( drive->media_unload_iterator );
72 * Called from IOKit when an IOMedia is inserted that we have be interested in.
73 * FIXME: Can the matcher be restricted to descendents of the drive node? currently
74 * we watch for all IOMedia events and compare the device path to see if it's one we
76 * FIXME: We assume for now that a drive has at most one piece of media at a time.
77 * If this isn't the case, the system may get a little confused.
79 static void osx_cdrom_media_inserted( void *ref, io_iterator_t iterator )
81 osx_cdrom_drive_t drive = (osx_cdrom_drive_t)ref;
84 while( (object = IOIteratorNext(iterator)) != 0 ) {
85 io_string_t iopath = "";
86 IORegistryEntryGetPath( object, kIOServicePlane, iopath );
87 if( drive != NULL && g_str_has_prefix(iopath, drive->ioservice_path ) &&
88 get_bsdname_for_iomedia(object, drive->media_path, sizeof(drive->media_path)) ) {
89 // A disc was inserted within the drive of interest
90 if( drive->media_fh != -1 ) {
91 close(drive->media_fh);
95 if( drive->media_changed != NULL ) {
96 drive->media_changed(drive, TRUE, drive->media_changed_user_data);
98 // Add a notification listener to get removal events.
99 IOServiceAddInterestNotification( notify_port, object, kIOGeneralInterest,
100 osx_cdrom_media_notify, drive, &drive->media_unload_iterator );
103 IOObjectRelease( object );
107 static void osx_drives_changed( void *ref, io_iterator_t iterator )
110 while( (object = IOIteratorNext(iterator)) != 0 ) {
111 IOObjectRelease(object);
116 /******************** Support functions *********************/
119 * Determine the BSD device name (ie "/dev/rdisk1") for a given IO object.
120 * @return TRUE if the device name was retrieved, FALSE if the request failed.
122 static gboolean get_bsdname_for_iomedia( io_object_t iomedia, char *buf, int buflen )
124 gboolean result = FALSE;
125 CFTypeRef pathRef = IORegistryEntryCreateCFProperty(iomedia, CFSTR(kIOBSDNameKey),
126 kCFAllocatorDefault, 0 );
129 strcpy( buf, _PATH_DEV "r" );
130 pathlen = strlen(buf);
131 if( CFStringGetCString( pathRef, buf + pathlen, buflen-pathlen,
132 kCFStringEncodingASCII ) != noErr ) {
140 static gboolean osx_cdrom_drive_get_name( io_object_t object, char *vendor, int vendor_len,
141 char *product, int product_len )
143 gboolean result = FALSE;
144 CFMutableDictionaryRef props = 0;
145 if( IORegistryEntryCreateCFProperties(object, &props, kCFAllocatorDefault, kNilOptions) == KERN_SUCCESS ) {
146 CFDictionaryRef dict =
147 (CFDictionaryRef)CFDictionaryGetValue(props, CFSTR(kIOPropertyDeviceCharacteristicsKey));
149 CFTypeRef value = CFDictionaryGetValue(dict, CFSTR(kIOPropertyVendorNameKey));
150 if( value && CFGetTypeID(value) == CFStringGetTypeID() ) {
151 CFStringGetCString( (CFStringRef)value, vendor, vendor_len, kCFStringEncodingUTF8 );
156 value = CFDictionaryGetValue(dict, CFSTR(kIOPropertyProductNameKey));
157 if ( value && CFGetTypeID(value) == CFStringGetTypeID() ) {
158 CFStringGetCString( (CFStringRef)value, product, product_len, kCFStringEncodingUTF8 );
171 * Construct and initialize a new osx_cdrom_drive object, including registering
172 * it's media inserted notification.
174 static osx_cdrom_drive_t osx_cdrom_drive_new( io_object_t device )
176 osx_cdrom_drive_t drive = g_malloc0(sizeof(struct osx_cdrom_drive));
178 IORegistryEntryGetPath( device, kIOServicePlane, drive->ioservice_path );
179 osx_cdrom_drive_get_name( device, drive->vendor_name, sizeof(drive->vendor_name),
180 drive->product_name, sizeof(drive->product_name) );
181 drive->media_path[0] = '\0';
182 drive->media_changed = NULL;
183 drive->media_changed_user_data = NULL;
184 drive->media_fh = -1;
186 IOServiceAddMatchingNotification( notify_port, kIOFirstPublishNotification,
187 IOServiceMatching("IOMedia"),
188 osx_cdrom_media_inserted, drive,
189 &drive->media_load_iterator );
190 osx_cdrom_media_inserted( drive, drive->media_load_iterator );
194 /************************ Exported functions *************************/
196 osx_cdrom_drive_t osx_cdrom_open_drive( const char *devname )
198 io_object_t object = IORegistryEntryFromPath( kIOMasterPortDefault, devname );
199 if( object == MACH_PORT_NULL ) {
203 osx_cdrom_drive_t drive = osx_cdrom_drive_new( object );
204 IOObjectRelease( object );
208 void osx_cdrom_set_media_changed_callback( osx_cdrom_drive_t drive,
209 media_changed_callback_t callback,
212 drive->media_changed = callback;
213 drive->media_changed_user_data = user_data;
216 void osx_cdrom_close_drive( osx_cdrom_drive_t drive )
218 IOObjectRelease( drive->media_load_iterator );
219 IOObjectRelease( drive->media_unload_iterator );
220 if( drive->media_fh != -1 ) {
221 close(drive->media_fh);
222 drive->media_fh = -1;
227 int osx_cdrom_get_media_handle( osx_cdrom_drive_t drive )
229 if( drive->media_fh == -1 ) {
230 if( drive->media_path[0] != '\0' ) {
231 drive->media_fh = open( drive->media_path, O_RDONLY|O_NONBLOCK );
234 return drive->media_fh;
237 void osx_cdrom_release_media_handle( osx_cdrom_drive_t drive )
239 if( drive->media_fh != -1 ) {
240 close( drive->media_fh );
241 drive->media_fh = -1;
245 static io_object_t iterator_find_cdrom( io_object_t iterator, find_drive_callback_t callback, void *user_data )
248 while( (object = IOIteratorNext(iterator)) != 0 ) {
249 io_string_t iopath = "";
250 char product[256], vendor[256];
251 IORegistryEntryGetPath( object, kIOServicePlane, iopath );
252 osx_cdrom_drive_get_name( object, vendor, sizeof(vendor), product, sizeof(product) );
253 if( callback( object, vendor, product, iopath, user_data ) ) {
254 IOObjectRelease(iterator);
257 IOObjectRelease(object);
259 IOObjectRelease(iterator);
264 * Search for a CD or DVD drive (instance of IODVDServices or IOCompactDiscServices).
265 * The callback will be called repeatedly until either it returns TRUE, or all drives
266 * have been iterated over.
268 * @return an IO registry entry for the matched drive, or 0 if no drives matched.
270 * Note: Use of IOCompactDiscServices is somewhat tentative since I don't have a Mac
271 * with a CD-Rom drive.
273 io_object_t find_cdrom_drive( find_drive_callback_t callback, void *user_data )
275 mach_port_t master_port;
276 CFMutableDictionaryRef match;
277 io_iterator_t services;
280 if( IOMasterPort( MACH_PORT_NULL, &master_port ) != KERN_SUCCESS ) {
281 return 0; // Failed to get the master port?
284 match = IOServiceMatching("IODVDServices");
285 if( IOServiceGetMatchingServices(master_port, match, &services) != kIOReturnSuccess ) {
289 result = iterator_find_cdrom( services, callback, user_data );
294 match = IOServiceMatching("IOCompactDiscServices");
295 if( IOServiceGetMatchingServices(master_port, match, &services) != kIOReturnSuccess ) {
298 return iterator_find_cdrom( services, callback, user_data );
302 // *********************** Notification management ************************/
304 static void osx_hid_inserted( void *ref, io_iterator_t iterator )
307 while( (object = IOIteratorNext(iterator)) != 0 ) {
308 io_string_t iopath = "";
309 IORegistryEntryGetPath( object, kIOServicePlane, iopath );
310 IOObjectRelease( object );
314 gboolean osx_register_iokit_notifications()
316 notify_port = IONotificationPortCreate( kIOMasterPortDefault );
317 CFRunLoopSourceRef runloop_source = IONotificationPortGetRunLoopSource( notify_port );
318 CFRunLoopAddSource( CFRunLoopGetMain(), runloop_source, kCFRunLoopCommonModes );
320 // Drive notifications
321 if( IOServiceAddMatchingNotification( notify_port, kIOFirstPublishNotification,
322 IOServiceMatching("IOCompactDiscServies"),
323 osx_drives_changed, NULL, &iokit_iterators[0] ) != kIOReturnSuccess ) {
324 ERROR( "IOServiceAddMatchingNotification failed" );
326 osx_drives_changed(NULL, iokit_iterators[0]);
327 if( IOServiceAddMatchingNotification( notify_port, kIOFirstPublishNotification,
328 IOServiceMatching("IODVDServies"),
329 osx_drives_changed, NULL, &iokit_iterators[1] ) != kIOReturnSuccess ) {
330 ERROR( "IOServiceAddMatchingNotification failed" );
332 osx_drives_changed(NULL, iokit_iterators[1]);
334 if( IOServiceAddMatchingNotification( notify_port, kIOFirstPublishNotification,
335 IOServiceMatching(kIOHIDDeviceKey),
336 osx_hid_inserted, NULL, &iokit_iterators[4] ) != kIOReturnSuccess ) {
337 ERROR( "IOServiceAddMatchingNotification failed" );
339 osx_hid_inserted(NULL, iokit_iterators[2]);
342 void osx_unregister_iokit_notifications()
344 CFRunLoopSourceRef runloop_source = IONotificationPortGetRunLoopSource( notify_port );
345 CFRunLoopRemoveSource( CFRunLoopGetMain(), runloop_source, kCFRunLoopCommonModes );
346 IONotificationPortDestroy( notify_port );
.