sharp: check whether the 'auto power on' option is disabled and tell the user how...
[deb_libcec.git] / src / lib / implementations / CECCommandHandler.cpp
index aa2a0dca503a49d4fbb6675e78b1ed912bd959eb..12e74e3e31bfded1ffe5406d46c806b43a607739 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the libCEC(R) library.
  *
- * libCEC(R) is Copyright (C) 2011-2012 Pulse-Eight Limited.  All rights reserved.
+ * libCEC(R) is Copyright (C) 2011-2013 Pulse-Eight Limited.  All rights reserved.
  * libCEC(R) is an original work, containing original code.
  *
  * libCEC(R) is a trademark of Pulse-Eight Limited.
  *     http://www.pulse-eight.net/
  */
 
+#include "env.h"
 #include "CECCommandHandler.h"
-#include "../devices/CECBusDevice.h"
-#include "../devices/CECAudioSystem.h"
-#include "../devices/CECPlaybackDevice.h"
-#include "../CECClient.h"
-#include "../CECProcessor.h"
-#include "../LibCEC.h"
-#include "../CECTypeUtils.h"
-#include "../platform/util/util.h"
+
+#include "lib/devices/CECBusDevice.h"
+#include "lib/devices/CECAudioSystem.h"
+#include "lib/devices/CECPlaybackDevice.h"
+#include "lib/CECClient.h"
+#include "lib/CECProcessor.h"
+#include "lib/LibCEC.h"
+#include "lib/CECTypeUtils.h"
+#include "lib/platform/util/util.h"
 
 using namespace CEC;
 using namespace std;
@@ -46,24 +48,24 @@ using namespace PLATFORM;
 
 #define LIB_CEC     m_busDevice->GetProcessor()->GetLib()
 #define ToString(p) CCECTypeUtils::ToString(p)
+#define REQUEST_POWER_STATUS_TIMEOUT 5000
 
-CCECCommandHandler::CCECCommandHandler(CCECBusDevice *busDevice) :
+CCECCommandHandler::CCECCommandHandler(CCECBusDevice *busDevice,
+                                       int32_t iTransmitTimeout /* = CEC_DEFAULT_TRANSMIT_TIMEOUT */,
+                                       int32_t iTransmitWait /* = CEC_DEFAULT_TRANSMIT_WAIT */,
+                                       int8_t iTransmitRetries /* = CEC_DEFAULT_TRANSMIT_RETRIES */,
+                                       int64_t iActiveSourcePending /* = 0 */) :
     m_busDevice(busDevice),
     m_processor(m_busDevice->GetProcessor()),
-    m_iTransmitTimeout(CEC_DEFAULT_TRANSMIT_TIMEOUT),
-    m_iTransmitWait(CEC_DEFAULT_TRANSMIT_WAIT),
-    m_iTransmitRetries(CEC_DEFAULT_TRANSMIT_RETRIES),
+    m_iTransmitTimeout(iTransmitTimeout),
+    m_iTransmitWait(iTransmitWait),
+    m_iTransmitRetries(iTransmitRetries),
     m_bHandlerInited(false),
     m_bOPTSendDeckStatusUpdateOnActiveSource(false),
     m_vendorId(CEC_VENDOR_UNKNOWN),
-    m_waitForResponse(new CWaitForResponse),
-    m_bActiveSourcePending(false)
-{
-}
-
-CCECCommandHandler::~CCECCommandHandler(void)
+    m_iActiveSourcePending(iActiveSourcePending),
+    m_iPowerStatusRequested(0)
 {
-  DELETE_AND_NULL(m_waitForResponse);
 }
 
 bool CCECCommandHandler::HandleCommand(const cec_command &command)
@@ -73,9 +75,7 @@ bool CCECCommandHandler::HandleCommand(const cec_command &command)
 
   int iHandled(CEC_ABORT_REASON_UNRECOGNIZED_OPCODE);
 
-  CCECClient *client = m_busDevice->GetClient();
-  if (client)
-    client->AddCommand(command);
+  LIB_CEC->AddCommand(command);
 
   switch(command.opcode)
   {
@@ -197,7 +197,7 @@ bool CCECCommandHandler::HandleCommand(const cec_command &command)
   }
 
   if (iHandled == COMMAND_HANDLED)
-    m_waitForResponse->Received((command.opcode == CEC_OPCODE_FEATURE_ABORT && command.parameters.size > 0) ? (cec_opcode)command.parameters[0] : command.opcode);
+    m_busDevice->SignalOpcode((command.opcode == CEC_OPCODE_FEATURE_ABORT && command.parameters.size > 0) ? (cec_opcode)command.parameters[0] : command.opcode);
   else
     UnhandledCommand(command, (cec_abort_reason)iHandled);
 
@@ -209,12 +209,15 @@ int CCECCommandHandler::HandleActiveSource(const cec_command &command)
   if (command.parameters.size == 2)
   {
     uint16_t iAddress = ((uint16_t)command.parameters[0] << 8) | ((uint16_t)command.parameters[1]);
-    CCECBusDevice *device = m_processor->GetDeviceByPhysicalAddress(iAddress);
+    CCECBusDevice *device = m_processor->GetDevice(command.initiator);
     if (device)
     {
+      device->SetPhysicalAddress(iAddress);
       device->MarkAsActiveSource();
-      return COMMAND_HANDLED;
     }
+
+    m_processor->GetDevices()->SignalAll(command.opcode);
+    return COMMAND_HANDLED;
   }
 
   return CEC_ABORT_REASON_INVALID_OPERAND;
@@ -248,7 +251,7 @@ int CCECCommandHandler::HandleDeviceCecVersion(const cec_command &command)
 
 int CCECCommandHandler::HandleDeviceVendorCommandWithId(const cec_command & UNUSED(command))
 {
-  return CEC_ABORT_REASON_REFUSED;
+  return CEC_ABORT_REASON_INVALID_OPERAND;
 }
 
 int CCECCommandHandler::HandleDeviceVendorId(const cec_command &command)
