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