MythTV  master
mediamonitor-darwin.cpp
Go to the documentation of this file.
1 
8 #include <QDir>
9 #include <QMetaType>
10 
11 #include "mythmediamonitor.h"
12 #include "mediamonitor-darwin.h"
13 #include "mythcdrom.h"
14 #include "mythhdd.h"
15 
16 #include "mythlogging.h"
17 
18 #include <IOKit/IOKitLib.h>
19 #include <IOKit/storage/IOMedia.h>
20 #include <IOKit/storage/IOCDMedia.h>
21 #include <IOKit/storage/IODVDMedia.h>
22 #include <IOKit/storage/IOBlockStorageDevice.h>
23 #include <IOKit/storage/IOStorageDeviceCharacteristics.h>
24 #include <IOKit/storage/IOStorageProtocolCharacteristics.h>
25 #include <DiskArbitration/DiskArbitration.h>
26 
27 
28 // These aren't external, they are defined in this file.
29 // The 'extern "C"' forces them in the C namespace, not the C++
30 extern "C" void diskAppearedCallback(DADiskRef disk, void *context);
31 extern "C" void diskDisappearedCallback(DADiskRef disk, void *context);
32 extern "C" void diskChangedCallback(DADiskRef disk,
33  CFArrayRef keys, void *context);
34 extern "C" MythMediaType MediaTypeForBSDName(const char *bsdName);
35 
36 static mach_port_t sMasterPort;
37 
38 
42 MythMediaType FindMediaType(io_service_t service)
43 {
44  kern_return_t kernResult;
45  io_iterator_t iter;
46  MythMediaType mediaType = MEDIATYPE_UNKNOWN;
47  QString msg = QString("FindMediaType() - ");
48  bool isWholeMedia = false;
49 
50  // Create an iterator across all parents of the service object passed in.
51  kernResult = IORegistryEntryCreateIterator(service,
52  kIOServicePlane,
53  kIORegistryIterateRecursively
54  | kIORegistryIterateParents,
55  &iter);
56 
57  if (KERN_SUCCESS != kernResult)
58  LOG(VB_GENERAL, LOG_CRIT, msg +
59  QString("IORegistryEntryCreateIterator returned %1")
60  .arg(kernResult));
61  else if (!iter)
62  LOG(VB_GENERAL, LOG_CRIT, msg +
63  "IORegistryEntryCreateIterator returned NULL iterator");
64  else
65  {
66  // A reference on the initial service object is released in
67  // the do-while loop below, so add a reference to balance
68  IOObjectRetain(service);
69 
70  do
71  {
72  isWholeMedia = false;
73  if (IOObjectConformsTo(service, kIOMediaClass))
74  {
75  CFTypeRef wholeMedia;
76 
77  wholeMedia = IORegistryEntryCreateCFProperty
78  (service, CFSTR(kIOMediaWholeKey),
79  kCFAllocatorDefault, 0);
80 
81  if (!wholeMedia)
82  LOG(VB_GENERAL, LOG_ALERT, msg +
83  "Could not retrieve Whole property");
84  else
85  {
86  isWholeMedia = CFBooleanGetValue((CFBooleanRef)wholeMedia);
87  CFRelease(wholeMedia);
88  }
89  }
90 
91  if (isWholeMedia)
92  {
93  if (IOObjectConformsTo(service, kIODVDMediaClass))
94  mediaType = MEDIATYPE_DVD;
95  else if (IOObjectConformsTo(service, kIOCDMediaClass))
96  mediaType = MEDIATYPE_AUDIO;
97  }
98 
99  IOObjectRelease(service);
100 
101  } while ((service = IOIteratorNext(iter))
102  && (mediaType == MEDIATYPE_UNKNOWN));
103 
104  IOObjectRelease(iter);
105  }
106  return mediaType;
107 }
108 
112 MythMediaType MediaTypeForBSDName(const char *bsdName)
113 {
114  CFMutableDictionaryRef matchingDict;
115  kern_return_t kernResult;
116  io_iterator_t iter;
117  io_service_t service;
118  QString msg = QString("MediaTypeForBSDName(%1)")
119  .arg(bsdName);
120  MythMediaType mediaType;
121 
122 
123  if (!bsdName || !*bsdName)
124  {
125  LOG(VB_GENERAL, LOG_ALERT, msg + " - No name supplied?");
126  return MEDIATYPE_UNKNOWN;
127  }
128 
129  matchingDict = IOBSDNameMatching(sMasterPort, 0, bsdName);
130  if (!matchingDict)
131  {
132  LOG(VB_GENERAL, LOG_ALERT,
133  msg + " - IOBSDNameMatching() returned a NULL dictionary.");
134  return MEDIATYPE_UNKNOWN;
135  }
136 
137  // Return an iterator across all objects with the matching
138  // BSD node name. Note that there should only be one match!
139  kernResult = IOServiceGetMatchingServices(sMasterPort, matchingDict, &iter);
140 
141  if (KERN_SUCCESS != kernResult)
142  {
143  LOG(VB_GENERAL, LOG_ALERT,
144  QString(msg + " - IOServiceGetMatchingServices() returned %2")
145  .arg(kernResult));
146  return MEDIATYPE_UNKNOWN;
147  }
148  if (!iter)
149  {
150  LOG(VB_GENERAL, LOG_ALERT,
151  msg + " - IOServiceGetMatchingServices() returned a NULL "
152  "iterator");
153  return MEDIATYPE_UNKNOWN;
154  }
155 
156  service = IOIteratorNext(iter);
157 
158  // Release this now because we only expect
159  // the iterator to contain a single io_service_t.
160  IOObjectRelease(iter);
161 
162  if (!service)
163  {
164  LOG(VB_GENERAL, LOG_ALERT,
165  msg + " - IOIteratorNext() returned a NULL iterator");
166  return MEDIATYPE_UNKNOWN;
167  }
168  mediaType = FindMediaType(service);
169  IOObjectRelease(service);
170  return mediaType;
171 }
172 
173 
177 static char * getVolName(CFDictionaryRef diskDetails)
178 {
179  CFStringRef name;
180  CFIndex size;
181  char *volName;
182 
183  name = (CFStringRef)
184  CFDictionaryGetValue(diskDetails, kDADiskDescriptionVolumeNameKey);
185 
186  if (!name)
187  return nullptr;
188 
189  size = CFStringGetLength(name) + 1;
190  volName = (char *) malloc(size);
191  if (!volName)
192  {
193  LOG(VB_GENERAL, LOG_ALERT,
194  QString("getVolName() - Can't malloc(%1)?").arg(size));
195  return nullptr;
196  }
197 
198  if (!CFStringGetCString(name, volName, size, kCFStringEncodingUTF8))
199  {
200  free(volName);
201  return nullptr;
202  }
203 
204  return volName;
205 }
206 
207 /*
208  * Given a DA description, return a compound description to help identify it.
209  */
210 static const QString getModel(CFDictionaryRef diskDetails)
211 {
212  QString desc;
213  const void *strRef;
214 
215  // Location
216  if (kCFBooleanTrue ==
217  CFDictionaryGetValue(diskDetails,
218  kDADiskDescriptionDeviceInternalKey))
219  desc.append("Internal ");
220 
221  // Manufacturer
222  strRef = CFDictionaryGetValue(diskDetails,
223  kDADiskDescriptionDeviceVendorKey);
224  if (strRef)
225  {
226  desc.append(CFStringGetCStringPtr((CFStringRef)strRef,
227  kCFStringEncodingMacRoman));
228  desc.append(' ');
229  }
230 
231  // Product
232  strRef = CFDictionaryGetValue(diskDetails,
233  kDADiskDescriptionDeviceModelKey);
234  if (strRef)
235  {
236  desc.append(CFStringGetCStringPtr((CFStringRef)strRef,
237  kCFStringEncodingMacRoman));
238  desc.append(' ');
239  }
240 
241  // Remove the trailing space
242  desc.truncate(desc.length() - 1);
243 
244  // and multiple spaces
245  desc.remove(" ");
246 
247  return desc;
248 }
249 
250 
251 /*
252  * Callbacks which the Disk Arbitration session invokes
253  * whenever a disk comes or goes, or is renamed
254  */
255 
256 void diskAppearedCallback(DADiskRef disk, void *context)
257 {
258  const char *BSDname = DADiskGetBSDName(disk);
259  CFDictionaryRef details;
260  bool isCDorDVD;
261  MythMediaType mediaType;
262  QString model;
263  MonitorThreadDarwin *mtd;
264  QString msg = "diskAppearedCallback() - ";
265  char *volName;
266 
267 
268  if (!BSDname)
269  {
270  LOG(VB_MEDIA, LOG_INFO, msg + "Skipping non-local device");
271  return;
272  }
273 
274  if (!context)
275  {
276  LOG(VB_GENERAL, LOG_ALERT, msg + "Error. Invoked with a NULL context.");
277  return;
278  }
279 
280  mtd = reinterpret_cast<MonitorThreadDarwin*>(context);
281 
282 
283  // We want to monitor CDs/DVDs and USB cameras or flash drives,
284  // but probably not hard disk or network drives. For now, ignore
285  // any disk or partitions that are not on removable media.
286  // Seems OK for hot-plug USB/FireWire disks (i.e. they are removable)
287 
288  details = DADiskCopyDescription(disk);
289 
290  if (kCFBooleanFalse ==
291  CFDictionaryGetValue(details, kDADiskDescriptionMediaRemovableKey))
292  {
293  LOG(VB_MEDIA, LOG_INFO, msg + QString("Skipping non-removable %1")
294  .arg(BSDname));
295  CFRelease(details);
296  return;
297  }
298 
299  // Get the volume and model name for more user-friendly interaction
300  volName = getVolName(details);
301  if (!volName)
302  {
303  LOG(VB_MEDIA, LOG_INFO, msg + QString("No volume name for dev %1")
304  .arg(BSDname));
305  CFRelease(details);
306  return;
307  }
308 
309  model = getModel(details);
310 
311  if (model.contains("Disk Image"))
312  {
313  LOG(VB_MEDIA, LOG_INFO, msg + QString("DMG %1 mounted, ignoring")
314  .arg(BSDname));
315  CFRelease(details);
316  free(volName);
317  return;
318  }
319 
320  mediaType = MediaTypeForBSDName(BSDname);
321  isCDorDVD = (mediaType == MEDIATYPE_DVD) || (mediaType == MEDIATYPE_AUDIO);
322 
323 
324  // We know it is removable, and have guessed the type.
325  // Call a helper function to create appropriate objects and insert
326 
327  LOG(VB_MEDIA, LOG_INFO, QString("Found disk %1 - volume name '%2'.")
328  .arg(BSDname).arg(volName));
329 
330  mtd->diskInsert(BSDname, volName, model, isCDorDVD);
331 
332  CFRelease(details);
333  free(volName);
334 }
335 
336 void diskDisappearedCallback(DADiskRef disk, void *context)
337 {
338  const char *BSDname = DADiskGetBSDName(disk);
339 
340  if (context)
341  reinterpret_cast<MonitorThreadDarwin *>(context)->diskRemove(BSDname);
342 }
343 
344 void diskChangedCallback(DADiskRef disk, CFArrayRef keys, void *context)
345 {
346  if (CFArrayContainsValue(keys, CFRangeMake(0, CFArrayGetCount(keys)),
347  kDADiskDescriptionVolumeNameKey))
348  {
349  const char *BSDname = DADiskGetBSDName(disk);
350  CFDictionaryRef details = DADiskCopyDescription(disk);
351  char *volName = getVolName(details);
352 
353  LOG(VB_MEDIA, LOG_INFO, QString("Disk %1 - changed name to '%2'.")
354  .arg(BSDname).arg(volName));
355 
356  reinterpret_cast<MonitorThreadDarwin *>(context)
357  ->diskRename(BSDname, volName);
358  CFRelease(details);
359  free(volName);
360  }
361 }
362 
363 
368 {
369  CFDictionaryRef match = kDADiskDescriptionMatchVolumeMountable;
370  DASessionRef daSession = DASessionCreate(kCFAllocatorDefault);
371 
372  IOMasterPort(MACH_PORT_NULL, &sMasterPort);
373 
374  DARegisterDiskAppearedCallback(daSession, match,
375  diskAppearedCallback, this);
376  DARegisterDiskDisappearedCallback(daSession, match,
378  DARegisterDiskDescriptionChangedCallback(daSession, match,
379  kDADiskDescriptionWatchVolumeName,
380  diskChangedCallback, this);
381 
382  DASessionScheduleWithRunLoop(daSession,
383  CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
384 
385 
386  // Nice and simple, as long as our monitor is valid and active,
387  // loop and let daSession check the devices.
388  while (m_Monitor && m_Monitor->IsActive())
389  {
390  // Run the run loop for interval (milliseconds) - this will
391  // handle any disk arbitration appeared/dissappeared events
392  CFRunLoopRunInMode(kCFRunLoopDefaultMode,
393  (float) m_Interval / 1000.0F, false );
394  }
395 
396  DAUnregisterCallback(daSession, (void(*))diskChangedCallback, this);
397  DAUnregisterCallback(daSession, (void(*))diskDisappearedCallback, this);
398  DAUnregisterCallback(daSession, (void(*))diskAppearedCallback, this);
399  CFRelease(daSession);
400 }
401 
408 void MonitorThreadDarwin::diskInsert(const char *devName,
409  const char *volName,
410  QString model, bool isCDorDVD)
411 {
412  MythMediaDevice *media;
413  QString msg = "MonitorThreadDarwin::diskInsert";
414 
415  LOG(VB_MEDIA, LOG_DEBUG, msg + QString("(%1,%2,'%3',%4)")
416  .arg(devName).arg(volName).arg(model).arg(isCDorDVD));
417 
418  if (isCDorDVD)
419  media = MythCDROM::get(nullptr, devName, true, m_Monitor->m_AllowEject);
420  else
421  media = MythHDD::Get(nullptr, devName, true, false);
422 
423  if (!media)
424  {
425  LOG(VB_GENERAL, LOG_ALERT, msg + "Couldn't create MythMediaDevice.");
426  return;
427  }
428 
429  // We store the volume name for user activities like ChooseAndEjectMedia().
430  media->setVolumeID(volName);
431  media->setDeviceModel(model.toLatin1()); // Same for the Manufacturer and model
432 
433  // Mac OS X devices are pre-mounted here:
434  QString mnt = "/Volumes/"; mnt += volName;
435  media->setMountPath(mnt.toLatin1());
436 
437  int attempts = 0;
438  QDir d(mnt);
439  while (!d.exists())
440  {
441  LOG(VB_MEDIA, LOG_WARNING,
442  (msg + "() - Waiting for mount '%1' to become stable.").arg(mnt));
443  usleep(120000);
444  if ( ++attempts > 4 )
445  usleep(200000);
446  if ( attempts > 8 )
447  {
448  delete media;
449  LOG(VB_MEDIA, LOG_ALERT, msg + "() - Giving up");
450  return;
451  }
452  }
453 
455 
456  // This is checked in AddDevice(), but checking earlier means
457  // we can avoid scanning all the files to determine its type
458  if (m_Monitor->shouldIgnore(media))
459  return;
460 
461  // We want to use MythMedia's code to work out the mediaType.
462  // media->onDeviceMounted() is protected,
463  // so to call it indirectly, we pretend to mount it here.
464  media->mount();
465 
466  m_Monitor->AddDevice(media);
467 }
468 
469 void MonitorThreadDarwin::diskRemove(QString devName)
470 {
471  LOG(VB_MEDIA, LOG_DEBUG,
472  QString("MonitorThreadDarwin::diskRemove(%1)").arg(devName));
473 
474  MythMediaDevice *pDevice = m_Monitor->GetMedia(devName);
475 
476  if (pDevice) // Probably should ValidateAndLock() here?
477  pDevice->setStatus(MEDIASTAT_NODISK);
478  else
479  LOG(VB_MEDIA, LOG_INFO, "Couldn't find MythMediaDevice: " + devName);
480 
481  m_Monitor->RemoveDevice(devName);
482 }
483 
490 void MonitorThreadDarwin::diskRename(const char *devName, const char *volName)
491 {
492  LOG(VB_MEDIA, LOG_DEBUG,
493  QString("MonitorThreadDarwin::diskRename(%1,%2)")
494  .arg(devName).arg(volName));
495 
496  MythMediaDevice *pDevice = m_Monitor->GetMedia(devName);
497 
498  if (m_Monitor->ValidateAndLock(pDevice))
499  {
500  // Send message to plugins to ignore this drive:
501  pDevice->setStatus(MEDIASTAT_NODISK);
502 
503  pDevice->setVolumeID(volName);
504  pDevice->setMountPath((QString("/Volumes/") + volName).toLatin1());
505 
506  // Plugins can now use it again:
507  pDevice->setStatus(MEDIASTAT_USEABLE);
508 
509  m_Monitor->Unlock(pDevice);
510  }
511  else
512  LOG(VB_MEDIA, LOG_INFO,
513  QString("Couldn't find MythMediaDevice: %1").arg(devName));
514 }
515 
523 {
524  // Sanity check
525  if (m_Active)
526  return;
527 
528  // If something (like the MythMusic plugin) stops and starts monitoring,
529  // DiskArbitration would re-add the same drives several times over.
530  // So, we make sure the device list is deleted.
531  m_Devices.clear();
532 
533 
534  if (!m_Thread)
536 
537  qRegisterMetaType<MythMediaStatus>("MythMediaStatus");
538 
539  LOG(VB_MEDIA, LOG_NOTICE, "Starting MediaMonitor");
540  m_Active = true;
541  m_Thread->start();
542 }
543 
550 {
551  if ( !pDevice )
552  {
553  LOG(VB_GENERAL, LOG_ERR, "MediaMonitor::AddDevice(null)");
554  return false;
555  }
556 
557  // If the user doesn't want this device to be monitored, stop now:
558  if (shouldIgnore(pDevice))
559  return false;
560 
561  m_Devices.push_back( pDevice );
562  m_UseCount[pDevice] = 0;
563 
564 
565  // Devices on Mac OS X don't change status the way Linux ones do,
566  // so we force a status change for mediaStatusChanged() to send an event
567  pDevice->setStatus(MEDIASTAT_NODISK);
568  connect(pDevice, SIGNAL(statusChanged(MythMediaStatus, MythMediaDevice*)),
570  pDevice->setStatus(MEDIASTAT_USEABLE);
571 
572 
573  return true;
574 }
575 
576 /*
577  * Given a device, return a compound description to help identify it.
578  * We try to find out if it is internal, its manufacturer, and model.
579  */
580 static const QString getModel(io_object_t drive)
581 {
582  QString desc;
583  CFMutableDictionaryRef props = nullptr;
584 
585  props = (CFMutableDictionaryRef) IORegistryEntrySearchCFProperty(drive, kIOServicePlane, CFSTR(kIOPropertyProtocolCharacteristicsKey), kCFAllocatorDefault, kIORegistryIterateParents | kIORegistryIterateRecursively);
586 CFShow(props);
587  if (props)
588  {
589  const void *location = CFDictionaryGetValue(props, CFSTR(kIOPropertyPhysicalInterconnectLocationKey));
590  if (CFEqual(location, CFSTR("Internal")))
591  desc.append("Internal ");
592  }
593 
594  props = (CFMutableDictionaryRef) IORegistryEntrySearchCFProperty(drive, kIOServicePlane, CFSTR(kIOPropertyDeviceCharacteristicsKey), kCFAllocatorDefault, kIORegistryIterateParents | kIORegistryIterateRecursively);
595  if (props)
596  {
597  const void *product = CFDictionaryGetValue(props, CFSTR(kIOPropertyProductNameKey));
598  const void *vendor = CFDictionaryGetValue(props, CFSTR(kIOPropertyVendorNameKey));
599  if (vendor)
600  {
601  desc.append(CFStringGetCStringPtr((CFStringRef)vendor, kCFStringEncodingMacRoman));
602  desc.append(" ");
603  }
604  if (product)
605  {
606  desc.append(CFStringGetCStringPtr((CFStringRef)product, kCFStringEncodingMacRoman));
607  desc.append(" ");
608  }
609  }
610 
611  // Omit the trailing space
612  desc.truncate(desc.length() - 1);
613 
614  return desc;
615 }
616 
627 {
628  kern_return_t kernResult;
629  CFMutableDictionaryRef devices;
630  io_iterator_t iter;
631  QStringList list;
632  QString msg = QString("GetCDRomBlockDevices() - ");
633 
634 
635  devices = IOServiceMatching(kIOBlockStorageDeviceClass);
636  if (!devices)
637  {
638  LOG(VB_GENERAL, LOG_ALERT, msg + "No Storage Devices? Unlikely!");
639  return list;
640  }
641 
642  // Create an iterator across all parents of the service object passed in.
643  kernResult = IOServiceGetMatchingServices(sMasterPort, devices, &iter);
644 
645  if (KERN_SUCCESS != kernResult)
646  {
647  LOG(VB_GENERAL, LOG_ALERT, msg +
648  QString("IORegistryEntryCreateIterator returned %1")
649  .arg(kernResult));
650  return list;
651  }
652  if (!iter)
653  {
654  LOG(VB_GENERAL, LOG_ALERT, msg +
655  "IORegistryEntryCreateIterator returned a NULL iterator");
656  return list;
657  }
658 
659  io_object_t drive;
660 
661  while ((drive = IOIteratorNext(iter)))
662  {
663  CFMutableDictionaryRef p = nullptr; // properties of drive
664 
665  IORegistryEntryCreateCFProperties(drive, &p, kCFAllocatorDefault, 0);
666  if (p)
667  {
668  const void *type = CFDictionaryGetValue(p, CFSTR("device-type"));
669 
670  if (CFEqual(type, CFSTR("DVD")) || CFEqual(type, CFSTR("CD")))
671  {
672  QString desc = getModel(drive);
673 
674  list.append(desc);
675  LOG(VB_MEDIA, LOG_INFO, desc.prepend("Found CD/DVD: "));
676  CFRelease(p);
677  }
678  }
679  else
680  LOG(VB_GENERAL, LOG_ALERT,
681  msg + "Could not retrieve drive properties");
682 
683  IOObjectRelease(drive);
684  }
685 
686  IOObjectRelease(iter);
687 
688  return list;
689 }
static MythHDD * Get(QObject *par, const char *devicePath, bool SuperMount, bool AllowEject)
Helper function used to create a new instance of a hard disk device.
Definition: mythhdd.cpp:15
void StartMonitoring(void) override
Start the monitoring thread if needed.
void start(QThread::Priority=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:294
void diskChangedCallback(DADiskRef disk, CFArrayRef keys, void *context)
QMap< MythMediaDevice *, int > m_UseCount
void setDeviceModel(const char *model)
Definition: mythmedia.h:68
MythMediaType
Definition: mythmedia.h:24
QPointer< MediaMonitor > m_Monitor
MonitorThread * m_Thread
friend class MonitorThreadDarwin
unsigned long m_MonitorPollingInterval
void diskRename(const char *devName, const char *volName)
Deal with the user, or another program, renaming a volume.
bool shouldIgnore(const MythMediaDevice *device)
Check user preferences to see if this device should be monitored.
MythMediaType MediaTypeForBSDName(const char *bsdName)
Given a BSD device node name, guess its media type.
MythMediaType FindMediaType(io_service_t service)
Guess the media that a volume/partition is on.
bool AddDevice(MythMediaDevice *pDevice) override
Simpler version of MediaMonitorUnix::AddDevice()
void diskInsert(const char *devName, const char *volName, QString model, bool isCDorDVD=1)
Create a MythMedia instance and insert in MythMediaMonitor list.
void run(void) override
Use the DiskArbitration Daemon to inform us of media changes.
unsigned long m_Interval
MythMediaStatus setStatus(MythMediaStatus newStat, bool CloseIt=false)
Definition: mythmedia.cpp:461
void setVolumeID(const char *vol)
Definition: mythmedia.h:73
void diskRemove(QString devName)
QList< MythMediaDevice * > m_Devices
QStringList GetCDROMBlockDevices(void) override
List of CD/DVD devices.
void mediaStatusChanged(MythMediaStatus oldStatus, MythMediaDevice *pMedia)
Slot which is called when the device status changes and posts a media event to the mainwindow.
static const uint16_t * d
void diskDisappearedCallback(DADiskRef disk, void *context)
const char * name
Definition: ParseText.cpp:328
static const QString getModel(CFDictionaryRef diskDetails)
static MythCDROM * get(QObject *par, const char *devicePath, bool SuperMount, bool AllowEject)
Definition: mythcdrom.cpp:35
static mach_port_t sMasterPort
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
CD/DVD tray closed but empty, device unusable.
Definition: mythmedia.h:17
static char * getVolName(CFDictionaryRef diskDetails)
Given a description of a disk, copy and return the volume name.
MythMediaStatus
Definition: mythmedia.h:12
bool volatile m_Active
Was MonitorThread started?
static void usleep(unsigned long time)
Definition: mthread.cpp:348
void diskAppearedCallback(DADiskRef disk, void *context)
void setMountPath(const char *path)
Definition: mythmedia.h:59