@@ -271,7 +274,7 @@ int CCECCommandHandler::HandleGetCecVersion(const cec_command &command)
   if (m_processor->CECInitialised() && m_processor->IsHandledByLibCEC(command.destination))
   {
     CCECBusDevice *device = GetDevice(command.destination);
-    if (device && device->TransmitCECVersion(command.initiator))
+    if (device && device->TransmitCECVersion(command.initiator, true))
       return COMMAND_HANDLED;
     return CEC_ABORT_REASON_INVALID_OPERAND;
   }
@@ -284,7 +287,7 @@ int CCECCommandHandler::HandleGiveAudioStatus(const cec_command &command)
   if (m_processor->CECInitialised() && m_processor->IsHandledByLibCEC(command.destination))
   {
     CCECAudioSystem *device = CCECBusDevice::AsAudioSystem(GetDevice(command.destination));
-    if (device && device->TransmitAudioStatus(command.initiator))
+    if (device && device->TransmitAudioStatus(command.initiator, true))
       return COMMAND_HANDLED;
     return CEC_ABORT_REASON_INVALID_OPERAND;
   }
@@ -297,7 +300,7 @@ int CCECCommandHandler::HandleGiveDeckStatus(const cec_command &command)
   if (m_processor->CECInitialised() && m_processor->IsHandledByLibCEC(command.destination))
   {
     CCECPlaybackDevice *device = CCECBusDevice::AsPlaybackDevice(GetDevice(command.destination));
-    if (device && device->TransmitDeckStatus(command.initiator))
+    if (device && device->TransmitDeckStatus(command.initiator, true))
       return COMMAND_HANDLED;
     return CEC_ABORT_REASON_INVALID_OPERAND;
   }
@@ -310,7 +313,7 @@ int CCECCommandHandler::HandleGiveDevicePowerStatus(const cec_command &command)
   if (m_processor->CECInitialised() && m_processor->IsHandledByLibCEC(command.destination))
   {
     CCECBusDevice *device = GetDevice(command.destination);
-    if (device && device->TransmitPowerState(command.initiator))
+    if (device && device->TransmitPowerState(command.initiator, true))
       return COMMAND_HANDLED;
     return CEC_ABORT_REASON_INVALID_OPERAND;
   }
@@ -323,7 +326,7 @@ int CCECCommandHandler::HandleGiveDeviceVendorId(const cec_command &command)
   if (m_processor->CECInitialised() && m_processor->IsHandledByLibCEC(command.destination))
   {
     CCECBusDevice *device = GetDevice(command.destination);
-    if (device && device->TransmitVendorID(command.initiator))
+    if (device && device->TransmitVendorID(command.initiator, true, true))
       return COMMAND_HANDLED;
   }
 
@@ -335,7 +338,7 @@ int CCECCommandHandler::HandleGiveOSDName(const cec_command &command)
   if (m_processor->CECInitialised() && m_processor->IsHandledByLibCEC(command.destination))
   {
     CCECBusDevice *device = GetDevice(command.destination);
-    if (device && device->TransmitOSDName(command.initiator))
+    if (device && device->TransmitOSDName(command.initiator, true))
       return COMMAND_HANDLED;
   }
 
@@ -347,7 +350,7 @@ int CCECCommandHandler::HandleGivePhysicalAddress(const cec_command &command)
   if (m_processor->CECInitialised() && m_processor->IsHandledByLibCEC(command.destination))
   {
     CCECBusDevice *device = GetDevice(command.destination);
-    if (device && device->TransmitPhysicalAddress())
+    if (device && device->TransmitPhysicalAddress(true))
       return COMMAND_HANDLED;
     return CEC_ABORT_REASON_INVALID_OPERAND;
   }
@@ -360,7 +363,7 @@ int CCECCommandHandler::HandleGiveMenuLanguage(const cec_command &command)
   if (m_processor->CECInitialised() && m_processor->IsHandledByLibCEC(command.destination))
   {
     CCECBusDevice *device = GetDevice(command.destination);
-    if (device && device->TransmitSetMenuLanguage(command.initiator))
+    if (device && device->TransmitSetMenuLanguage(command.initiator, true))
       return COMMAND_HANDLED;
     return CEC_ABORT_REASON_INVALID_OPERAND;
   }
@@ -373,7 +376,7 @@ int CCECCommandHandler::HandleGiveSystemAudioModeStatus(const cec_command &comma
   if (m_processor->CECInitialised() && m_processor->IsHandledByLibCEC(command.destination))
   {
     CCECAudioSystem *device = CCECBusDevice::AsAudioSystem(GetDevice(command.destination));
-    if (device && device->TransmitSystemAudioModeStatus(command.initiator))
+    if (device && device->TransmitSystemAudioModeStatus(command.initiator, true))
       return COMMAND_HANDLED;
     return CEC_ABORT_REASON_INVALID_OPERAND;
   }
@@ -383,7 +386,16 @@ int CCECCommandHandler::HandleGiveSystemAudioModeStatus(const cec_command &comma
 
 int CCECCommandHandler::HandleImageViewOn(const cec_command &command)
 {
-  m_processor->GetDevice(command.initiator)->MarkAsActiveSource();
+  CCECBusDevice *device = GetDevice(command.destination);
+  if (device && device->GetCurrentStatus() == CEC_DEVICE_STATUS_PRESENT)
+  {
+    if (device->GetCurrentPowerStatus() == CEC_POWER_STATUS_STANDBY ||
+        device->GetCurrentPowerStatus() == CEC_POWER_STATUS_IN_TRANSITION_ON_TO_STANDBY)
+      device->SetPowerStatus(CEC_POWER_STATUS_IN_TRANSITION_STANDBY_TO_ON);
+    CCECBusDevice* tv = GetDevice(CECDEVICE_TV);
+    if (tv)
+      tv->OnImageViewOnSent(false);
+  }
   return COMMAND_HANDLED;
 }
 
@@ -408,7 +420,7 @@ int CCECCommandHandler::HandleMenuRequest(const cec_command &command)
             device->SetMenuState(CEC_MENU_STATE_DEACTIVATED);
         }
       }
