cec: fixed possible crash when trying to request a vendor id of a device when the...
[deb_libcec.git] / src / lib / devices / CECBusDevice.cpp
index 3c393716d0270e81be0265106d8d176b8465eb30..5b9d421bd25c2dea260092887eb04c624d405658 100644 (file)
@@ -60,7 +60,8 @@ CCECBusDevice::CCECBusDevice(CCECProcessor *processor, cec_logical_address iLogi
   m_cecVersion(CEC_VERSION_UNKNOWN),
   m_deviceStatus(CEC_DEVICE_STATUS_UNKNOWN),
   m_iHandlerUseCount(0),
-  m_bAwaitingReceiveFailed(false)
+  m_bAwaitingReceiveFailed(false),
+  m_bVendorIdRequested(false)
 {
   m_handler = new CCECCommandHandler(this);
 
@@ -87,7 +88,7 @@ bool CCECBusDevice::HandleCommand(const cec_command &command)
     m_iLastActive = GetTimeMs();
 
     /* don't call GetStatus() here, just read the value with the mutex locked */
-    if (m_deviceStatus != CEC_DEVICE_STATUS_HANDLED_BY_LIBCEC)
+    if (m_deviceStatus != CEC_DEVICE_STATUS_HANDLED_BY_LIBCEC && command.opcode_set == 1)
       m_deviceStatus = CEC_DEVICE_STATUS_PRESENT;
 
     MarkBusy();
@@ -160,7 +161,10 @@ cec_version CCECBusDevice::GetCecVersion(bool bUpdate /* = false */)
   }
 
   if (bRequestUpdate)
+  {
+    CheckVendorIdRequested();
     RequestCecVersion();
+  }
 
   CLockObject lock(m_mutex);
   return m_cecVersion;
@@ -198,7 +202,10 @@ cec_menu_language &CCECBusDevice::GetMenuLanguage(bool bUpdate /* = false */)
   }
 
   if (bRequestUpdate)
+  {
+    CheckVendorIdRequested();
     RequestMenuLanguage();
+  }
 
   CLockObject lock(m_mutex);
   return m_menuLanguage;
@@ -247,7 +254,10 @@ CStdString CCECBusDevice::GetOSDName(bool bUpdate /* = false */)
   }
 
   if (bRequestUpdate)
+  {
+    CheckVendorIdRequested();
     RequestOSDName();
+  }
 
   CLockObject lock(m_mutex);
   return m_strDeviceName;
@@ -278,8 +288,12 @@ uint16_t CCECBusDevice::GetPhysicalAddress(bool bUpdate /* = false */)
       (m_iPhysicalAddress == 0xFFFF || bUpdate);
   }
 
-  if (bRequestUpdate && !RequestPhysicalAddress())
-    CLibCEC::AddLog(CEC_LOG_ERROR, "failed to request the physical address");
+  if (bRequestUpdate)
+  {
+    CheckVendorIdRequested();
+    if (!RequestPhysicalAddress())
+      CLibCEC::AddLog(CEC_LOG_ERROR, "failed to request the physical address");
+  }
 
   CLockObject lock(m_mutex);
   return m_iPhysicalAddress;
@@ -313,7 +327,10 @@ cec_power_status CCECBusDevice::GetPowerStatus(bool bUpdate /* = false */)
   }
 
   if (bRequestUpdate)
+  {
+    CheckVendorIdRequested();
     RequestPowerStatus();
+  }
 
   CLockObject lock(m_mutex);
   return m_powerStatus;
