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  RunProlog();
370  CFDictionaryRef match = kDADiskDescriptionMatchVolumeMountable;
371  DASessionRef daSession = DASessionCreate(kCFAllocatorDefault);
372 
373  IOMasterPort(MACH_PORT_NULL, &sMasterPort);
374 
375  DARegisterDiskAppearedCallback(daSession, match,
376  diskAppearedCallback, this);
377  DARegisterDiskDisappearedCallback(daSession, match,
379  DARegisterDiskDescriptionChangedCallback(daSession, match,
380  kDADiskDescriptionWatchVolumeName,
381  diskChangedCallback, this);
382 
383  DASessionScheduleWithRunLoop(daSession,
384  CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
385 
386 
387  // Nice and simple, as long as our monitor is valid and active,
388  // loop and let daSession check the devices.
389  while (m_Monitor && m_Monitor->IsActive())
390  {
391  // Run the run loop for interval (milliseconds) - this will
392  // handle any disk arbitration appeared/dissappeared events
393  CFRunLoopRunInMode(kCFRunLoopDefaultMode,
394  (float) m_Interval / 1000.0F, false );
395  }
396 
397  DAUnregisterCallback(daSession, (void(*))diskChangedCallback, this);
398  DAUnregisterCallback(daSession, (void(*))diskDisappearedCallback, this);
399  DAUnregisterCallback(daSession, (void(*))diskAppearedCallback, this);
400  CFRelease(daSession);
401  RunEpilog();
402 }
403 
410 void MonitorThreadDarwin::diskInsert(const char *devName,
411  const char *volName,
412  QString model, bool isCDorDVD)
413 {
414  MythMediaDevice *media;
415  QString msg = "MonitorThreadDarwin::diskInsert";
416 
417  LOG(VB_MEDIA, LOG_DEBUG, msg + QString("(%1,%2,'%3',%4)")
418  .arg(devName).arg(volName).arg(model).arg(isCDorDVD));
419 
420  if (isCDorDVD)
421  media = MythCDROM::get(nullptr, devName, true, m_Monitor->m_AllowEject);
422  else
423  media = MythHDD::Get(nullptr, devName, true, false);
424 
425  if (!media)
426  {
427  LOG(VB_GENERAL, LOG_ALERT, msg + "Couldn't create MythMediaDevice.");
428  return;
429  }
430 
431  // We store the volume name for user activities like ChooseAndEjectMedia().
432  media->setVolumeID(volName);
433  media->setDeviceModel(model.toLatin1()); // Same for the Manufacturer and model
434 
435  // Mac OS X devices are pre-mounted here:
436  QString mnt = "/Volumes/"; mnt += volName;
437  media->setMountPath(mnt.toLatin1());
438 
439  int attempts = 0;
440  QDir d(mnt);
441  while (!d.exists())
442  {
443  LOG(VB_MEDIA, LOG_WARNING,
444  (msg + "() - Waiting for mount '%1' to become stable.").arg(mnt));
445  usleep(120000);
446  if ( ++attempts > 4 )
447  usleep(200000);
448  if ( attempts > 8 )
449  {
450  delete media;
451  LOG(VB_MEDIA, LOG_ALERT, msg + "() - Giving up");
452  return;
453  }
454  }
455 
457 
458  // This is checked in AddDevice(), but checking earlier means
459  // we can avoid scanning all the files to determine its type
460  if (m_Monitor->shouldIgnore(media))
461  return;
462 
463  // We want to use MythMedia's code to work out the mediaType.
464  // media->onDeviceMounted() is protected,
465  // so to call it indirectly, we pretend to mount it here.
466  media->mount();
467 
468  m_Monitor->AddDevice(media);
469 }
470 
471 void MonitorThreadDarwin::diskRemove(QString devName)
472 {
473  LOG(VB_MEDIA, LOG_DEBUG,
474  QString("MonitorThreadDarwin::diskRemove(%1)").arg(devName));
475 
476  MythMediaDevice *pDevice = m_Monitor->GetMedia(devName);
477 
478  if (pDevice) // Probably should ValidateAndLock() here?
479  pDevice->setStatus(MEDIASTAT_NODISK);
480  else
481  LOG(VB_MEDIA, LOG_INFO, "Couldn't find MythMediaDevice: " + devName);
482 
483  m_Monitor->RemoveDevice(devName);
484 }
485 
492 void MonitorThreadDarwin::diskRename(const char *devName, const char *volName)
493 {
494  LOG(VB_MEDIA, LOG_DEBUG,
495  QString("MonitorThreadDarwin::diskRename(%1,%2)")
496  .arg(devName).arg(volName));
497 
498  MythMediaDevice *pDevice = m_Monitor->GetMedia(devName);
499 
500  if (m_Monitor->ValidateAndLock(pDevice))
501  {
502  // Send message to plugins to ignore this drive:
503  pDevice->setStatus(MEDIASTAT_NODISK);
504 
505  pDevice->setVolumeID(volName);
506  pDevice->setMountPath((QString("/Volumes/") + volName).toLatin1());
507 
508  // Plugins can now use it again:
509  pDevice->setStatus(MEDIASTAT_USEABLE);
510 
511  m_Monitor->Unlock(pDevice);
512  }
513  else
514  LOG(VB_MEDIA, LOG_INFO,
515  QString("Couldn't find MythMediaDevice: %1").arg(devName));
516 }
517 
525 {
526  // Sanity check
527  if (m_Active)
528  return;
529 
530  // If something (like the MythMusic plugin) stops and starts monitoring,
531  // DiskArbitration would re-add the same drives several times over.
532  // So, we make sure the device list is deleted.
533  m_Devices.clear();
534 
535 
536  if (!m_Thread)
538 
539  qRegisterMetaType<MythMediaStatus>("MythMediaStatus");
540 
541  LOG(VB_MEDIA, LOG_NOTICE, "Starting MediaMonitor");
542  m_Active = true;
543  m_Thread->start();
544 }
545 
552 {
553  if ( !pDevice )
554  {
555  LOG(VB_GENERAL, LOG_ERR, "MediaMonitor::AddDevice(null)");
556  return false;
557  }
558 
559  // If the user doesn't want this device to be monitored, stop now:
560  if (shouldIgnore(pDevice))
561  return false;
562 
563  m_Devices.push_back( pDevice );
564  m_UseCount[pDevice] = 0;
565 
566 
567  // Devices on Mac OS X don't change status the way Linux ones do,
568  // so we force a status change for mediaStatusChanged() to send an event
569  pDevice->setStatus(MEDIASTAT_NODISK);
570  connect(pDevice, SIGNAL(statusChanged(MythMediaStatus, MythMediaDevice*)),
572  pDevice->setStatus(MEDIASTAT_USEABLE);
573 
574 
575  return true;
576 }
577 
578 /*
579  * Given a device, return a compound description to help identify it.
580  * We try to find out if it is internal, its manufacturer, and model.
581  */
582 static const QString getModel(io_object_t drive)
583 {
584  QString desc;
585  CFMutableDictionaryRef props = nullptr;
586 
587  props = (CFMutableDictionaryRef) IORegistryEntrySearchCFProperty(drive, kIOServicePlane, CFSTR(kIOPropertyProtocolCharacteristicsKey), kCFAllocatorDefault, kIORegistryIterateParents | kIORegistryIterateRecursively);
588 CFShow(props);
589  if (props)
590  {
591  const void *location = CFDictionaryGetValue(props, CFSTR(kIOPropertyPhysicalInterconnectLocationKey));
592  if (CFEqual(location, CFSTR("Internal")))
593  desc.append("Internal ");
594  }
595 
596  props = (CFMutableDictionaryRef) IORegistryEntrySearchCFProperty(drive, kIOServicePlane, CFSTR(kIOPropertyDeviceCharacteristicsKey), kCFAllocatorDefault, kIORegistryIterateParents | kIORegistryIterateRecursively);
597  if (props)
598  {
599  const void *product = CFDictionaryGetValue(props, CFSTR(kIOPropertyProductNameKey));
600  const void *vendor = CFDictionaryGetValue(props, CFSTR(kIOPropertyVendorNameKey));
601  if (vendor)
602  {
603  desc.append(CFStringGetCStringPtr((CFStringRef)vendor, kCFStringEncodingMacRoman));
604  desc.append(" ");
605  }
606  if (product)
607  {
608  desc.append(CFStringGetCStringPtr((CFStringRef)product, kCFStringEncodingMacRoman));
609  desc.append(" ");
610  }
611  }
612 
613  // Omit the trailing space
614  desc.truncate(desc.length() - 1);
615 
616  return desc;
617 }
618 
629 {
630  kern_return_t kernResult;
631  CFMutableDictionaryRef devices;
632  io_iterator_t iter;
633  QStringList list;
634  QString msg = QString("GetCDRomBlockDevices() - ");
635 
636 
637  devices = IOServiceMatching(kIOBlockStorageDeviceClass);
638  if (!devices)
639  {
640  LOG(VB_GENERAL, LOG_ALERT, msg + "No Storage Devices? Unlikely!");
641  return list;
642  }
643 
644  // Create an iterator across all parents of the service object passed in.
645  kernResult = IOServiceGetMatchingServices(sMasterPort, devices, &iter);
646 
647  if (KERN_SUCCESS != kernResult)
648  {
649  LOG(VB_GENERAL, LOG_ALERT, msg +
650  QString("IORegistryEntryCreateIterator returned %1")
651  .arg(kernResult));
652  return list;
653  }
654  if (!iter)
655  {
656  LOG(VB_GENERAL, LOG_ALERT, msg +
657  "IORegistryEntryCreateIterator returned a NULL iterator");
658  return list;
659  }
660 
661  io_object_t drive;
662 
663  while ((drive = IOIteratorNext(iter)))
664  {
665  CFMutableDictionaryRef p = nullptr; // properties of drive
666 
667  IORegistryEntryCreateCFProperties(drive, &p, kCFAllocatorDefault, 0);
668  if (p)
669  {
670  const void *type = CFDictionaryGetValue(p, CFSTR("device-type"));
671 
672  if (CFEqual(type, CFSTR("DVD")) || CFEqual(type, CFSTR("CD")))
673  {
674  QString desc = getModel(drive);
675 
676  list.append(desc);
677  LOG(VB_MEDIA, LOG_INFO, desc.prepend("Found CD/DVD: "));
678  CFRelease(p);
679  }
680  }
681  else
682  LOG(VB_GENERAL, LOG_ALERT,
683  msg + "Could not retrieve drive properties");
684 
685  IOObjectRelease(drive);
686  }
687 
688  IOObjectRelease(iter);
689 
690  return list;
691 }
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:215
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)
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
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:202
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