-      if (device->TransmitMenuState(command.initiator))
+      if (device->TransmitMenuState(command.initiator, true))
         return COMMAND_HANDLED;
     }
     return CEC_ABORT_REASON_INVALID_OPERAND;
@@ -471,7 +483,7 @@ int CCECCommandHandler::HandleRequestActiveSource(const cec_command &command)
 
     vector<CCECBusDevice *> devices;
     for (size_t iDevicePtr = 0; iDevicePtr < GetMyDevices(devices); iDevicePtr++)
-      devices[iDevicePtr]->TransmitActiveSource();
+      devices[iDevicePtr]->TransmitActiveSource(true);
   }
 
   return COMMAND_HANDLED;
@@ -481,13 +493,11 @@ int CCECCommandHandler::HandleRoutingChange(const cec_command &command)
 {
   if (command.parameters.size == 4)
   {
-    uint16_t iOldAddress = ((uint16_t)command.parameters[0] << 8) | ((uint16_t)command.parameters[1]);
-    uint16_t iNewAddress = ((uint16_t)command.parameters[2] << 8) | ((uint16_t)command.parameters[3]);
-
     CCECBusDevice *device = GetDevice(command.initiator);
     if (device)
     {
-      device->SetStreamPath(iNewAddress, iOldAddress);
+      uint16_t iNewAddress = ((uint16_t)command.parameters[2] << 8) | ((uint16_t)command.parameters[3]);
+      device->SetActiveRoute(iNewAddress);
       return COMMAND_HANDLED;
     }
   }
@@ -499,11 +509,11 @@ int CCECCommandHandler::HandleRoutingInformation(const cec_command &command)
 {
   if (command.parameters.size == 2)
   {
-    uint16_t iNewAddress = ((uint16_t)command.parameters[0] << 8) | ((uint16_t)command.parameters[1]);
-    CCECBusDevice *device = m_processor->GetDeviceByPhysicalAddress(iNewAddress);
+    CCECBusDevice *device = GetDevice(command.initiator);
     if (device)
     {
-      device->MarkAsActiveSource();
+      uint16_t iNewAddress = ((uint16_t)command.parameters[0] << 8) | ((uint16_t)command.parameters[1]);
+      device->SetActiveRoute(iNewAddress);
       return COMMAND_HANDLED;
     }
   }
@@ -561,13 +571,16 @@ int CCECCommandHandler::HandleSetStreamPath(const cec_command &command)
   if (command.parameters.size >= 2)
   {
     uint16_t iStreamAddress = ((uint16_t)command.parameters[0] << 8) | ((uint16_t)command.parameters[1]);
-    LIB_CEC->AddLog(CEC_LOG_DEBUG, ">> %i sets stream path to physical address %04x", command.initiator, iStreamAddress);
+    LIB_CEC->AddLog(CEC_LOG_DEBUG, ">> %s (%x) sets stream path to physical address %04x", ToString(command.initiator), command.initiator, iStreamAddress);
 
     /* one of the device handled by libCEC has been made active */
     CCECBusDevice *device = GetDeviceByPhysicalAddress(iStreamAddress);
-    if (device && device->IsHandledByLibCEC())
+    if (device)
     {
-      device->ActivateSource();
+      if (device->IsHandledByLibCEC() && !device->IsActiveSource())
+        device->ActivateSource();
+      else
+        device->MarkAsActiveSource();
       return COMMAND_HANDLED;
     }
   }
@@ -590,13 +603,13 @@ int CCECCommandHandler::HandleSystemAudioModeRequest(const cec_command &command)
         CCECBusDevice *newActiveDevice = GetDeviceByPhysicalAddress(iNewAddress);
         if (newActiveDevice)
           newActiveDevice->MarkAsActiveSource();
-        if (device->TransmitSetSystemAudioMode(command.initiator))
+        if (device->TransmitSetSystemAudioMode(command.initiator, true))
           return COMMAND_HANDLED;
       }
       else
       {
         device->SetSystemAudioModeStatus(CEC_SYSTEM_AUDIO_STATUS_OFF);
-        if (device->TransmitSetSystemAudioMode(command.initiator))
+        if (device->TransmitSetSystemAudioMode(command.initiator, true))
           return COMMAND_HANDLED;
       }
     }
@@ -665,19 +678,18 @@ int CCECCommandHandler::HandleUserControlPressed(const cec_command &command)
 
   CCECClient *client = device->GetClient();
   if (client)
-    client->AddKey();
-
-  if (command.parameters[0] <= CEC_USER_CONTROL_CODE_MAX)
     client->SetCurrentButton((cec_user_control_code) command.parameters[0]);
 
   if (command.parameters[0] == CEC_USER_CONTROL_CODE_POWER ||
-      command.parameters[0] == CEC_USER_CONTROL_CODE_POWER_ON_FUNCTION)
+      command.parameters[0] == CEC_USER_CONTROL_CODE_POWER_ON_FUNCTION||
+      command.parameters[0] == CEC_USER_CONTROL_CODE_POWER_TOGGLE_FUNCTION)
   {
     bool bPowerOn(true);
 
-    // CEC_USER_CONTROL_CODE_POWER operates as a toggle
+    // CEC_USER_CONTROL_CODE_POWER and CEC_USER_CONTROL_CODE_POWER_TOGGLE_FUNCTION operate as a toggle
     // assume CEC_USER_CONTROL_CODE_POWER_ON_FUNCTION does not
-    if (command.parameters[0] == CEC_USER_CONTROL_CODE_POWER)
+    if (command.parameters[0] == CEC_USER_CONTROL_CODE_POWER ||
+        command.parameters[0] == CEC_USER_CONTROL_CODE_POWER_TOGGLE_FUNCTION)
     {
       cec_power_status status = device->GetCurrentPowerStatus();
       bPowerOn = !(status == CEC_POWER_STATUS_ON || status == CEC_POWER_STATUS_IN_TRANSITION_STANDBY_TO_ON);
@@ -694,6 +706,12 @@ int CCECCommandHandler::HandleUserControlPressed(const cec_command &command)
       device->SetMenuState(CEC_MENU_STATE_DEACTIVATED);
     }
   }