@@ -355,7 +372,7 @@ bool CCECBusDevice::RequestVendorId(void)
 {
   bool bReturn(false);
 
-  if (!MyLogicalAddressContains(m_iLogicalAddress))
+  if (!MyLogicalAddressContains(m_iLogicalAddress) && GetMyLogicalAddress() != CECDEVICE_UNKNOWN)
   {
     MarkBusy();
     CLibCEC::AddLog(CEC_LOG_NOTICE, "<< requesting vendor ID of '%s' (%X)", GetLogicalAddressName(), m_iLogicalAddress);
@@ -453,7 +470,7 @@ cec_bus_device_status CCECBusDevice::GetStatus(bool bForcePoll /* = false */)
   if (bNeedsPoll)
   {
     bool bPollAcked(false);
-    if (bNeedsPoll || NeedsPoll())
+    if (bNeedsPoll && NeedsPoll())
       bPollAcked = m_processor->PollDevice(m_iLogicalAddress);
 
     status = bPollAcked ? CEC_DEVICE_STATUS_PRESENT : CEC_DEVICE_STATUS_NOT_PRESENT;
@@ -507,6 +524,8 @@ void CCECBusDevice::SetInactiveSource(void)
 {
   {
     CLockObject lock(m_mutex);
+    if (m_bActiveSource)
+      CLibCEC::AddLog(CEC_LOG_DEBUG, "marking %s (%X) as inactive source", GetLogicalAddressName(), m_iLogicalAddress);
     m_bActiveSource = false;
   }
 
@@ -548,44 +567,49 @@ bool CCECBusDevice::TryLogicalAddress(void)
 
 void CCECBusDevice::SetDeviceStatus(const cec_bus_device_status newStatus)
 {
-  CLockObject lock(m_mutex);
-  switch (newStatus)
-  {
-  case CEC_DEVICE_STATUS_UNKNOWN:
-    if (m_deviceStatus != newStatus)
-      CLibCEC::AddLog(CEC_LOG_DEBUG, "device status of %s changed into 'unknown'", ToString(m_iLogicalAddress));
-    m_iStreamPath      = 0;
-    m_powerStatus      = CEC_POWER_STATUS_UNKNOWN;
-    m_vendor           = CEC_VENDOR_UNKNOWN;
-    m_menuState        = CEC_MENU_STATE_ACTIVATED;
-    m_bActiveSource    = false;
-    m_iLastActive      = 0;
-    m_cecVersion       = CEC_VERSION_UNKNOWN;
-    m_deviceStatus     = newStatus;
-    break;
-  case CEC_DEVICE_STATUS_HANDLED_BY_LIBCEC:
-    if (m_deviceStatus != newStatus)
-      CLibCEC::AddLog(CEC_LOG_DEBUG, "device status of %s changed into 'handled by libCEC'", ToString(m_iLogicalAddress));
-    m_iStreamPath      = 0;
-    m_powerStatus      = CEC_POWER_STATUS_IN_TRANSITION_STANDBY_TO_ON;
-    m_vendor           = CEC_VENDOR_UNKNOWN;
-    m_menuState        = CEC_MENU_STATE_ACTIVATED;
-    m_bActiveSource    = false;
-    m_iLastActive      = 0;
-    m_cecVersion       = CEC_VERSION_1_3A;
-    m_deviceStatus     = newStatus;
-    break;
-  case CEC_DEVICE_STATUS_PRESENT:
-    if (m_deviceStatus != newStatus)
-      CLibCEC::AddLog(CEC_LOG_DEBUG, "device status of %s changed into 'present'", ToString(m_iLogicalAddress));
-    m_deviceStatus = newStatus;
-    break;
-  case CEC_DEVICE_STATUS_NOT_PRESENT:
-    if (m_deviceStatus != newStatus)
-      CLibCEC::AddLog(CEC_LOG_DEBUG, "device status of %s changed into 'not present'", ToString(m_iLogicalAddress));
-    m_deviceStatus = newStatus;
-    break;
+  {
+    CLockObject lock(m_mutex);
+    switch (newStatus)
+    {
+    case CEC_DEVICE_STATUS_UNKNOWN:
+      if (m_deviceStatus != newStatus)
+        CLibCEC::AddLog(CEC_LOG_DEBUG, "device status of %s changed into 'unknown'", ToString(m_iLogicalAddress));
+      m_iStreamPath      = 0;
+      m_powerStatus      = CEC_POWER_STATUS_UNKNOWN;
+      m_vendor           = CEC_VENDOR_UNKNOWN;
+      m_menuState        = CEC_MENU_STATE_ACTIVATED;
+      m_bActiveSource    = false;
+      m_iLastActive      = 0;
+      m_cecVersion       = CEC_VERSION_UNKNOWN;
+      m_deviceStatus     = newStatus;
+      break;
+    case CEC_DEVICE_STATUS_HANDLED_BY_LIBCEC:
+      if (m_deviceStatus != newStatus)
+        CLibCEC::AddLog(CEC_LOG_DEBUG, "device status of %s changed into 'handled by libCEC'", ToString(m_iLogicalAddress));
+      m_iStreamPath      = 0;
+      m_powerStatus      = CEC_POWER_STATUS_IN_TRANSITION_STANDBY_TO_ON;
+      m_vendor           = CEC_VENDOR_UNKNOWN;
+      m_menuState        = CEC_MENU_STATE_ACTIVATED;
+      m_bActiveSource    = false;
+      m_iLastActive      = 0;
+      m_cecVersion       = CEC_VERSION_1_3A;
+      m_deviceStatus     = newStatus;
+      break;
+    case CEC_DEVICE_STATUS_PRESENT:
+      if (m_deviceStatus != newStatus)
+        CLibCEC::AddLog(CEC_LOG_DEBUG, "device status of %s changed into 'present'", ToString(m_iLogicalAddress));
+      m_deviceStatus = newStatus;
+      break;
+    case CEC_DEVICE_STATUS_NOT_PRESENT:
+      if (m_deviceStatus != newStatus)
+        CLibCEC::AddLog(CEC_LOG_DEBUG, "device status of %s changed into 'not present'", ToString(m_iLogicalAddress));
+      m_deviceStatus = newStatus;
+      break;
+    }
   }
+
+  if (newStatus == CEC_DEVICE_STATUS_PRESENT)
+    RequestVendorId();
 }
 
 void CCECBusDevice::SetPhysicalAddress(uint16_t iNewAddress)
@@ -606,6 +630,20 @@ void CCECBusDevice::SetStreamPath(uint16_t iNewAddress, uint16_t iOldAddress /*
     CLibCEC::AddLog(CEC_LOG_DEBUG, ">> %s (%X): stream path changed from %04x to %04x", GetLogicalAddressName(), m_iLogicalAddress, iOldAddress == 0 ? m_iStreamPath : iOldAddress, iNewAddress);
     m_iStreamPath = iNewAddress;
 
+    CCECBusDevice *device = m_processor->GetDeviceByPhysicalAddress(iNewAddress, false);
+    if (device)
+    {
+      // if a device is found with the new physical address, mark it as active, which will automatically mark all other devices as inactive
+      device->SetActiveSource();
+    }
+    else
+    {
+      // try to find the device with the old address, and mark it as inactive when found
+      device = m_processor->GetDeviceByPhysicalAddress(iOldAddress, false);
+      if (device)
+        device->SetInactiveSource();
+    }
+
     if (iNewAddress > 0)
     {
       lock.Unlock();
@@ -850,6 +888,35 @@ bool CCECBusDevice::TransmitPhysicalAddress(void)
   return bReturn;
 }
 
+bool CCECBusDevice::TransmitSetMenuLanguage(cec_logical_address dest)
+{
+  bool bReturn(false);
+  cec_menu_language language = GetMenuLanguage();
+
+  char lang[3];
+  {
+    CLockObject lock(m_mutex);
+    lang[0] = language.language[0];
+    lang[1] = language.language[1];
+    lang[2] = language.language[2];
+  }
+
+  MarkBusy();
+  if (lang[0] == '?' && lang[1] == '?' && lang[2] == '?')
+  {
+      CLibCEC::AddLog(CEC_LOG_NOTICE, "<< %s (%X) -> %s (%X): Menu language feature abort", GetLogicalAddressName(), m_iLogicalAddress, ToString(dest), dest);
+      m_processor->TransmitAbort(dest, CEC_OPCODE_GIVE_DEVICE_VENDOR_ID);
+      bReturn = true;
+  }
+  else
+  {
+      CLibCEC::AddLog(CEC_LOG_NOTICE, "<< %s (%X) -> broadcast (F): Menu language '%s'", GetLogicalAddressName(), m_iLogicalAddress, lang);
+      bReturn = m_handler->TransmitSetMenuLanguage(m_iLogicalAddress, lang);
+  }
+  MarkReady();
+  return bReturn;
+}
+
 bool CCECBusDevice::TransmitPoll(cec_logical_address dest)
 {
   bool bReturn(false);
@@ -884,7 +951,10 @@ bool CCECBusDevice::TransmitPowerState(cec_logical_address dest)
   {
     CLockObject lock(m_mutex);
     if (!IsActiveSource())
+    {
+      CLibCEC::AddLog(CEC_LOG_NOTICE, "power state requested of %s (%X), but we are not the active source. setting power state to standby", GetLogicalAddressName(), m_iLogicalAddress);
       SetPowerStatus(CEC_POWER_STATUS_STANDBY);
+    }
 
     CLibCEC::AddLog(CEC_LOG_NOTICE, "<< %s (%X) -> %s (%X): %s", GetLogicalAddressName(), m_iLogicalAddress, ToString(dest), dest, ToString(m_powerStatus));
     state = m_powerStatus;
@@ -975,4 +1045,20 @@ bool CCECBusDevice::HandleReceiveFailed(void)
   return bReturn;
 }
 
+void CCECBusDevice::CheckVendorIdRequested(void)
+{
+  bool bRequestVendorId(false);
+  {
+    CLockObject lock(m_mutex);
+    bRequestVendorId = !m_bVendorIdRequested;
+    m_bVendorIdRequested = true;
+  }
+
+  if (bRequestVendorId)
+  {
+    ReplaceHandler(false);
+    GetVendorId();
+  }
+}
+
 //@}