00001
00008 #include "mythmediamonitor.h"
00009 #include "mediamonitor-darwin.h"
00010 #include "mythcdrom.h"
00011 #include "mythhdd.h"
00012
00013 #include "mythcontext.h"
00014
00015 #include <IOKit/IOKitLib.h>
00016 #include <IOKit/storage/IOMedia.h>
00017 #include <IOKit/storage/IOCDMedia.h>
00018 #include <IOKit/storage/IODVDMedia.h>
00019 #include <IOKit/storage/IOBlockStorageDevice.h>
00020 #include <IOKit/storage/IOStorageDeviceCharacteristics.h>
00021 #include <IOKit/storage/IOStorageProtocolCharacteristics.h>
00022 #include <DiskArbitration/DiskArbitration.h>
00023 #include <qapplication.h>
00024
00025
00026
00027
00028 extern "C" static void diskAppearedCallback(DADiskRef disk, void *context);
00029 extern "C" static void diskDisappearedCallback(DADiskRef disk, void *context);
00030 extern "C" static void diskChangedCallback(DADiskRef disk,
00031 CFArrayRef keys, void *context);
00032 extern "C" static MediaType MediaTypeForBSDName(const char *bsdName);
00033
00034 static mach_port_t sMasterPort;
00035
00036
00040 MediaType FindMediaType(io_service_t service)
00041 {
00042 kern_return_t kernResult;
00043 io_iterator_t iter;
00044 MediaType mediaType = MEDIATYPE_UNKNOWN;
00045 QString msg = QString("FindMediaType() - ");
00046 bool isWholeMedia = false;
00047
00048
00049 kernResult = IORegistryEntryCreateIterator(service,
00050 kIOServicePlane,
00051 kIORegistryIterateRecursively
00052 | kIORegistryIterateParents,
00053 &iter);
00054
00055 if (KERN_SUCCESS != kernResult)
00056 VERBOSE(VB_IMPORTANT, (msg + "IORegistryEntryCreateIterator"
00057 + " returned %1").arg(kernResult));
00058 else if (!iter)
00059 VERBOSE(VB_IMPORTANT, msg + "IORegistryEntryCreateIterator"
00060 + " returned a NULL iterator");
00061 else
00062 {
00063
00064
00065 IOObjectRetain(service);
00066
00067 do
00068 {
00069 isWholeMedia = false;
00070 if (IOObjectConformsTo(service, kIOMediaClass))
00071 {
00072 CFTypeRef wholeMedia;
00073
00074 wholeMedia = IORegistryEntryCreateCFProperty
00075 (service, CFSTR(kIOMediaWholeKey),
00076 kCFAllocatorDefault, 0);
00077
00078 if (NULL == wholeMedia)
00079 VERBOSE(VB_IMPORTANT,
00080 msg + "Could not retrieve Whole property");
00081 else
00082 {
00083 isWholeMedia = CFBooleanGetValue((CFBooleanRef)wholeMedia);
00084 CFRelease(wholeMedia);
00085 }
00086 }
00087
00088 if (isWholeMedia)
00089 {
00090 if (IOObjectConformsTo(service, kIODVDMediaClass))
00091 mediaType = MEDIATYPE_DVD;
00092 else if (IOObjectConformsTo(service, kIOCDMediaClass))
00093 mediaType = MEDIATYPE_AUDIO;
00094 }
00095
00096 IOObjectRelease(service);
00097
00098 } while ((service = IOIteratorNext(iter))
00099 && (mediaType == MEDIATYPE_UNKNOWN));
00100
00101 IOObjectRelease(iter);
00102 }
00103 return mediaType;
00104 }
00105
00109 MediaType MediaTypeForBSDName(const char *bsdName)
00110 {
00111 CFMutableDictionaryRef matchingDict;
00112 kern_return_t kernResult;
00113 io_iterator_t iter;
00114 io_service_t service;
00115 QString msg = QString("MediaTypeForBSDName(%1)")
00116 .arg(bsdName);
00117 MediaType mediaType;
00118
00119
00120 if (!bsdName || !*bsdName)
00121 {
00122 VERBOSE(VB_IMPORTANT, msg + " - Error. No name supplied?");
00123 return MEDIATYPE_UNKNOWN;
00124 }
00125
00126 matchingDict = IOBSDNameMatching(sMasterPort, 0, bsdName);
00127 if (NULL == matchingDict)
00128 {
00129 VERBOSE(VB_IMPORTANT, msg + " - Error. IOBSDNameMatching()"
00130 + " returned a NULL dictionary.");
00131 return MEDIATYPE_UNKNOWN;
00132 }
00133
00134
00135
00136 kernResult = IOServiceGetMatchingServices(sMasterPort, matchingDict, &iter);
00137
00138 if (KERN_SUCCESS != kernResult)
00139 {
00140 VERBOSE(VB_IMPORTANT, (msg + " - Error. IOServiceGetMatchingServices()"
00141 + " returned %2").arg(kernResult));
00142 return MEDIATYPE_UNKNOWN;
00143 }
00144 if (!iter)
00145 {
00146 VERBOSE(VB_IMPORTANT, msg + " - Error. IOServiceGetMatchingServices()"
00147 + " returned a NULL iterator");
00148 return MEDIATYPE_UNKNOWN;
00149 }
00150
00151 service = IOIteratorNext(iter);
00152
00153
00154
00155 IOObjectRelease(iter);
00156
00157 if (!service)
00158 {
00159 VERBOSE(VB_IMPORTANT, msg + " - Error. IOIteratorNext()"
00160 + " returned a NULL iterator");
00161 return MEDIATYPE_UNKNOWN;
00162 }
00163 mediaType = FindMediaType(service);
00164 IOObjectRelease(service);
00165 return mediaType;
00166 }
00167
00168
00172 static char * getVolName(CFDictionaryRef diskDetails)
00173 {
00174 CFStringRef name;
00175 CFIndex size;
00176 char *volName;
00177
00178 name = (CFStringRef)
00179 CFDictionaryGetValue(diskDetails, kDADiskDescriptionVolumeNameKey);
00180 if (name)
00181 size = CFStringGetLength(name) + 1;
00182 else
00183 size = 9;
00184
00185 volName = (char *) malloc(size);
00186 if (!volName)
00187 {
00188 VERBOSE(VB_IMPORTANT,
00189 QString("getVolName() - Error. Can't malloc(%1)?").arg(size));
00190 return NULL;
00191 }
00192
00193 if (!name || !CFStringGetCString(name, volName, size,
00194 kCFStringEncodingUTF8))
00195 strcpy(volName, "Untitled");
00196
00197 return volName;
00198 }
00199
00200
00201
00202
00203 static const QString getModel(CFDictionaryRef diskDetails)
00204 {
00205 QString desc;
00206 const void *strRef;
00207
00208
00209 if (kCFBooleanTrue ==
00210 CFDictionaryGetValue(diskDetails,
00211 kDADiskDescriptionDeviceInternalKey))
00212 desc.append("Internal ");
00213
00214
00215 strRef = CFDictionaryGetValue(diskDetails,
00216 kDADiskDescriptionDeviceVendorKey);
00217 if (strRef)
00218 {
00219 desc.append(CFStringGetCStringPtr((CFStringRef)strRef,
00220 kCFStringEncodingMacRoman));
00221 desc.append(' ');
00222 }
00223
00224
00225 strRef = CFDictionaryGetValue(diskDetails,
00226 kDADiskDescriptionDeviceModelKey);
00227 if (strRef)
00228 {
00229 desc.append(CFStringGetCStringPtr((CFStringRef)strRef,
00230 kCFStringEncodingMacRoman));
00231 desc.append(' ');
00232 }
00233
00234
00235 desc.truncate(desc.length() - 1);
00236
00237
00238 desc.remove(" ");
00239
00240 return desc;
00241 }
00242
00243
00244
00245
00246
00247
00248
00249 void diskAppearedCallback(DADiskRef disk, void *context)
00250 {
00251 const char *BSDname = DADiskGetBSDName(disk);
00252 CFDictionaryRef details;
00253 bool isCDorDVD;
00254 MediaType mediaType;
00255 QString model;
00256 MonitorThreadDarwin *mtd;
00257 QString msg = "diskAppearedCallback() - ";
00258 char *volName;
00259
00260
00261 if (!BSDname)
00262 {
00263 VERBOSE(VB_MEDIA, msg + "Skipping non-local device");
00264 return;
00265 }
00266
00267 if (!context)
00268 {
00269 VERBOSE(VB_IMPORTANT, msg + "Error. Invoked with a NULL context.");
00270 return;
00271 }
00272
00273 mtd = reinterpret_cast<MonitorThreadDarwin*>(context);
00274
00275
00276
00277
00278
00279
00280
00281 details = DADiskCopyDescription(disk);
00282
00283 if (kCFBooleanFalse ==
00284 CFDictionaryGetValue(details, kDADiskDescriptionMediaRemovableKey))
00285 {
00286 VERBOSE(VB_MEDIA, msg + "Skipping non-removable " + BSDname);
00287 CFRelease(details);
00288 return;
00289 }
00290
00291
00292 volName = getVolName(details);
00293 model = getModel(details);
00294
00295 mediaType = MediaTypeForBSDName(BSDname);
00296 isCDorDVD = (mediaType == MEDIATYPE_DVD) || (mediaType == MEDIATYPE_AUDIO);
00297
00298
00299
00300
00301
00302 VERBOSE(VB_MEDIA, QString("Found disk %1 - volume name '%2'.")
00303 .arg(BSDname).arg(volName));
00304
00305 mtd->diskInsert(BSDname, volName, model, isCDorDVD);
00306
00307 CFRelease(details);
00308 free(volName);
00309 }
00310
00311 void diskDisappearedCallback(DADiskRef disk, void *context)
00312 {
00313 const char *BSDname = DADiskGetBSDName(disk);
00314
00315 if (context)
00316 reinterpret_cast<MonitorThreadDarwin *>(context)->diskRemove(BSDname);
00317 }
00318
00319 void diskChangedCallback(DADiskRef disk, CFArrayRef keys, void *context)
00320 {
00321 if (CFArrayContainsValue(keys, CFRangeMake(0, CFArrayGetCount(keys)),
00322 kDADiskDescriptionVolumeNameKey))
00323 {
00324 const char *BSDname = DADiskGetBSDName(disk);
00325 CFDictionaryRef details = DADiskCopyDescription(disk);
00326 char *volName = getVolName(details);
00327
00328 reinterpret_cast<MonitorThreadDarwin *>(context)
00329 ->diskRename(BSDname, volName);
00330 CFRelease(details);
00331 free(volName);
00332 }
00333 }
00334
00335
00339 void MonitorThreadDarwin::run(void)
00340 {
00341 CFDictionaryRef match = kDADiskDescriptionMatchVolumeMountable;
00342 DASessionRef daSession = DASessionCreate(kCFAllocatorDefault);
00343
00344 IOMasterPort(MACH_PORT_NULL, &sMasterPort);
00345
00346 DARegisterDiskAppearedCallback(daSession, match,
00347 diskAppearedCallback, this);
00348 DARegisterDiskDisappearedCallback(daSession, match,
00349 diskDisappearedCallback, this);
00350 DARegisterDiskDescriptionChangedCallback(daSession, match,
00351 kDADiskDescriptionWatchVolumeName,
00352 diskChangedCallback, this);
00353
00354 DASessionScheduleWithRunLoop(daSession,
00355 CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
00356
00357
00358
00359
00360 while (m_Monitor && m_Monitor->IsActive())
00361 {
00362
00363
00364 CFRunLoopRunInMode(kCFRunLoopDefaultMode,
00365 (float) m_Interval / 1000.0f, false );
00366 }
00367
00368 DAUnregisterCallback(daSession, (void(*))diskChangedCallback, this);
00369 DAUnregisterCallback(daSession, (void(*))diskDisappearedCallback, this);
00370 DAUnregisterCallback(daSession, (void(*))diskAppearedCallback, this);
00371 CFRelease(daSession);
00372 }
00373
00380 void MonitorThreadDarwin::diskInsert(const char *devName,
00381 const char *volName,
00382 QString model, bool isCDorDVD)
00383 {
00384 MythMediaDevice *media;
00385 QString msg = QString("MonitorThreadDarwin::diskInsert(%1,%2,%3)")
00386 .arg(devName).arg(volName).arg(isCDorDVD);
00387
00388 VERBOSE(VB_MEDIA, msg);
00389
00390 if (isCDorDVD)
00391 media = MythCDROM::get(m_Monitor, devName, true,
00392 m_Monitor->m_AllowEject);
00393 else
00394 media = MythHDD::Get(m_Monitor, devName, true, false);
00395
00396 if (!media)
00397 {
00398 VERBOSE(VB_IMPORTANT,
00399 msg + " - Error. Couldn't create MythMediaDevice.");
00400 return;
00401 }
00402
00404 media->setVolumeID(volName);
00405 media->setDeviceModel(model);
00406
00410 media->setMountPath(QString("/Volumes/") + volName);
00411 media->mount();
00412
00413 m_Monitor->AddDevice(media);
00414 }
00415
00416 void MonitorThreadDarwin::diskRemove(QString devName)
00417 {
00418 VERBOSE(VB_MEDIA,
00419 QString("MonitorThreadDarwin::diskRemove(%1)").arg(devName));
00420
00421 if (m_Monitor->m_SendEvent)
00422 {
00423 MythMediaDevice *pDevice = m_Monitor->GetMedia(devName);
00424
00425 if (pDevice)
00426 pDevice->setStatus(MEDIASTAT_NODISK);
00427 else
00428 VERBOSE(VB_MEDIA, "Couldn't find MythMediaDevice: " + devName);
00429 }
00430
00431 m_Monitor->RemoveDevice(devName);
00432 }
00433
00440 void MonitorThreadDarwin::diskRename(const char *devName, const char *volName)
00441 {
00442 VERBOSE(VB_MEDIA, QString("MonitorThreadDarwin::diskRename(%1,%2)")
00443 .arg(devName).arg(volName));
00444
00445 MythMediaDevice *pDevice = m_Monitor->GetMedia(devName);
00446
00447 if (m_Monitor->ValidateAndLock(pDevice))
00448 {
00449
00450 if (m_Monitor->m_SendEvent)
00451 pDevice->setStatus(MEDIASTAT_NODISK);
00452
00453 pDevice->setVolumeID(volName);
00454 pDevice->setMountPath(QString("/Volumes/") + volName);
00455
00456
00457 if (m_Monitor->m_SendEvent)
00458 pDevice->setStatus(MEDIASTAT_USEABLE);
00459
00460 m_Monitor->Unlock(pDevice);
00461 }
00462 else
00463 VERBOSE(VB_MEDIA, QString("Couldn't find MythMediaDevice: ") + devName);
00464 }
00465
00472 void MediaMonitorDarwin::StartMonitoring(void)
00473 {
00474
00475 if (m_Active)
00476 return;
00477
00478 if (!m_StartThread)
00479 return;
00480
00481
00482
00483
00484
00485 m_Devices.clear();
00486
00487
00488 if (!m_Thread)
00489 m_Thread = new MonitorThreadDarwin(this, m_MonitorPollingInterval);
00490
00491 VERBOSE(VB_MEDIA, "Starting MediaMonitor");
00492 m_Active = true;
00493 m_Thread->start();
00494 }
00495
00501 bool MediaMonitorDarwin::AddDevice(MythMediaDevice* pDevice)
00502 {
00503 if ( ! pDevice )
00504 {
00505 VERBOSE(VB_IMPORTANT, "Error - MediaMonitor::AddDevice(null)");
00506 return false;
00507 }
00508
00509
00510 if (shouldIgnore(pDevice))
00511 return false;
00512
00513 m_Devices.push_back( pDevice );
00514 m_UseCount[pDevice] = 0;
00515
00516
00517
00518
00519 if (m_SendEvent)
00520 {
00521 pDevice->setStatus(MEDIASTAT_NODISK);
00522 connect(pDevice, SIGNAL(statusChanged(MediaStatus, MythMediaDevice*)),
00523 this, SLOT(mediaStatusChanged(MediaStatus, MythMediaDevice*)));
00524 pDevice->setStatus(MEDIASTAT_USEABLE);
00525 }
00526
00527
00528 return true;
00529 }
00530
00531
00532
00533
00534
00535 static const QString getModel(io_object_t drive)
00536 {
00537 QString desc;
00538 CFMutableDictionaryRef props = NULL;
00539
00540 props = (CFMutableDictionaryRef) IORegistryEntrySearchCFProperty(drive, kIOServicePlane, CFSTR(kIOPropertyProtocolCharacteristicsKey), kCFAllocatorDefault, kIORegistryIterateParents | kIORegistryIterateRecursively);
00541 CFShow(props);
00542 if (props)
00543 {
00544 const void *location = CFDictionaryGetValue(props, CFSTR(kIOPropertyPhysicalInterconnectLocationKey));
00545 if (CFEqual(location, CFSTR("Internal")))
00546 desc.append("Internal ");
00547 }
00548
00549 props = (CFMutableDictionaryRef) IORegistryEntrySearchCFProperty(drive, kIOServicePlane, CFSTR(kIOPropertyDeviceCharacteristicsKey), kCFAllocatorDefault, kIORegistryIterateParents | kIORegistryIterateRecursively);
00550 if (props)
00551 {
00552 const void *product = CFDictionaryGetValue(props, CFSTR(kIOPropertyProductNameKey));
00553 const void *vendor = CFDictionaryGetValue(props, CFSTR(kIOPropertyVendorNameKey));
00554 if (vendor)
00555 {
00556 desc.append(CFStringGetCStringPtr((CFStringRef)vendor, kCFStringEncodingMacRoman));
00557 desc.append(" ");
00558 }
00559 if (product)
00560 {
00561 desc.append(CFStringGetCStringPtr((CFStringRef)product, kCFStringEncodingMacRoman));
00562 desc.append(" ");
00563 }
00564 }
00565
00566
00567 desc.truncate(desc.length() - 1);
00568
00569 return desc;
00570 }
00571
00581 QStringList MediaMonitorDarwin::GetCDROMBlockDevices()
00582 {
00583 kern_return_t kernResult;
00584 CFMutableDictionaryRef devices;
00585 io_iterator_t iter;
00586 QStringList list;
00587 QString msg = QString("GetCDRomBlockDevices() - ");
00588
00589
00590 devices = IOServiceMatching(kIOBlockStorageDeviceClass);
00591 if (!devices)
00592 {
00593 VERBOSE(VB_IMPORTANT, msg + "No Storage Devices? Unlikely!");
00594 return list;
00595 }
00596
00597
00598 kernResult = IOServiceGetMatchingServices(sMasterPort, devices, &iter);
00599
00600 if (KERN_SUCCESS != kernResult)
00601 {
00602 VERBOSE(VB_IMPORTANT, (msg + "IORegistryEntryCreateIterator"
00603 + " returned %1").arg(kernResult));
00604 return list;
00605 }
00606 if (!iter)
00607 {
00608 VERBOSE(VB_IMPORTANT, msg + "IORegistryEntryCreateIterator"
00609 + " returned a NULL iterator");
00610 return list;
00611 }
00612
00613 io_object_t drive;
00614
00615 while ((drive = IOIteratorNext(iter)))
00616 {
00617 CFMutableDictionaryRef p = NULL;
00618
00619 IORegistryEntryCreateCFProperties(drive, &p, kCFAllocatorDefault, 0);
00620 if (p)
00621 {
00622 const void *type = CFDictionaryGetValue(p, CFSTR("device-type"));
00623
00624 if (CFEqual(type, CFSTR("DVD")) || CFEqual(type, CFSTR("CD")))
00625 {
00626 QString desc = getModel(drive);
00627
00628 list.append(desc);
00629 VERBOSE(VB_MEDIA, desc.prepend("Found CD/DVD: "));
00630 CFRelease(p);
00631 }
00632 }
00633 else
00634 VERBOSE(VB_IMPORTANT, msg + "Could not retrieve drive properties");
00635
00636 IOObjectRelease(drive);
00637 }
00638
00639 IOObjectRelease(iter);
00640
00641 return list;
00642 }