+  else if (command.parameters[0] != CEC_USER_CONTROL_CODE_POWER_OFF_FUNCTION)
+  {
+    // we're not marked as active source, but the tv sends keypresses to us, so assume it forgot to activate us
+    if (!device->IsActiveSource() && command.initiator == CECDEVICE_TV)
+      device->MarkAsActiveSource();
+  }
 
   return COMMAND_HANDLED;
 }
@@ -713,7 +731,16 @@ int CCECCommandHandler::HandleUserControlRelease(const cec_command &command)
 
 int CCECCommandHandler::HandleVendorCommand(const cec_command & UNUSED(command))
 {
-  return CEC_ABORT_REASON_REFUSED;
+  return CEC_ABORT_REASON_INVALID_OPERAND;
+}
+
+int CCECCommandHandler::HandleVendorRemoteButtonDown(const cec_command& command)
+{
+  if (command.parameters.size == 0)
+    return CEC_ABORT_REASON_INVALID_OPERAND;
+
+  LIB_CEC->AddLog(CEC_LOG_NOTICE, "unhandled vendor remote button received with keycode %x", command.parameters[0]);
+  return COMMAND_HANDLED;
 }
 
 void CCECCommandHandler::UnhandledCommand(const cec_command &command, const cec_abort_reason reason)
@@ -722,6 +749,9 @@ void CCECCommandHandler::UnhandledCommand(const cec_command &command, const cec_
   {
     LIB_CEC->AddLog(CEC_LOG_DEBUG, "sending abort with opcode %02x and reason '%s' to %s", command.opcode, ToString(reason), ToString(command.initiator));
     m_processor->TransmitAbort(command.destination, command.initiator, command.opcode, reason);
+
+    if (reason == CEC_ABORT_REASON_INVALID_OPERAND)
+      RequestEmailFromCustomer(command);
   }
 }
 
@@ -782,13 +812,17 @@ void CCECCommandHandler::SetPhysicalAddress(cec_logical_address iAddress, uint16
     if (device)
       device->SetPhysicalAddress(iNewAddress);
     else
-    {
       LIB_CEC->AddLog(CEC_LOG_DEBUG, "device with logical address %X not found", iAddress);
-    }
 
     /* another device reported the same physical address as ours */
     if (client)
+    {
+      libcec_parameter param;
+      param.paramType = CEC_PARAMETER_TYPE_STRING;
+      param.paramData = (void*)"Physical address in use by another device. Please verify your settings";
+      client->Alert(CEC_ALERT_PHYSICAL_ADDRESS_ERROR, param);
       client->ResetPhysicalAddress();
+    }
   }
   else
   {
@@ -810,7 +844,14 @@ bool CCECCommandHandler::TransmitImageViewOn(const cec_logical_address iInitiato
   cec_command command;
   cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_IMAGE_VIEW_ON);
 
-  return Transmit(command);
+  if (Transmit(command, false, false))
+  {
+    CCECBusDevice* dest = m_processor->GetDevice(iDestination);
+    if (dest && dest->GetCurrentPowerStatus() != CEC_POWER_STATUS_ON)
+      dest->SetPowerStatus(CEC_POWER_STATUS_IN_TRANSITION_STANDBY_TO_ON);
+    return true;
+  }
+  return false;
 }
 
 bool CCECCommandHandler::TransmitStandby(const cec_logical_address iInitiator, const cec_logical_address iDestination)
@@ -818,7 +859,7 @@ bool CCECCommandHandler::TransmitStandby(const cec_logical_address iInitiator, c
   cec_command command;
   cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_STANDBY);
 
-  return Transmit(command);
+  return Transmit(command, false, false);
 }
 
 bool CCECCommandHandler::TransmitRequestActiveSource(const cec_logical_address iInitiator, bool bWaitForResponse /* = true */)
@@ -826,7 +867,7 @@ bool CCECCommandHandler::TransmitRequestActiveSource(const cec_logical_address i
   cec_command command;
   cec_command::Format(command, iInitiator, CECDEVICE_BROADCAST, CEC_OPCODE_REQUEST_ACTIVE_SOURCE);
 
-  return Transmit(command, !bWaitForResponse);
+  return Transmit(command, !bWaitForResponse, false);
 }
 
 bool CCECCommandHandler::TransmitRequestCecVersion(const cec_logical_address iInitiator, const cec_logical_address iDestination, bool bWaitForResponse /* = true */)
@@ -834,7 +875,7 @@ bool CCECCommandHandler::TransmitRequestCecVersion(const cec_logical_address iIn
   cec_command command;
   cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_GET_CEC_VERSION);
 
-  return Transmit(command, !bWaitForResponse);
+  return Transmit(command, !bWaitForResponse, false);
 }
 
 bool CCECCommandHandler::TransmitRequestMenuLanguage(const cec_logical_address iInitiator, const cec_logical_address iDestination, bool bWaitForResponse /* = true */)
@@ -842,7 +883,7 @@ bool CCECCommandHandler::TransmitRequestMenuLanguage(const cec_logical_address i
   cec_command command;
   cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_GET_MENU_LANGUAGE);
 
-  return Transmit(command, !bWaitForResponse);
+  return Transmit(command, !bWaitForResponse, false);
 }
 
 bool CCECCommandHandler::TransmitRequestOSDName(const cec_logical_address iInitiator, const cec_logical_address iDestination, bool bWaitForResponse /* = true */)
