+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);
+ command.parameters.PushBack((uint8_t)duration);
+
+ size_t iLen = strlen(strMessage);
+ if (iLen > 13) iLen = 13;
+
+ for (size_t iPtr = 0; iPtr < iLen; iPtr++)
+ command.parameters.PushBack(strMessage[iPtr]);
+
+ return Transmit(command, false, bIsReply);
+}
+
+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);
+ command.parameters.PushBack((uint8_t) ((iPhysicalAddress >> 8) & 0xFF));
+ command.parameters.PushBack((uint8_t) (iPhysicalAddress & 0xFF));
+ command.parameters.PushBack((uint8_t) (type));
+
+ return Transmit(command, false, bIsReply);
+}
+
+bool CCECCommandHandler::TransmitSetMenuLanguage(const cec_logical_address iInitiator, const char lang[3], bool bIsReply)
+{
+ cec_command command;
+ command.Format(command, iInitiator, CECDEVICE_BROADCAST, CEC_OPCODE_SET_MENU_LANGUAGE);
+ command.parameters.PushBack((uint8_t) lang[0]);
+ command.parameters.PushBack((uint8_t) lang[1]);
+ command.parameters.PushBack((uint8_t) lang[2]);
+
+ return Transmit(command, false, bIsReply);
+}
+
+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, false, bIsReply);
+}
+
+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, false, bIsReply);
+}
+
+bool CCECCommandHandler::TransmitVendorID(const cec_logical_address iInitiator, uint64_t iVendorId, bool bIsReply)
+{
+ cec_command command;
+ cec_command::Format(command, iInitiator, CECDEVICE_BROADCAST, CEC_OPCODE_DEVICE_VENDOR_ID);
+
+ command.parameters.PushBack((uint8_t) (((uint64_t)iVendorId >> 16) & 0xFF));
+ command.parameters.PushBack((uint8_t) (((uint64_t)iVendorId >> 8) & 0xFF));
+ command.parameters.PushBack((uint8_t) ((uint64_t)iVendorId & 0xFF));
+
+ return Transmit(command, false, bIsReply);
+}
+
+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, false, bIsReply);
+}
+
+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, false, bIsReply);
+}
+
+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, false, bIsReply);
+}
+
+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, false, bIsReply);
+}
+
+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, false, bIsReply);
+}
+
+bool CCECCommandHandler::TransmitKeypress(const cec_logical_address iInitiator, const cec_logical_address iDestination, cec_user_control_code key, bool bWait /* = true */)
+{
+ cec_command command;
+ cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_USER_CONTROL_PRESSED);
+ command.parameters.PushBack((uint8_t)key);
+
+ return Transmit(command, !bWait, false);
+}
+
+bool CCECCommandHandler::TransmitKeyRelease(const cec_logical_address iInitiator, const cec_logical_address iDestination, bool bWait /* = true */)
+{
+ cec_command command;
+ cec_command::Format(command, iInitiator, iDestination, CEC_OPCODE_USER_CONTROL_RELEASE);
+
+ return Transmit(command, !bWait, false);
+}
+
+bool CCECCommandHandler::Transmit(cec_command &command, bool bSuppressWait, bool bIsReply)
+{
+ bool bReturn(false);
+ cec_opcode expectedResponse(cec_command::GetResponseOpcode(command.opcode));
+ bool bExpectResponse(expectedResponse != CEC_OPCODE_NONE && !bSuppressWait);
+ command.transmit_timeout = m_iTransmitTimeout;
+
+ if (command.initiator == CECDEVICE_UNKNOWN)
+ {
+ LIB_CEC->AddLog(CEC_LOG_ERROR, "not transmitting a command without a valid initiator");
+ return bReturn;
+ }
+
+ // check whether the destination is not marked as not present or handled by libCEC
+ if (command.destination != CECDEVICE_BROADCAST && command.opcode_set)
+ {
+ 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)
+ {
+ 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;
+ }
+ }
+
+ {
+ uint8_t iTries(0), iMaxTries(!command.opcode_set ? 1 : m_iTransmitRetries + 1);
+ while (!bReturn && ++iTries <= iMaxTries && !m_busDevice->IsUnsupportedFeature(command.opcode))
+ {
+ if ((bReturn = m_processor->Transmit(command, bIsReply)) == true)
+ {
+#ifdef CEC_DEBUGGING
+ LIB_CEC->AddLog(CEC_LOG_DEBUG, "command transmitted");
+#endif
+ if (bExpectResponse)
+ {
+ 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));
+ }
+ }
+ }
+ }
+
+ return bReturn;
+}
+
+bool CCECCommandHandler::ActivateSource(bool bTransmitDelayedCommandsOnly /* = false */)
+{
+ if (m_busDevice->IsActiveSource() &&
+ m_busDevice->IsHandledByLibCEC())
+ {
+ {
+ CLockObject lock(m_mutex);
+ // 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
+ }
+ }
+
+ // 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 = !m_busDevice->TransmitImageViewOn();
+ else
+ LIB_CEC->AddLog(CEC_LOG_DEBUG, "TV not present, not sending 'image view on'");
+
+ // 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");
+
+ // switch sources (if allowed)
+ if (!bActiveSourceFailed && bSourceSwitchAllowed)
+ {
+ 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);
+ }
+ }
+
+ // 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);
+ 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::ScheduleActivateSource(uint64_t iDelay)
+{
+ CLockObject lock(m_mutex);
+ m_iActiveSourcePending = GetTimeMs() + iDelay;