@@ -850,7 +891,15 @@ bool CCECCommandHandler::TransmitRequestOSDName(const cec_logical_address iIniti
   cec_command command;
   cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_GIVE_OSD_NAME);
 
-  return Transmit(command, !bWaitForResponse);
+  return Transmit(command, !bWaitForResponse, false);
+}
+
+bool CCECCommandHandler::TransmitRequestAudioStatus(const cec_logical_address iInitiator, const cec_logical_address iDestination, bool bWaitForResponse /* = true */)
+{
+  cec_command command;
+  cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_GIVE_AUDIO_STATUS);
+
+  return Transmit(command, !bWaitForResponse, false);
 }
 
 bool CCECCommandHandler::TransmitRequestPhysicalAddress(const cec_logical_address iInitiator, const cec_logical_address iDestination, bool bWaitForResponse /* = true */)
@@ -858,15 +907,25 @@ bool CCECCommandHandler::TransmitRequestPhysicalAddress(const cec_logical_addres
   cec_command command;
   cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_GIVE_PHYSICAL_ADDRESS);
 
-  return Transmit(command, !bWaitForResponse);
+  return Transmit(command, !bWaitForResponse, false);
 }
 
-bool CCECCommandHandler::TransmitRequestPowerStatus(const cec_logical_address iInitiator, const cec_logical_address iDestination, bool bWaitForResponse /* = true */)
+bool CCECCommandHandler::TransmitRequestPowerStatus(const cec_logical_address iInitiator, const cec_logical_address iDestination, bool bUpdate, bool bWaitForResponse /* = true */)
 {
+  if (iDestination == CECDEVICE_TV)
+  {
+    int64_t now(GetTimeMs());
+    if (!bUpdate && now - m_iPowerStatusRequested < REQUEST_POWER_STATUS_TIMEOUT)
+      return true;
+    m_iPowerStatusRequested = now;
+  }
+
+  LIB_CEC->AddLog(CEC_LOG_DEBUG, "<< requesting power status of '%s' (%X)", m_busDevice->GetLogicalAddressName(), iDestination);
+
   cec_command command;
   cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_GIVE_DEVICE_POWER_STATUS);
 
-  return Transmit(command, !bWaitForResponse);
+  return Transmit(command, !bWaitForResponse, false);
 }
 
 bool CCECCommandHandler::TransmitRequestVendorId(const cec_logical_address iInitiator, const cec_logical_address iDestination, bool bWaitForResponse /* = true */)
@@ -874,26 +933,26 @@ bool CCECCommandHandler::TransmitRequestVendorId(const cec_logical_address iInit
   cec_command command;
   cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_GIVE_DEVICE_VENDOR_ID);
 
-  return Transmit(command, !bWaitForResponse);
+  return Transmit(command, !bWaitForResponse, false);
 }
 
-bool CCECCommandHandler::TransmitActiveSource(const cec_logical_address iInitiator, uint16_t iPhysicalAddress)
+bool CCECCommandHandler::TransmitActiveSource(const cec_logical_address iInitiator, uint16_t iPhysicalAddress, bool bIsReply)
 {
   cec_command command;
   cec_command::Format(command, iInitiator, CECDEVICE_BROADCAST, CEC_OPCODE_ACTIVE_SOURCE);
   command.parameters.PushBack((uint8_t) ((iPhysicalAddress >> 8) & 0xFF));
   command.parameters.PushBack((uint8_t) (iPhysicalAddress & 0xFF));
 
-  return Transmit(command);
+  return Transmit(command, false, bIsReply);
 }
 
-bool CCECCommandHandler::TransmitCECVersion(const cec_logical_address iInitiator, const cec_logical_address iDestination, cec_version cecVersion)
+bool CCECCommandHandler::TransmitCECVersion(const cec_logical_address iInitiator, const cec_logical_address iDestination, cec_version cecVersion, bool bIsReply)
 {
   cec_command command;
   cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_CEC_VERSION);
   command.parameters.PushBack((uint8_t)cecVersion);
 
-  return Transmit(command);
+  return Transmit(command, false, bIsReply);
 }
 
 bool CCECCommandHandler::TransmitInactiveSource(const cec_logical_address iInitiator, uint16_t iPhysicalAddress)
@@ -903,29 +962,29 @@ bool CCECCommandHandler::TransmitInactiveSource(const cec_logical_address iIniti
   command.parameters.PushBack((iPhysicalAddress >> 8) & 0xFF);
   command.parameters.PushBack(iPhysicalAddress & 0xFF);
 
-  return Transmit(command);
+  return Transmit(command, false, false);
 }
 
-bool CCECCommandHandler::TransmitMenuState(const cec_logical_address iInitiator, const cec_logical_address iDestination, cec_menu_state menuState)
+bool CCECCommandHandler::TransmitMenuState(const cec_logical_address iInitiator, const cec_logical_address iDestination, cec_menu_state menuState, bool bIsReply)
 {
   cec_command command;
   cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_MENU_STATUS);
   command.parameters.PushBack((uint8_t)menuState);
 
-  return Transmit(command);
+  return Transmit(command, false, bIsReply);
 }
 
-bool CCECCommandHandler::TransmitOSDName(const cec_logical_address iInitiator, const cec_logical_address iDestination, CStdString strDeviceName)
+bool CCECCommandHandler::TransmitOSDName(const cec_logical_address iInitiator, const cec_logical_address iDestination, std::string strDeviceName, bool bIsReply)
 {
   cec_command command;
   cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_SET_OSD_NAME);
   for (size_t iPtr = 0; iPtr < strDeviceName.length(); iPtr++)
     command.parameters.PushBack(strDeviceName.at(iPtr));
 
-  return Transmit(command);
+  return Transmit(command, false, bIsReply);
 }
 
-bool CCECCommandHandler::TransmitOSDString(const cec_logical_address iInitiator, const cec_logical_address iDestination, cec_display_control duration, const char *strMessage)
+bool CCECCommandHandler::TransmitOSDString(const cec_logical_address iInitiator, const cec_logical_address iDestination, cec_display_control duration, const char *strMessage, bool bIsReply)
 {
   cec_command command;
   cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_SET_OSD_STRING);
@@ -937,10 +996,10 @@ bool CCECCommandHandler::TransmitOSDString(const cec_logical_address iInitiator,
   for (size_t iPtr = 0; iPtr < iLen; iPtr++)
     command.parameters.PushBack(strMessage[iPtr]);
 
-  return Transmit(command);
+  return Transmit(command, false, bIsReply);
 }
 
-bool CCECCommandHandler::TransmitPhysicalAddress(const cec_logical_address iInitiator, uint16_t iPhysicalAddress, cec_device_type type)
+bool CCECCommandHandler::TransmitPhysicalAddress(const cec_logical_address iInitiator, uint16_t iPhysicalAddress, cec_device_type type, bool bIsReply)
 {
   cec_command command;
   cec_command::Format(command, iInitiator, CECDEVICE_BROADCAST, CEC_OPCODE_REPORT_PHYSICAL_ADDRESS);
@@ -948,10 +1007,10 @@ bool CCECCommandHandler::TransmitPhysicalAddress(const cec_logical_address iInit
   command.parameters.PushBack((uint8_t) (iPhysicalAddress & 0xFF));
   command.parameters.PushBack((uint8_t) (type));
 
-  return Transmit(command);
+  return Transmit(command, false, bIsReply);
 }
 
-bool CCECCommandHandler::TransmitSetMenuLanguage(const cec_logical_address iInitiator, const char lang[3])
+bool CCECCommandHandler::TransmitSetMenuLanguage(const cec_logical_address iInitiator, const char lang[4], bool bIsReply)
 {
   cec_command command;
   command.Format(command, iInitiator, CECDEVICE_BROADCAST, CEC_OPCODE_SET_MENU_LANGUAGE);
@@ -959,27 +1018,27 @@ bool CCECCommandHandler::TransmitSetMenuLanguage(const cec_logical_address iInit
   command.parameters.PushBack((uint8_t) lang[1]);
   command.parameters.PushBack((uint8_t) lang[2]);
 
-  return Transmit(command);
+  return Transmit(command, false, bIsReply);
 }
 
-bool CCECCommandHandler::TransmitPoll(const cec_logical_address iInitiator, const cec_logical_address iDestination)
+bool CCECCommandHandler::TransmitPoll(const cec_logical_address iInitiator, const cec_logical_address iDestination, bool bIsReply)
 {
   cec_command command;
   cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_NONE);
 
-  return Transmit(command);
+  return Transmit(command, false, bIsReply);
 }
 
-bool CCECCommandHandler::TransmitPowerState(const cec_logical_address iInitiator, const cec_logical_address iDestination, cec_power_status state)
+bool CCECCommandHandler::TransmitPowerState(const cec_logical_address iInitiator, const cec_logical_address iDestination, cec_power_status state, bool bIsReply)
 {
   cec_command command;
   cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_REPORT_POWER_STATUS);
   command.parameters.PushBack((uint8_t) state);
 
-  return Transmit(command);
+  return Transmit(command, false, bIsReply);
 }
 
-bool CCECCommandHandler::TransmitVendorID(const cec_logical_address iInitiator, uint64_t iVendorId)
+bool CCECCommandHandler::TransmitVendorID(const cec_logical_address iInitiator, const cec_logical_address UNUSED(iDestination), uint64_t iVendorId, bool bIsReply)
 {
   cec_command command;
   cec_command::Format(command, iInitiator, CECDEVICE_BROADCAST, CEC_OPCODE_DEVICE_VENDOR_ID);
@@ -988,53 +1047,58 @@ bool CCECCommandHandler::TransmitVendorID(const cec_logical_address iInitiator,
   command.parameters.PushBack((uint8_t) (((uint64_t)iVendorId >> 8) & 0xFF));
   command.parameters.PushBack((uint8_t) ((uint64_t)iVendorId & 0xFF));
 
-  return Transmit(command);
+  return Transmit(command, false, bIsReply);
 }
 
-bool CCECCommandHandler::TransmitAudioStatus(const cec_logical_address iInitiator, const cec_logical_address iDestination, uint8_t state)
+bool CCECCommandHandler::TransmitAudioStatus(const cec_logical_address iInitiator, const cec_logical_address iDestination, uint8_t state, bool bIsReply)
 {
   cec_command command;
   cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_REPORT_AUDIO_STATUS);
   command.parameters.PushBack(state);
 
-  return Transmit(command);
+  return Transmit(command, false, bIsReply);
 }
 
-bool CCECCommandHandler::TransmitSetSystemAudioMode(const cec_logical_address iInitiator, const cec_logical_address iDestination, cec_system_audio_status state)
+bool CCECCommandHandler::TransmitSetSystemAudioMode(const cec_logical_address iInitiator, const cec_logical_address iDestination, cec_system_audio_status state, bool bIsReply)
 {
   cec_command command;
   cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_SET_SYSTEM_AUDIO_MODE);
   command.parameters.PushBack((uint8_t)state);
 
-  return Transmit(command);
+  return Transmit(command, false, bIsReply);
 }
 
-bool CCECCommandHandler::TransmitSetStreamPath(uint16_t iStreamPath)
+bool CCECCommandHandler::TransmitSetStreamPath(uint16_t iStreamPath, bool bIsReply)
 {
+  if (m_busDevice->GetLogicalAddress() != CECDEVICE_TV)
+  {
+    LIB_CEC->AddLog(CEC_LOG_ERROR, "only the TV is allowed to send CEC_OPCODE_SET_STREAM_PATH");
+    return false;
+  }
   cec_command command;
   cec_command::Format(command, m_busDevice->GetLogicalAddress(), CECDEVICE_BROADCAST, CEC_OPCODE_SET_STREAM_PATH);
   command.parameters.PushBack((uint8_t) ((iStreamPath >> 8) & 0xFF));
   command.parameters.PushBack((uint8_t) (iStreamPath        & 0xFF));
 
-  return Transmit(command);
+  return Transmit(command, false, bIsReply);
 }
 
-bool CCECCommandHandler::TransmitSystemAudioModeStatus(const cec_logical_address iInitiator, const cec_logical_address iDestination, cec_system_audio_status state)
+bool CCECCommandHandler::TransmitSystemAudioModeStatus(const cec_logical_address iInitiator, const cec_logical_address iDestination, cec_system_audio_status state, bool bIsReply)
 {
   cec_command command;
   cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_SYSTEM_AUDIO_MODE_STATUS);
   command.parameters.PushBack((uint8_t)state);
 
-  return Transmit(command);
+  return Transmit(command, false, bIsReply);
 }
 
-bool CCECCommandHandler::TransmitDeckStatus(const cec_logical_address iInitiator, const cec_logical_address iDestination, cec_deck_info state)
+bool CCECCommandHandler::TransmitDeckStatus(const cec_logical_address iInitiator, const cec_logical_address iDestination, cec_deck_info state, bool bIsReply)
 {
   cec_command command;
   cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_DECK_STATUS);
   command.PushBack((uint8_t)state);
 
-  return Transmit(command);
+  return Transmit(command, false, bIsReply);
 }
 
 bool CCECCommandHandler::TransmitKeypress(const cec_logical_address iInitiator, const cec_logical_address iDestination, cec_user_control_code key, bool bWait /* = true */)
@@ -1043,7 +1107,7 @@ bool CCECCommandHandler::TransmitKeypress(const cec_logical_address iInitiator,
   cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_USER_CONTROL_PRESSED);
   command.parameters.PushBack((uint8_t)key);
 
-  return Transmit(command, !bWait);
+  return Transmit(command, !bWait, false);
 }
 
 bool CCECCommandHandler::TransmitKeyRelease(const cec_logical_address iInitiator, const cec_logical_address iDestination, bool bWait /* = true */)
@@ -1051,10 +1115,10 @@ bool CCECCommandHandler::TransmitKeyRelease(const cec_logical_address iInitiator
   cec_command command;
   cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_USER_CONTROL_RELEASE);
 
-  return Transmit(command, !bWait);
+  return Transmit(command, !bWait, false);
 }
 
-bool CCECCommandHandler::Transmit(cec_command &command, bool bSuppressWait /* = false */)
+bool CCECCommandHandler::Transmit(cec_command &command, bool bSuppressWait, bool bIsReply)
 {
   bool bReturn(false);
   cec_opcode expectedResponse(cec_command::GetResponseOpcode(command.opcode));
@@ -1067,16 +1131,39 @@ bool CCECCommandHandler::Transmit(cec_command &command, bool bSuppressWait /* =
     return bReturn;
   }
 
+  // check whether the destination is not marked as not present or handled by libCEC
+  if (command.destination != CECDEVICE_BROADCAST && command.opcode_set)
   {
-    uint8_t iTries(0), iMaxTries(!command.opcode_set ? 1 : m_iTransmitRetries + 1);
-    while (!bReturn && ++iTries <= iMaxTries && !m_busDevice->IsUnsupportedFeature(command.opcode))
+    CCECBusDevice* destinationDevice = m_processor->GetDevice(command.destination);
+    cec_bus_device_status status = destinationDevice ? destinationDevice->GetStatus() : CEC_DEVICE_STATUS_NOT_PRESENT;
+    if (status == CEC_DEVICE_STATUS_NOT_PRESENT)
     {
-      if ((bReturn = m_processor->Transmit(command)) == true)
+      LIB_CEC->AddLog(CEC_LOG_DEBUG, "not sending command '%s': destination device '%s' marked as not present", ToString(command.opcode),ToString(command.destination));
+      return bReturn;
+    }
+    else if (status == CEC_DEVICE_STATUS_HANDLED_BY_LIBCEC)
+    {
+      LIB_CEC->AddLog(CEC_LOG_DEBUG, "not sending command '%s': destination device '%s' marked as handled by libCEC", ToString(command.opcode),ToString(command.destination));
+      return bReturn;
+    }
+    else if (destinationDevice->IsUnsupportedFeature(command.opcode))
+    {
+      return true;
+    }
+  }
+
+  {
+    uint8_t iTries(0), iMaxTries(m_iTransmitRetries + 1);
+    while (!bReturn && ++iTries <= iMaxTries)
+    {
+      if ((bReturn = m_processor->Transmit(command, bIsReply)) == true)
       {
+#ifdef CEC_DEBUGGING
         LIB_CEC->AddLog(CEC_LOG_DEBUG, "command transmitted");
+#endif
         if (bExpectResponse)
         {
-          bReturn = m_waitForResponse->Wait(expectedResponse);
+          bReturn = m_busDevice->WaitForOpcode(expectedResponse);
           LIB_CEC->AddLog(CEC_LOG_DEBUG, bReturn ? "expected response received (%X: %s)" : "expected response not received (%X: %s)", (int)expectedResponse, ToString(expectedResponse));
         }
       }
@@ -1086,50 +1173,127 @@ bool CCECCommandHandler::Transmit(cec_command &command, bool bSuppressWait /* =
   return bReturn;
 }
 
-bool CCECCommandHandler::ActivateSource(void)
+bool CCECCommandHandler::ActivateSource(bool bTransmitDelayedCommandsOnly /* = false */)
 {
   if (m_busDevice->IsActiveSource() &&
-    m_busDevice->IsHandledByLibCEC())
+      m_busDevice->IsHandledByLibCEC())
   {
     {
       CLockObject lock(m_mutex);
-      m_bActiveSourcePending = false;
+      // check if we need to send a delayed source switch
+      if (bTransmitDelayedCommandsOnly)
+      {
+        if (m_iActiveSourcePending == 0 || GetTimeMs() < m_iActiveSourcePending)
+          return false;
+
+#ifdef CEC_DEBUGGING
+        LIB_CEC->AddLog(CEC_LOG_DEBUG, "transmitting delayed activate source command");
+#endif
+      }
     }
 
-    m_busDevice->SetPowerStatus(CEC_POWER_STATUS_ON);
-    m_busDevice->SetMenuState(CEC_MENU_STATE_ACTIVATED);
+    // update the power state and menu state
+    if (!bTransmitDelayedCommandsOnly)
+    {
+      m_busDevice->SetPowerStatus(CEC_POWER_STATUS_ON);
+      m_busDevice->SetMenuState(CEC_MENU_STATE_ACTIVATED);
+    }
+
+    // vendor specific hook
+    VendorPreActivateSourceHook();
+
+    // power on the TV
+    CCECBusDevice* tv = m_processor->GetDevice(CECDEVICE_TV);
+    bool bTvPresent = (tv && tv->GetStatus() == CEC_DEVICE_STATUS_PRESENT);
+    bool bActiveSourceFailed(false);
+    if (bTvPresent)
+      bActiveSourceFailed = !tv->PowerOn(m_busDevice->GetLogicalAddress());
+    else
+      LIB_CEC->AddLog(CEC_LOG_DEBUG, "TV not present, not sending 'image view on'");
 
-    bool bActiveSourceFailed = !m_busDevice->TransmitImageViewOn() ||
-                               !m_busDevice->TransmitActiveSource() ||
-                               !m_busDevice->TransmitMenuState(CECDEVICE_TV);
+    // check if we're allowed to switch sources
+    bool bSourceSwitchAllowed = SourceSwitchAllowed();
+    if (!bSourceSwitchAllowed)
+      LIB_CEC->AddLog(CEC_LOG_DEBUG, "source switch is currently not allowed by command handler");
 
-    if (!bActiveSourceFailed)
+    // switch sources (if allowed)
+    if (!bActiveSourceFailed && bSourceSwitchAllowed)
     {
-      CCECPlaybackDevice *playbackDevice = m_busDevice->AsPlaybackDevice();
-      if (playbackDevice && SendDeckStatusUpdateOnActiveSource())
-        bActiveSourceFailed = !playbackDevice->TransmitDeckStatus(CECDEVICE_TV);
+      bActiveSourceFailed = !m_busDevice->TransmitActiveSource(false);
+      if (bTvPresent && !bActiveSourceFailed)
+        bActiveSourceFailed = !m_busDevice->TransmitMenuState(CECDEVICE_TV, false);
+
+      // update the deck status for playback devices
+      if (bTvPresent && !bActiveSourceFailed)
+      {
+        CCECPlaybackDevice *playbackDevice = m_busDevice->AsPlaybackDevice();
+        if (playbackDevice && SendDeckStatusUpdateOnActiveSource())
+          bActiveSourceFailed = !playbackDevice->TransmitDeckStatus(CECDEVICE_TV, false);
+      }
+
+      // update system audio mode for audiosystem devices
+      if (bTvPresent && !bActiveSourceFailed)
+      {
+        CCECAudioSystem* audioDevice = m_busDevice->AsAudioSystem();
+        if (audioDevice)
+          bActiveSourceFailed = !audioDevice->TransmitSetSystemAudioMode(CECDEVICE_TV, false);
+      }
     }
 
-    if (bActiveSourceFailed)
+    // retry later
+    if (bActiveSourceFailed || !bSourceSwitchAllowed)
     {
       LIB_CEC->AddLog(CEC_LOG_DEBUG, "failed to make '%s' the active source. will retry later", m_busDevice->GetLogicalAddressName());
+      int64_t now(GetTimeMs());
       CLockObject lock(m_mutex);
-      m_bActiveSourcePending = true;
+      if (m_iActiveSourcePending == 0 || m_iActiveSourcePending < now)
+        m_iActiveSourcePending = now + (int64_t)CEC_ACTIVE_SOURCE_SWITCH_RETRY_TIME_MS;
       return false;
     }
+    else
+    {
+      CLockObject lock(m_mutex);
+      // clear previous pending active source command
+      m_iActiveSourcePending = 0;
+    }
 
+    // mark the handler as initialised
+    CLockObject lock(m_mutex);
     m_bHandlerInited = true;
   }
   return true;
 }
 
-void CCECCommandHandler::SignalOpcode(cec_opcode opcode)
+void CCECCommandHandler::ScheduleActivateSource(uint64_t iDelay)
 {
-  m_waitForResponse->Received(opcode);
+  CLockObject lock(m_mutex);
+  m_iActiveSourcePending = GetTimeMs() + iDelay;
 }
 
-bool CCECCommandHandler::ActiveSourcePending(void)
+void CCECCommandHandler::RequestEmailFromCustomer(const cec_command& command)
 {
-  CLockObject lock(m_mutex);
-  return m_bActiveSourcePending;
+  bool bInserted(false);
+  map<cec_opcode, vector<cec_command> >::iterator it = m_logsRequested.find(command.opcode);
+  if (it != m_logsRequested.end())
+  {
+    for (vector<cec_command>::const_iterator it2 = it->second.begin(); it2 != it->second.end(); it2++)
+    {
+      // we already logged this one
+      if ((*it2).parameters == command.parameters)
+        return;
+    }
+
+    it->second.push_back(command);
+    bInserted = true;
+  }
+
+  if (!bInserted)
+  {
+    vector<cec_command> commands;
+    commands.push_back(command);
+    m_logsRequested.insert(make_pair(command.opcode, commands));
+  }
+
+  LIB_CEC->AddLog(CEC_LOG_NOTICE, "Unmapped code detected. Please send an email to support@pulse-eight.com with the following details, and if you pressed a key, tell us which one you pressed, and we'll add support for this it.\nCEC command: %s\nVendor ID: %s (%06x)", ToString(command).c_str(), ToString(m_vendorId), m_vendorId);
 }
+