2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
22 #if defined(HAVE_LIBCEC)
23 #include "PeripheralCecAdapter.h"
24 #include "input/XBIRRemote.h"
25 #include "Application.h"
26 #include "ApplicationMessenger.h"
27 #include "DynamicDll.h"
28 #include "threads/SingleLock.h"
29 #include "dialogs/GUIDialogKaiToast.h"
30 #include "guilib/GUIWindowManager.h"
31 #include "guilib/Key.h"
32 #include "guilib/LocalizeStrings.h"
33 #include "peripherals/Peripherals.h"
34 #include "peripherals/bus/PeripheralBus.h"
35 #include "pictures/GUIWindowSlideShow.h"
36 #include "settings/Settings.h"
37 #include "utils/log.h"
38 #include "utils/Variant.h"
40 #include <libcec/cec.h>
42 using namespace PERIPHERALS;
43 using namespace ANNOUNCEMENT;
47 #define CEC_LIB_SUPPORTED_VERSION 0x2100
49 /* time in seconds to ignore standby commands from devices after the screensaver has been activated */
50 #define SCREENSAVER_TIMEOUT 10
51 #define VOLUME_CHANGE_TIMEOUT 250
52 #define VOLUME_REFRESH_TIMEOUT 100
54 #define LOCALISED_ID_TV 36037
55 #define LOCALISED_ID_AVR 36038
56 #define LOCALISED_ID_TV_AVR 36039
57 #define LOCALISED_ID_NONE 231
59 /* time in seconds to suppress source activation after receiving OnStop */
60 #define CEC_SUPPRESS_ACTIVATE_SOURCE_AFTER_ON_STOP 2
62 class DllLibCECInterface
65 virtual ~DllLibCECInterface() {}
66 virtual ICECAdapter* CECInitialise(libcec_configuration *configuration)=0;
67 virtual void* CECDestroy(ICECAdapter *adapter)=0;
70 class DllLibCEC : public DllDynamic, DllLibCECInterface
72 DECLARE_DLL_WRAPPER(DllLibCEC, DLL_PATH_LIBCEC)
74 DEFINE_METHOD1(ICECAdapter*, CECInitialise, (libcec_configuration *p1))
75 DEFINE_METHOD1(void* , CECDestroy, (ICECAdapter *p1))
77 BEGIN_METHOD_RESOLVE()
78 RESOLVE_METHOD_RENAME(CECInitialise, CECInitialise)
79 RESOLVE_METHOD_RENAME(CECDestroy, CECDestroy)
83 CPeripheralCecAdapter::CPeripheralCecAdapter(const PeripheralScanResult& scanResult) :
84 CPeripheralHID(scanResult),
85 CThread("CECAdapter"),
90 m_features.push_back(FEATURE_CEC);
91 m_strComPort = scanResult.m_strLocation;
94 CPeripheralCecAdapter::~CPeripheralCecAdapter(void)
97 CSingleLock lock(m_critSection);
98 CAnnouncementManager::RemoveAnnouncer(this);
103 delete m_queryThread;
105 if (m_dll && m_cecAdapter)
107 m_dll->CECDestroy(m_cecAdapter);
114 void CPeripheralCecAdapter::ResetMembers(void)
116 if (m_cecAdapter && m_dll)
117 m_dll->CECDestroy(m_cecAdapter);
122 m_bHasButton = false;
124 m_bHasConnectedAudioSystem = false;
125 m_strMenuLanguage = "???";
127 m_lastChange = VOLUME_CHANGE_NONE;
129 m_bIsMuted = false; // TODO fetch the correct initial value when system audiostatus is implemented in libCEC
130 m_bGoingToStandby = false;
131 m_bIsRunning = false;
132 m_bDeviceRemoved = false;
133 m_bActiveSourcePending = false;
134 m_bStandbyPending = false;
135 m_bActiveSourceBeforeStandby = false;
136 m_bOnPlayReceived = false;
137 m_bPlaybackPaused = false;
138 m_queryThread = NULL;
140 m_currentButton.iButton = 0;
141 m_currentButton.iDuration = 0;
142 m_screensaverLastActivated.SetValid(false);
143 m_configuration.Clear();
146 void CPeripheralCecAdapter::Announce(AnnouncementFlag flag, const char *sender, const char *message, const CVariant &data)
148 if (flag == System && !strcmp(sender, "xbmc") && !strcmp(message, "OnQuit") && m_bIsReady)
150 CSingleLock lock(m_critSection);
151 m_iExitCode = (int)data.asInteger(0);
152 CAnnouncementManager::RemoveAnnouncer(this);
155 else if (flag == GUI && !strcmp(sender, "xbmc") && !strcmp(message, "OnScreensaverDeactivated") && m_bIsReady)
157 bool bIgnoreDeactivate(false);
158 if (data.isBoolean())
160 // don't respond to the deactivation if we are just going to suspend/shutdown anyway
161 // the tv will not have time to switch on before being told to standby and
162 // may not action the standby command.
163 bIgnoreDeactivate = data.asBoolean();
164 if (bIgnoreDeactivate)
165 CLog::Log(LOGDEBUG, "%s - ignoring OnScreensaverDeactivated for power action", __FUNCTION__);
167 if (m_configuration.bPowerOnScreensaver == 1 && !bIgnoreDeactivate &&
168 m_configuration.bActivateSource == 1)
173 else if (flag == GUI && !strcmp(sender, "xbmc") && !strcmp(message, "OnScreensaverActivated") && m_bIsReady)
175 // Don't put devices to standby if application is currently playing
176 if ((!g_application.m_pPlayer->IsPlaying() && !g_application.m_pPlayer->IsPaused()) && m_configuration.bPowerOffScreensaver == 1)
178 m_screensaverLastActivated = CDateTime::GetCurrentDateTime();
179 // only power off when we're the active source
180 if (m_cecAdapter->IsLibCECActiveSource())
184 else if (flag == System && !strcmp(sender, "xbmc") && !strcmp(message, "OnSleep"))
186 // this will also power off devices when we're the active source
188 CSingleLock lock(m_critSection);
189 m_bGoingToStandby = true;
193 else if (flag == System && !strcmp(sender, "xbmc") && !strcmp(message, "OnWake"))
195 CLog::Log(LOGDEBUG, "%s - reconnecting to the CEC adapter after standby mode", __FUNCTION__);
196 if (ReopenConnection())
198 bool bActivate(false);
200 CSingleLock lock(m_critSection);
201 bActivate = m_bActiveSourceBeforeStandby;
202 m_bActiveSourceBeforeStandby = false;
208 else if (flag == Player && !strcmp(sender, "xbmc") && !strcmp(message, "OnStop"))
210 CSingleLock lock(m_critSection);
211 m_preventActivateSourceOnPlay = CDateTime::GetCurrentDateTime();
212 m_bOnPlayReceived = false;
214 else if (flag == Player && !strcmp(sender, "xbmc") && !strcmp(message, "OnPlay"))
216 // activate the source when playback started, and the option is enabled
217 bool bActivateSource(false);
219 CSingleLock lock(m_critSection);
220 bActivateSource = (m_configuration.bActivateSource &&
221 !m_bOnPlayReceived &&
222 !m_cecAdapter->IsLibCECActiveSource() &&
223 (!m_preventActivateSourceOnPlay.IsValid() || CDateTime::GetCurrentDateTime() - m_preventActivateSourceOnPlay > CDateTimeSpan(0, 0, 0, CEC_SUPPRESS_ACTIVATE_SOURCE_AFTER_ON_STOP)));
224 m_bOnPlayReceived = true;
231 bool CPeripheralCecAdapter::InitialiseFeature(const PeripheralFeature feature)
233 if (feature == FEATURE_CEC && !m_bStarted && GetSettingBool("enabled"))
235 // hide settings that have an override set
236 if (!GetSettingString("wake_devices_advanced").empty())
237 SetSettingVisible("wake_devices", false);
238 if (!GetSettingString("standby_devices_advanced").empty())
239 SetSettingVisible("standby_devices", false);
241 SetConfigurationFromSettings();
243 m_callbacks.CBCecLogMessage = &CecLogMessage;
244 m_callbacks.CBCecKeyPress = &CecKeyPress;
245 m_callbacks.CBCecCommand = &CecCommand;
246 m_callbacks.CBCecConfigurationChanged = &CecConfiguration;
247 m_callbacks.CBCecAlert = &CecAlert;
248 m_callbacks.CBCecSourceActivated = &CecSourceActivated;
249 m_configuration.callbackParam = this;
250 m_configuration.callbacks = &m_callbacks;
252 m_dll = new DllLibCEC;
253 if (m_dll->Load() && m_dll->IsLoaded())
254 m_cecAdapter = m_dll->CECInitialise(&m_configuration);
257 // display warning: libCEC could not be loaded
258 CLog::Log(LOGERROR, "%s", g_localizeStrings.Get(36017).c_str());
259 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(36000), g_localizeStrings.Get(36017));
266 if (m_configuration.serverVersion < CEC_LIB_SUPPORTED_VERSION)
268 /* unsupported libcec version */
269 CLog::Log(LOGERROR, g_localizeStrings.Get(36040).c_str(), m_cecAdapter ? m_configuration.serverVersion : -1, CEC_LIB_SUPPORTED_VERSION);
271 // display warning: incompatible libCEC
272 CStdString strMessage = StringUtils::Format(g_localizeStrings.Get(36040).c_str(), m_cecAdapter ? m_configuration.serverVersion : -1, CEC_LIB_SUPPORTED_VERSION);
273 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(36000), strMessage);
276 m_dll->CECDestroy(m_cecAdapter);
284 CLog::Log(LOGDEBUG, "%s - using libCEC v%s", __FUNCTION__, m_cecAdapter->ToString((cec_server_version)m_configuration.serverVersion));
285 SetVersionInfo(m_configuration);
292 return CPeripheral::InitialiseFeature(feature);
295 void CPeripheralCecAdapter::SetVersionInfo(const libcec_configuration &configuration)
297 m_strVersionInfo = StringUtils::Format("libCEC %s - firmware v%d", m_cecAdapter->ToString((cec_server_version)configuration.serverVersion), configuration.iFirmwareVersion);
299 // append firmware build date
300 if (configuration.iFirmwareBuildDate != CEC_FW_BUILD_UNKNOWN)
302 CDateTime dt((time_t)configuration.iFirmwareBuildDate);
303 m_strVersionInfo.AppendFormat(" (%s)", dt.GetAsDBDate().c_str());
307 bool CPeripheralCecAdapter::OpenConnection(void)
311 if (!GetSettingBool("enabled"))
313 CLog::Log(LOGDEBUG, "%s - CEC adapter is disabled in peripheral settings", __FUNCTION__);
318 // open the CEC adapter
319 CLog::Log(LOGDEBUG, "%s - opening a connection to the CEC adapter: %s", __FUNCTION__, m_strComPort.c_str());
321 // scanning the CEC bus takes about 5 seconds, so display a notification to inform users that we're busy
322 CStdString strMessage = StringUtils::Format(g_localizeStrings.Get(21336).c_str(), g_localizeStrings.Get(36000).c_str());
323 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000), strMessage);
325 bool bConnectionFailedDisplayed(false);
327 while (!m_bStop && !bIsOpen)
329 if ((bIsOpen = m_cecAdapter->Open(m_strComPort.c_str(), 10000)) == false)
331 // display warning: couldn't initialise libCEC
332 CLog::Log(LOGERROR, "%s - could not opening a connection to the CEC adapter", __FUNCTION__);
333 if (!bConnectionFailedDisplayed)
334 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(36000), g_localizeStrings.Get(36012));
335 bConnectionFailedDisplayed = true;
343 CLog::Log(LOGDEBUG, "%s - connection to the CEC adapter opened", __FUNCTION__);
345 // read the configuration
346 libcec_configuration config;
347 if (m_cecAdapter->GetCurrentConfiguration(&config))
349 // update the local configuration
350 CSingleLock lock(m_critSection);
351 SetConfigurationFromLibCEC(config);
358 void CPeripheralCecAdapter::Process(void)
360 if (!OpenConnection())
364 CSingleLock lock(m_critSection);
365 m_iExitCode = EXITCODE_QUIT;
366 m_bGoingToStandby = false;
368 m_bActiveSourceBeforeStandby = false;
371 CAnnouncementManager::AddAnnouncer(this);
373 m_queryThread = new CPeripheralCecAdapterUpdateThread(this, &m_configuration);
374 m_queryThread->Create(false);
379 ProcessVolumeChange();
382 ProcessActivateSource();
385 ProcessStandbyDevices();
391 m_queryThread->StopThread(true);
393 bool bSendStandbyCommands(false);
395 CSingleLock lock(m_critSection);
396 bSendStandbyCommands = m_iExitCode != EXITCODE_REBOOT &&
397 m_iExitCode != EXITCODE_RESTARTAPP &&
399 (!m_bGoingToStandby || GetSettingBool("standby_tv_on_pc_standby")) &&
400 GetSettingBool("enabled");
402 if (m_bGoingToStandby)
403 m_bActiveSourceBeforeStandby = m_cecAdapter->IsLibCECActiveSource();
406 if (bSendStandbyCommands)
408 if (m_cecAdapter->IsLibCECActiveSource())
410 if (!m_configuration.powerOffDevices.IsEmpty())
412 CLog::Log(LOGDEBUG, "%s - sending standby commands", __FUNCTION__);
413 m_cecAdapter->StandbyDevices();
415 else if (m_configuration.bSendInactiveSource == 1)
417 CLog::Log(LOGDEBUG, "%s - sending inactive source commands", __FUNCTION__);
418 m_cecAdapter->SetInactiveView();
423 CLog::Log(LOGDEBUG, "%s - XBMC is not the active source, not sending any standby commands", __FUNCTION__);
427 m_cecAdapter->Close();
429 CLog::Log(LOGDEBUG, "%s - CEC adapter processor thread ended", __FUNCTION__);
432 CSingleLock lock(m_critSection);
434 m_bIsRunning = false;
438 bool CPeripheralCecAdapter::HasAudioControl(void)
440 CSingleLock lock(m_critSection);
441 return m_bHasConnectedAudioSystem;
444 void CPeripheralCecAdapter::SetAudioSystemConnected(bool bSetTo)
446 CSingleLock lock(m_critSection);
447 m_bHasConnectedAudioSystem = bSetTo;
450 void CPeripheralCecAdapter::ProcessVolumeChange(void)
452 bool bSendRelease(false);
453 CecVolumeChange pendingVolumeChange = VOLUME_CHANGE_NONE;
455 CSingleLock lock(m_critSection);
456 if (m_volumeChangeQueue.size() > 0)
458 /* get the first change from the queue */
459 pendingVolumeChange = m_volumeChangeQueue.front();
460 m_volumeChangeQueue.pop();
462 /* remove all dupe entries */
463 while (m_volumeChangeQueue.size() > 0 && m_volumeChangeQueue.front() == pendingVolumeChange)
464 m_volumeChangeQueue.pop();
466 /* send another keypress after VOLUME_REFRESH_TIMEOUT ms */
467 bool bRefresh(XbmcThreads::SystemClockMillis() - m_lastKeypress > VOLUME_REFRESH_TIMEOUT);
469 /* only send the keypress when it hasn't been sent yet */
470 if (pendingVolumeChange != m_lastChange)
472 m_lastKeypress = XbmcThreads::SystemClockMillis();
473 m_lastChange = pendingVolumeChange;
477 m_lastKeypress = XbmcThreads::SystemClockMillis();
478 pendingVolumeChange = m_lastChange;
481 pendingVolumeChange = VOLUME_CHANGE_NONE;
483 else if (m_lastKeypress > 0 && XbmcThreads::SystemClockMillis() - m_lastKeypress > VOLUME_CHANGE_TIMEOUT)
485 /* send a key release */
488 m_lastChange = VOLUME_CHANGE_NONE;
492 switch (pendingVolumeChange)
494 case VOLUME_CHANGE_UP:
495 m_cecAdapter->SendKeypress(CECDEVICE_AUDIOSYSTEM, CEC_USER_CONTROL_CODE_VOLUME_UP, false);
497 case VOLUME_CHANGE_DOWN:
498 m_cecAdapter->SendKeypress(CECDEVICE_AUDIOSYSTEM, CEC_USER_CONTROL_CODE_VOLUME_DOWN, false);
500 case VOLUME_CHANGE_MUTE:
501 m_cecAdapter->SendKeypress(CECDEVICE_AUDIOSYSTEM, CEC_USER_CONTROL_CODE_MUTE, false);
503 CSingleLock lock(m_critSection);
504 m_bIsMuted = !m_bIsMuted;
507 case VOLUME_CHANGE_NONE:
509 m_cecAdapter->SendKeyRelease(CECDEVICE_AUDIOSYSTEM, false);
514 void CPeripheralCecAdapter::VolumeUp(void)
516 if (HasAudioControl())
518 CSingleLock lock(m_critSection);
519 m_volumeChangeQueue.push(VOLUME_CHANGE_UP);
523 void CPeripheralCecAdapter::VolumeDown(void)
525 if (HasAudioControl())
527 CSingleLock lock(m_critSection);
528 m_volumeChangeQueue.push(VOLUME_CHANGE_DOWN);
532 void CPeripheralCecAdapter::ToggleMute(void)
534 if (HasAudioControl())
536 CSingleLock lock(m_critSection);
537 m_volumeChangeQueue.push(VOLUME_CHANGE_MUTE);
541 bool CPeripheralCecAdapter::IsMuted(void)
543 if (HasAudioControl())
545 CSingleLock lock(m_critSection);
551 void CPeripheralCecAdapter::SetMenuLanguage(const char *strLanguage)
553 if (m_strMenuLanguage.Equals(strLanguage))
556 CStdString strGuiLanguage;
558 if (!strcmp(strLanguage, "bul"))
559 strGuiLanguage = "Bulgarian";
560 else if (!strcmp(strLanguage, "hrv"))
561 strGuiLanguage = "Croatian";
562 else if (!strcmp(strLanguage, "cze"))
563 strGuiLanguage = "Czech";
564 else if (!strcmp(strLanguage, "dan"))
565 strGuiLanguage = "Danish";
566 else if (!strcmp(strLanguage, "dut"))
567 strGuiLanguage = "Dutch";
568 else if (!strcmp(strLanguage, "eng"))
569 strGuiLanguage = "English";
570 else if (!strcmp(strLanguage, "fin"))
571 strGuiLanguage = "Finnish";
572 else if (!strcmp(strLanguage, "fre"))
573 strGuiLanguage = "French";
574 else if (!strcmp(strLanguage, "ger"))
575 strGuiLanguage = "German";
576 else if (!strcmp(strLanguage, "gre"))
577 strGuiLanguage = "Greek";
578 else if (!strcmp(strLanguage, "hun"))
579 strGuiLanguage = "Hungarian";
580 else if (!strcmp(strLanguage, "ita"))
581 strGuiLanguage = "Italian";
582 else if (!strcmp(strLanguage, "nor"))
583 strGuiLanguage = "Norwegian";
584 else if (!strcmp(strLanguage, "pol"))
585 strGuiLanguage = "Polish";
586 else if (!strcmp(strLanguage, "por"))
587 strGuiLanguage = "Portuguese";
588 else if (!strcmp(strLanguage, "rum"))
589 strGuiLanguage = "Romanian";
590 else if (!strcmp(strLanguage, "rus"))
591 strGuiLanguage = "Russian";
592 else if (!strcmp(strLanguage, "srp"))
593 strGuiLanguage = "Serbian";
594 else if (!strcmp(strLanguage, "slo"))
595 strGuiLanguage = "Slovenian";
596 else if (!strcmp(strLanguage, "spa"))
597 strGuiLanguage = "Spanish";
598 else if (!strcmp(strLanguage, "swe"))
599 strGuiLanguage = "Swedish";
600 else if (!strcmp(strLanguage, "tur"))
601 strGuiLanguage = "Turkish";
603 if (!strGuiLanguage.empty())
605 CApplicationMessenger::Get().SetGUILanguage(strGuiLanguage);
606 CLog::Log(LOGDEBUG, "%s - language set to '%s'", __FUNCTION__, strGuiLanguage.c_str());
609 CLog::Log(LOGWARNING, "%s - TV menu language set to unknown value '%s'", __FUNCTION__, strLanguage);
612 int CPeripheralCecAdapter::CecCommand(void *cbParam, const cec_command command)
614 CPeripheralCecAdapter *adapter = (CPeripheralCecAdapter *)cbParam;
618 if (adapter->m_bIsReady)
620 switch (command.opcode)
622 case CEC_OPCODE_STANDBY:
623 /* a device was put in standby mode */
624 if (command.initiator == CECDEVICE_TV &&
625 (adapter->m_configuration.bPowerOffOnStandby == 1 || adapter->m_configuration.bShutdownOnStandby == 1) &&
626 (!adapter->m_screensaverLastActivated.IsValid() || CDateTime::GetCurrentDateTime() - adapter->m_screensaverLastActivated > CDateTimeSpan(0, 0, 0, SCREENSAVER_TIMEOUT)))
628 adapter->m_bStarted = false;
629 if (adapter->m_configuration.bPowerOffOnStandby == 1)
630 CApplicationMessenger::Get().Suspend();
631 else if (adapter->m_configuration.bShutdownOnStandby == 1)
632 CApplicationMessenger::Get().Shutdown();
635 case CEC_OPCODE_SET_MENU_LANGUAGE:
636 if (adapter->m_configuration.bUseTVMenuLanguage == 1 && command.initiator == CECDEVICE_TV && command.parameters.size == 3)
638 char strNewLanguage[4];
639 for (int iPtr = 0; iPtr < 3; iPtr++)
640 strNewLanguage[iPtr] = command.parameters[iPtr];
641 strNewLanguage[3] = 0;
642 adapter->SetMenuLanguage(strNewLanguage);
645 case CEC_OPCODE_DECK_CONTROL:
646 if (command.initiator == CECDEVICE_TV &&
647 command.parameters.size == 1 &&
648 command.parameters[0] == CEC_DECK_CONTROL_MODE_STOP)
652 key.keycode = CEC_USER_CONTROL_CODE_STOP;
653 adapter->PushCecKeypress(key);
656 case CEC_OPCODE_PLAY:
657 if (command.initiator == CECDEVICE_TV &&
658 command.parameters.size == 1)
660 if (command.parameters[0] == CEC_PLAY_MODE_PLAY_FORWARD)
664 key.keycode = CEC_USER_CONTROL_CODE_PLAY;
665 adapter->PushCecKeypress(key);
667 else if (command.parameters[0] == CEC_PLAY_MODE_PLAY_STILL)
671 key.keycode = CEC_USER_CONTROL_CODE_PAUSE;
672 adapter->PushCecKeypress(key);
683 int CPeripheralCecAdapter::CecConfiguration(void *cbParam, const libcec_configuration config)
685 CPeripheralCecAdapter *adapter = (CPeripheralCecAdapter *)cbParam;
689 CSingleLock lock(adapter->m_critSection);
690 adapter->SetConfigurationFromLibCEC(config);
694 int CPeripheralCecAdapter::CecAlert(void *cbParam, const libcec_alert alert, const libcec_parameter data)
696 CPeripheralCecAdapter *adapter = (CPeripheralCecAdapter *)cbParam;
700 bool bReopenConnection(false);
704 case CEC_ALERT_SERVICE_DEVICE:
705 iAlertString = 36027;
707 case CEC_ALERT_CONNECTION_LOST:
708 iAlertString = 36030;
710 #if defined(CEC_ALERT_PERMISSION_ERROR)
711 case CEC_ALERT_PERMISSION_ERROR:
712 bReopenConnection = true;
713 iAlertString = 36031;
715 case CEC_ALERT_PORT_BUSY:
716 bReopenConnection = true;
717 iAlertString = 36032;
727 CStdString strLog(g_localizeStrings.Get(iAlertString));
728 if (data.paramType == CEC_PARAMETER_TYPE_STRING && data.paramData)
729 strLog.AppendFormat(" - %s", (const char *)data.paramData);
730 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000), strLog);
733 if (bReopenConnection)
734 adapter->ReopenConnection();
739 int CPeripheralCecAdapter::CecKeyPress(void *cbParam, const cec_keypress key)
741 CPeripheralCecAdapter *adapter = (CPeripheralCecAdapter *)cbParam;
745 adapter->PushCecKeypress(key);
749 void CPeripheralCecAdapter::GetNextKey(void)
751 CSingleLock lock(m_critSection);
752 m_bHasButton = false;
755 vector<CecButtonPress>::iterator it = m_buttonQueue.begin();
756 if (it != m_buttonQueue.end())
758 m_currentButton = (*it);
759 m_buttonQueue.erase(it);
765 void CPeripheralCecAdapter::PushCecKeypress(const CecButtonPress &key)
767 CLog::Log(LOGDEBUG, "%s - received key %2x duration %d", __FUNCTION__, key.iButton, key.iDuration);
769 CSingleLock lock(m_critSection);
770 if (key.iDuration > 0)
772 if (m_currentButton.iButton == key.iButton && m_currentButton.iDuration == 0)
774 // update the duration
776 m_currentButton.iDuration = key.iDuration;
777 // ignore this one, since it's already been handled by xbmc
780 // if we received a keypress with a duration set, try to find the same one without a duration set, and replace it
781 for (vector<CecButtonPress>::reverse_iterator it = m_buttonQueue.rbegin(); it != m_buttonQueue.rend(); it++)
783 if ((*it).iButton == key.iButton)
785 if ((*it).iDuration == 0)
787 // replace this entry
788 (*it).iDuration = key.iDuration;
797 m_buttonQueue.push_back(key);
800 void CPeripheralCecAdapter::PushCecKeypress(const cec_keypress &key)
802 CecButtonPress xbmcKey;
803 xbmcKey.iDuration = key.duration;
807 case CEC_USER_CONTROL_CODE_SELECT:
808 xbmcKey.iButton = XINPUT_IR_REMOTE_SELECT;
809 PushCecKeypress(xbmcKey);
811 case CEC_USER_CONTROL_CODE_UP:
812 xbmcKey.iButton = XINPUT_IR_REMOTE_UP;
813 PushCecKeypress(xbmcKey);
815 case CEC_USER_CONTROL_CODE_DOWN:
816 xbmcKey.iButton = XINPUT_IR_REMOTE_DOWN;
817 PushCecKeypress(xbmcKey);
819 case CEC_USER_CONTROL_CODE_LEFT:
820 xbmcKey.iButton = XINPUT_IR_REMOTE_LEFT;
821 PushCecKeypress(xbmcKey);
823 case CEC_USER_CONTROL_CODE_LEFT_UP:
824 xbmcKey.iButton = XINPUT_IR_REMOTE_LEFT;
825 PushCecKeypress(xbmcKey);
826 xbmcKey.iButton = XINPUT_IR_REMOTE_UP;
827 PushCecKeypress(xbmcKey);
829 case CEC_USER_CONTROL_CODE_LEFT_DOWN:
830 xbmcKey.iButton = XINPUT_IR_REMOTE_LEFT;
831 PushCecKeypress(xbmcKey);
832 xbmcKey.iButton = XINPUT_IR_REMOTE_DOWN;
833 PushCecKeypress(xbmcKey);
835 case CEC_USER_CONTROL_CODE_RIGHT:
836 xbmcKey.iButton = XINPUT_IR_REMOTE_RIGHT;
837 PushCecKeypress(xbmcKey);
839 case CEC_USER_CONTROL_CODE_RIGHT_UP:
840 xbmcKey.iButton = XINPUT_IR_REMOTE_RIGHT;
841 PushCecKeypress(xbmcKey);
842 xbmcKey.iButton = XINPUT_IR_REMOTE_UP;
843 PushCecKeypress(xbmcKey);
845 case CEC_USER_CONTROL_CODE_RIGHT_DOWN:
846 xbmcKey.iButton = XINPUT_IR_REMOTE_RIGHT;
847 PushCecKeypress(xbmcKey);
848 xbmcKey.iButton = XINPUT_IR_REMOTE_DOWN;
849 PushCecKeypress(xbmcKey);
851 case CEC_USER_CONTROL_CODE_SETUP_MENU:
852 xbmcKey.iButton = XINPUT_IR_REMOTE_TITLE;
853 PushCecKeypress(xbmcKey);
855 case CEC_USER_CONTROL_CODE_CONTENTS_MENU:
856 case CEC_USER_CONTROL_CODE_FAVORITE_MENU:
857 case CEC_USER_CONTROL_CODE_ROOT_MENU:
858 xbmcKey.iButton = XINPUT_IR_REMOTE_MENU;
859 PushCecKeypress(xbmcKey);
861 case CEC_USER_CONTROL_CODE_EXIT:
862 xbmcKey.iButton = XINPUT_IR_REMOTE_BACK;
863 PushCecKeypress(xbmcKey);
865 case CEC_USER_CONTROL_CODE_ENTER:
866 xbmcKey.iButton = XINPUT_IR_REMOTE_ENTER;
867 PushCecKeypress(xbmcKey);
869 case CEC_USER_CONTROL_CODE_CHANNEL_DOWN:
870 xbmcKey.iButton = XINPUT_IR_REMOTE_CHANNEL_MINUS;
871 PushCecKeypress(xbmcKey);
873 case CEC_USER_CONTROL_CODE_CHANNEL_UP:
874 xbmcKey.iButton = XINPUT_IR_REMOTE_CHANNEL_PLUS;
875 PushCecKeypress(xbmcKey);
877 case CEC_USER_CONTROL_CODE_PREVIOUS_CHANNEL:
878 xbmcKey.iButton = XINPUT_IR_REMOTE_TELETEXT;
879 PushCecKeypress(xbmcKey);
881 case CEC_USER_CONTROL_CODE_SOUND_SELECT:
882 xbmcKey.iButton = XINPUT_IR_REMOTE_LANGUAGE;
883 PushCecKeypress(xbmcKey);
885 case CEC_USER_CONTROL_CODE_POWER:
886 case CEC_USER_CONTROL_CODE_POWER_TOGGLE_FUNCTION:
887 case CEC_USER_CONTROL_CODE_POWER_OFF_FUNCTION:
888 xbmcKey.iButton = XINPUT_IR_REMOTE_POWER;
889 PushCecKeypress(xbmcKey);
891 case CEC_USER_CONTROL_CODE_VOLUME_UP:
892 xbmcKey.iButton = XINPUT_IR_REMOTE_VOLUME_PLUS;
893 PushCecKeypress(xbmcKey);
895 case CEC_USER_CONTROL_CODE_VOLUME_DOWN:
896 xbmcKey.iButton = XINPUT_IR_REMOTE_VOLUME_MINUS;
897 PushCecKeypress(xbmcKey);
899 case CEC_USER_CONTROL_CODE_MUTE:
900 case CEC_USER_CONTROL_CODE_MUTE_FUNCTION:
901 case CEC_USER_CONTROL_CODE_RESTORE_VOLUME_FUNCTION:
902 xbmcKey.iButton = XINPUT_IR_REMOTE_MUTE;
903 PushCecKeypress(xbmcKey);
905 case CEC_USER_CONTROL_CODE_PLAY:
906 xbmcKey.iButton = XINPUT_IR_REMOTE_PLAY;
907 PushCecKeypress(xbmcKey);
909 case CEC_USER_CONTROL_CODE_STOP:
910 xbmcKey.iButton = XINPUT_IR_REMOTE_STOP;
911 PushCecKeypress(xbmcKey);
913 case CEC_USER_CONTROL_CODE_PAUSE:
914 xbmcKey.iButton = XINPUT_IR_REMOTE_PAUSE;
915 PushCecKeypress(xbmcKey);
917 case CEC_USER_CONTROL_CODE_REWIND:
918 xbmcKey.iButton = XINPUT_IR_REMOTE_REVERSE;
919 PushCecKeypress(xbmcKey);
921 case CEC_USER_CONTROL_CODE_FAST_FORWARD:
922 xbmcKey.iButton = XINPUT_IR_REMOTE_FORWARD;
923 PushCecKeypress(xbmcKey);
925 case CEC_USER_CONTROL_CODE_NUMBER0:
926 xbmcKey.iButton = XINPUT_IR_REMOTE_0;
927 PushCecKeypress(xbmcKey);
929 case CEC_USER_CONTROL_CODE_NUMBER1:
930 xbmcKey.iButton = XINPUT_IR_REMOTE_1;
931 PushCecKeypress(xbmcKey);
933 case CEC_USER_CONTROL_CODE_NUMBER2:
934 xbmcKey.iButton = XINPUT_IR_REMOTE_2;
935 PushCecKeypress(xbmcKey);
937 case CEC_USER_CONTROL_CODE_NUMBER3:
938 xbmcKey.iButton = XINPUT_IR_REMOTE_3;
939 PushCecKeypress(xbmcKey);
941 case CEC_USER_CONTROL_CODE_NUMBER4:
942 xbmcKey.iButton = XINPUT_IR_REMOTE_4;
943 PushCecKeypress(xbmcKey);
945 case CEC_USER_CONTROL_CODE_NUMBER5:
946 xbmcKey.iButton = XINPUT_IR_REMOTE_5;
947 PushCecKeypress(xbmcKey);
949 case CEC_USER_CONTROL_CODE_NUMBER6:
950 xbmcKey.iButton = XINPUT_IR_REMOTE_6;
951 PushCecKeypress(xbmcKey);
953 case CEC_USER_CONTROL_CODE_NUMBER7:
954 xbmcKey.iButton = XINPUT_IR_REMOTE_7;
955 PushCecKeypress(xbmcKey);
957 case CEC_USER_CONTROL_CODE_NUMBER8:
958 xbmcKey.iButton = XINPUT_IR_REMOTE_8;
959 PushCecKeypress(xbmcKey);
961 case CEC_USER_CONTROL_CODE_NUMBER9:
962 xbmcKey.iButton = XINPUT_IR_REMOTE_9;
963 PushCecKeypress(xbmcKey);
965 case CEC_USER_CONTROL_CODE_RECORD:
966 xbmcKey.iButton = XINPUT_IR_REMOTE_RECORD;
967 PushCecKeypress(xbmcKey);
969 case CEC_USER_CONTROL_CODE_CLEAR:
970 xbmcKey.iButton = XINPUT_IR_REMOTE_CLEAR;
971 PushCecKeypress(xbmcKey);
973 case CEC_USER_CONTROL_CODE_DISPLAY_INFORMATION:
974 xbmcKey.iButton = XINPUT_IR_REMOTE_INFO;
975 PushCecKeypress(xbmcKey);
977 case CEC_USER_CONTROL_CODE_PAGE_UP:
978 xbmcKey.iButton = XINPUT_IR_REMOTE_CHANNEL_PLUS;
979 PushCecKeypress(xbmcKey);
981 case CEC_USER_CONTROL_CODE_PAGE_DOWN:
982 xbmcKey.iButton = XINPUT_IR_REMOTE_CHANNEL_MINUS;
983 PushCecKeypress(xbmcKey);
985 case CEC_USER_CONTROL_CODE_FORWARD:
986 xbmcKey.iButton = XINPUT_IR_REMOTE_SKIP_PLUS;
987 PushCecKeypress(xbmcKey);
989 case CEC_USER_CONTROL_CODE_BACKWARD:
990 xbmcKey.iButton = XINPUT_IR_REMOTE_SKIP_MINUS;
991 PushCecKeypress(xbmcKey);
993 case CEC_USER_CONTROL_CODE_F1_BLUE:
994 xbmcKey.iButton = XINPUT_IR_REMOTE_BLUE;
995 PushCecKeypress(xbmcKey);
997 case CEC_USER_CONTROL_CODE_F2_RED:
998 xbmcKey.iButton = XINPUT_IR_REMOTE_RED;
999 PushCecKeypress(xbmcKey);
1001 case CEC_USER_CONTROL_CODE_F3_GREEN:
1002 xbmcKey.iButton = XINPUT_IR_REMOTE_GREEN;
1003 PushCecKeypress(xbmcKey);
1005 case CEC_USER_CONTROL_CODE_F4_YELLOW:
1006 xbmcKey.iButton = XINPUT_IR_REMOTE_YELLOW;
1007 PushCecKeypress(xbmcKey);
1009 case CEC_USER_CONTROL_CODE_ELECTRONIC_PROGRAM_GUIDE:
1010 xbmcKey.iButton = XINPUT_IR_REMOTE_GUIDE;
1011 PushCecKeypress(xbmcKey);
1013 case CEC_USER_CONTROL_CODE_AN_CHANNELS_LIST:
1014 xbmcKey.iButton = XINPUT_IR_REMOTE_LIVE_TV;
1015 PushCecKeypress(xbmcKey);
1017 case CEC_USER_CONTROL_CODE_NEXT_FAVORITE:
1018 case CEC_USER_CONTROL_CODE_DOT:
1019 case CEC_USER_CONTROL_CODE_AN_RETURN:
1020 xbmcKey.iButton = XINPUT_IR_REMOTE_TITLE; // context menu
1021 PushCecKeypress(xbmcKey);
1023 case CEC_USER_CONTROL_CODE_DATA:
1024 xbmcKey.iButton = XINPUT_IR_REMOTE_TELETEXT;
1025 PushCecKeypress(xbmcKey);
1027 case CEC_USER_CONTROL_CODE_SUB_PICTURE:
1028 xbmcKey.iButton = XINPUT_IR_REMOTE_SUBTITLE;
1029 PushCecKeypress(xbmcKey);
1031 case CEC_USER_CONTROL_CODE_POWER_ON_FUNCTION:
1032 case CEC_USER_CONTROL_CODE_EJECT:
1033 case CEC_USER_CONTROL_CODE_INPUT_SELECT:
1034 case CEC_USER_CONTROL_CODE_INITIAL_CONFIGURATION:
1035 case CEC_USER_CONTROL_CODE_HELP:
1036 case CEC_USER_CONTROL_CODE_STOP_RECORD:
1037 case CEC_USER_CONTROL_CODE_PAUSE_RECORD:
1038 case CEC_USER_CONTROL_CODE_ANGLE:
1039 case CEC_USER_CONTROL_CODE_VIDEO_ON_DEMAND:
1040 case CEC_USER_CONTROL_CODE_TIMER_PROGRAMMING:
1041 case CEC_USER_CONTROL_CODE_PLAY_FUNCTION:
1042 case CEC_USER_CONTROL_CODE_PAUSE_PLAY_FUNCTION:
1043 case CEC_USER_CONTROL_CODE_RECORD_FUNCTION:
1044 case CEC_USER_CONTROL_CODE_PAUSE_RECORD_FUNCTION:
1045 case CEC_USER_CONTROL_CODE_STOP_FUNCTION:
1046 case CEC_USER_CONTROL_CODE_TUNE_FUNCTION:
1047 case CEC_USER_CONTROL_CODE_SELECT_MEDIA_FUNCTION:
1048 case CEC_USER_CONTROL_CODE_SELECT_AV_INPUT_FUNCTION:
1049 case CEC_USER_CONTROL_CODE_SELECT_AUDIO_INPUT_FUNCTION:
1050 case CEC_USER_CONTROL_CODE_F5:
1051 case CEC_USER_CONTROL_CODE_UNKNOWN:
1057 int CPeripheralCecAdapter::GetButton(void)
1059 CSingleLock lock(m_critSection);
1063 return m_bHasButton ? m_currentButton.iButton : 0;
1066 unsigned int CPeripheralCecAdapter::GetHoldTime(void)
1068 CSingleLock lock(m_critSection);
1072 return m_bHasButton ? m_currentButton.iDuration : 0;
1075 void CPeripheralCecAdapter::ResetButton(void)
1077 CSingleLock lock(m_critSection);
1078 m_bHasButton = false;
1080 // wait for the key release if the duration isn't 0
1081 if (m_currentButton.iDuration > 0)
1083 m_currentButton.iButton = 0;
1084 m_currentButton.iDuration = 0;
1088 void CPeripheralCecAdapter::OnSettingChanged(const CStdString &strChangedSetting)
1090 if (strChangedSetting.Equals("enabled"))
1092 bool bEnabled(GetSettingBool("enabled"));
1093 if (!bEnabled && IsRunning())
1095 CLog::Log(LOGDEBUG, "%s - closing the CEC connection", __FUNCTION__);
1098 else if (bEnabled && !IsRunning())
1100 CLog::Log(LOGDEBUG, "%s - starting the CEC connection", __FUNCTION__);
1101 SetConfigurationFromSettings();
1102 InitialiseFeature(FEATURE_CEC);
1105 else if (IsRunning())
1107 if (m_queryThread->IsRunning())
1109 CLog::Log(LOGDEBUG, "%s - sending the updated configuration to libCEC", __FUNCTION__);
1110 SetConfigurationFromSettings();
1111 m_queryThread->UpdateConfiguration(&m_configuration);
1116 CLog::Log(LOGDEBUG, "%s - restarting the CEC connection", __FUNCTION__);
1117 SetConfigurationFromSettings();
1118 InitialiseFeature(FEATURE_CEC);
1122 void CPeripheralCecAdapter::CecSourceActivated(void *cbParam, const CEC::cec_logical_address address, const uint8_t activated)
1124 CPeripheralCecAdapter *adapter = (CPeripheralCecAdapter *)cbParam;
1128 // wake up the screensaver, so the user doesn't switch to a black screen
1130 g_application.WakeUpScreenSaverAndDPMS();
1132 if (adapter->GetSettingBool("pause_playback_on_deactivate"))
1134 bool bShowingSlideshow = (g_windowManager.GetActiveWindow() == WINDOW_SLIDESHOW);
1135 CGUIWindowSlideShow *pSlideShow = bShowingSlideshow ? (CGUIWindowSlideShow *)g_windowManager.GetWindow(WINDOW_SLIDESHOW) : NULL;
1136 bool bPlayingAndDeactivated = activated == 0 && (
1137 (pSlideShow && pSlideShow->IsPlaying()) || g_application.m_pPlayer->IsPlaying());
1138 bool bPausedAndActivated = activated == 1 && adapter->m_bPlaybackPaused && (
1139 (pSlideShow && pSlideShow->IsPaused()) || g_application.m_pPlayer->IsPausedPlayback());
1140 if (bPlayingAndDeactivated)
1141 adapter->m_bPlaybackPaused = true;
1142 else if (bPausedAndActivated)
1143 adapter->m_bPlaybackPaused = false;
1145 if (bPlayingAndDeactivated || bPausedAndActivated)
1148 // pause/resume slideshow
1149 pSlideShow->OnAction(CAction(ACTION_PAUSE));
1151 // pause/resume player
1152 CApplicationMessenger::Get().MediaPause();
1157 int CPeripheralCecAdapter::CecLogMessage(void *cbParam, const cec_log_message message)
1159 CPeripheralCecAdapter *adapter = (CPeripheralCecAdapter *)cbParam;
1164 switch (message.level)
1169 case CEC_LOG_WARNING:
1170 iLevel = LOGWARNING;
1172 case CEC_LOG_NOTICE:
1175 case CEC_LOG_TRAFFIC:
1184 CLog::Log(iLevel, "%s - %s", __FUNCTION__, message.message);
1189 void CPeripheralCecAdapter::SetConfigurationFromLibCEC(const CEC::libcec_configuration &config)
1191 bool bChanged(false);
1193 // set the primary device type
1194 m_configuration.deviceTypes.Clear();
1195 m_configuration.deviceTypes.Add(config.deviceTypes[0]);
1197 // hide the "connected device" and "hdmi port number" settings when the PA was autodetected
1198 bool bPAAutoDetected(config.bAutodetectAddress == 1);
1200 SetSettingVisible("connected_device", !bPAAutoDetected);
1201 SetSettingVisible("cec_hdmi_port", !bPAAutoDetected);
1203 // set the connected device
1204 m_configuration.baseDevice = config.baseDevice;
1205 bChanged |= SetSetting("connected_device", config.baseDevice == CECDEVICE_AUDIOSYSTEM ? LOCALISED_ID_AVR : LOCALISED_ID_TV);
1207 // set the HDMI port number
1208 m_configuration.iHDMIPort = config.iHDMIPort;
1209 bChanged |= SetSetting("cec_hdmi_port", config.iHDMIPort);
1211 // set the physical address, when baseDevice or iHDMIPort are not set
1212 CStdString strPhysicalAddress("0");
1213 if (!bPAAutoDetected && (m_configuration.baseDevice == CECDEVICE_UNKNOWN ||
1214 m_configuration.iHDMIPort < CEC_MIN_HDMI_PORTNUMBER ||
1215 m_configuration.iHDMIPort > CEC_MAX_HDMI_PORTNUMBER))
1217 m_configuration.iPhysicalAddress = config.iPhysicalAddress;
1218 strPhysicalAddress = StringUtils::Format("%x", config.iPhysicalAddress);
1220 bChanged |= SetSetting("physical_address", strPhysicalAddress);
1222 // set the devices to wake when starting
1223 m_configuration.wakeDevices = config.wakeDevices;
1224 bChanged |= WriteLogicalAddresses(config.wakeDevices, "wake_devices", "wake_devices_advanced");
1226 // set the devices to power off when stopping
1227 m_configuration.powerOffDevices = config.powerOffDevices;
1228 bChanged |= WriteLogicalAddresses(config.powerOffDevices, "standby_devices", "standby_devices_advanced");
1230 // set the boolean settings
1231 m_configuration.bUseTVMenuLanguage = config.bUseTVMenuLanguage;
1232 bChanged |= SetSetting("use_tv_menu_language", m_configuration.bUseTVMenuLanguage == 1);
1234 m_configuration.bActivateSource = config.bActivateSource;
1235 bChanged |= SetSetting("activate_source", m_configuration.bActivateSource == 1);
1237 m_configuration.bPowerOffScreensaver = config.bPowerOffScreensaver;
1238 bChanged |= SetSetting("cec_standby_screensaver", m_configuration.bPowerOffScreensaver == 1);
1240 m_configuration.bPowerOnScreensaver = config.bPowerOnScreensaver;
1241 bChanged |= SetSetting("cec_wake_screensaver", m_configuration.bPowerOnScreensaver == 1);
1243 m_configuration.bPowerOffOnStandby = config.bPowerOffOnStandby;
1245 m_configuration.bSendInactiveSource = config.bSendInactiveSource;
1246 bChanged |= SetSetting("send_inactive_source", m_configuration.bSendInactiveSource == 1);
1248 m_configuration.iFirmwareVersion = config.iFirmwareVersion;
1249 m_configuration.bShutdownOnStandby = config.bShutdownOnStandby;
1251 memcpy(m_configuration.strDeviceLanguage, config.strDeviceLanguage, 3);
1252 m_configuration.iFirmwareBuildDate = config.iFirmwareBuildDate;
1254 SetVersionInfo(m_configuration);
1256 bChanged |= SetSetting("standby_pc_on_tv_standby",
1257 m_configuration.bPowerOffOnStandby == 1 ? 13011 :
1258 m_configuration.bShutdownOnStandby == 1 ? 13005 : 36028);
1261 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000), g_localizeStrings.Get(36023));
1264 void CPeripheralCecAdapter::SetConfigurationFromSettings(void)
1266 // use the same client version as libCEC version
1267 m_configuration.clientVersion = CEC_CLIENT_VERSION_CURRENT;
1269 // device name 'XBMC'
1270 snprintf(m_configuration.strDeviceName, 13, "%s", GetSettingString("device_name").c_str());
1272 // set the primary device type
1273 m_configuration.deviceTypes.Clear();
1274 int iDeviceType = GetSettingInt("device_type");
1275 if (iDeviceType != (int)CEC_DEVICE_TYPE_RECORDING_DEVICE &&
1276 iDeviceType != (int)CEC_DEVICE_TYPE_PLAYBACK_DEVICE &&
1277 iDeviceType != (int)CEC_DEVICE_TYPE_TUNER)
1278 iDeviceType = (int)CEC_DEVICE_TYPE_RECORDING_DEVICE;
1279 m_configuration.deviceTypes.Add((cec_device_type)iDeviceType);
1281 // always try to autodetect the address.
1282 // when the firmware supports this, it will override the physical address, connected device and hdmi port settings
1283 m_configuration.bAutodetectAddress = CEC_DEFAULT_SETTING_AUTODETECT_ADDRESS;
1285 // set the physical address
1286 // when set, it will override the connected device and hdmi port settings
1287 CStdString strPhysicalAddress = GetSettingString("physical_address");
1288 int iPhysicalAddress;
1289 if (sscanf(strPhysicalAddress.c_str(), "%x", &iPhysicalAddress) &&
1290 iPhysicalAddress >= CEC_PHYSICAL_ADDRESS_TV &&
1291 iPhysicalAddress <= CEC_MAX_PHYSICAL_ADDRESS)
1292 m_configuration.iPhysicalAddress = iPhysicalAddress;
1294 m_configuration.iPhysicalAddress = CEC_PHYSICAL_ADDRESS_TV;
1296 // set the connected device
1297 int iConnectedDevice = GetSettingInt("connected_device");
1298 if (iConnectedDevice == LOCALISED_ID_AVR)
1299 m_configuration.baseDevice = CECDEVICE_AUDIOSYSTEM;
1300 else if (iConnectedDevice == LOCALISED_ID_TV)
1301 m_configuration.baseDevice = CECDEVICE_TV;
1303 // set the HDMI port number
1304 int iHDMIPort = GetSettingInt("cec_hdmi_port");
1305 if (iHDMIPort >= CEC_MIN_HDMI_PORTNUMBER &&
1306 iHDMIPort <= CEC_MAX_HDMI_PORTNUMBER)
1307 m_configuration.iHDMIPort = iHDMIPort;
1309 // set the tv vendor override
1310 int iVendor = GetSettingInt("tv_vendor");
1311 if (iVendor >= CEC_MAX_VENDORID &&
1312 iVendor <= CEC_MAX_VENDORID)
1313 m_configuration.tvVendor = iVendor;
1315 // read the devices to wake when starting
1316 CStdString strWakeDevices = GetSettingString("wake_devices_advanced");
1317 StringUtils::Trim(strWakeDevices);
1318 m_configuration.wakeDevices.Clear();
1319 if (!strWakeDevices.empty())
1320 ReadLogicalAddresses(strWakeDevices, m_configuration.wakeDevices);
1322 ReadLogicalAddresses(GetSettingInt("wake_devices"), m_configuration.wakeDevices);
1324 // read the devices to power off when stopping
1325 CStdString strStandbyDevices = GetSettingString("standby_devices_advanced");
1326 StringUtils::Trim(strStandbyDevices);
1327 m_configuration.powerOffDevices.Clear();
1328 if (!strStandbyDevices.empty())
1329 ReadLogicalAddresses(strStandbyDevices, m_configuration.powerOffDevices);
1331 ReadLogicalAddresses(GetSettingInt("standby_devices"), m_configuration.powerOffDevices);
1333 // read the boolean settings
1334 m_configuration.bUseTVMenuLanguage = GetSettingBool("use_tv_menu_language") ? 1 : 0;
1335 m_configuration.bActivateSource = GetSettingBool("activate_source") ? 1 : 0;
1336 m_configuration.bPowerOffScreensaver = GetSettingBool("cec_standby_screensaver") ? 1 : 0;
1337 m_configuration.bPowerOnScreensaver = GetSettingBool("cec_wake_screensaver") ? 1 : 0;
1338 m_configuration.bSendInactiveSource = GetSettingBool("send_inactive_source") ? 1 : 0;
1340 // read the mutually exclusive boolean settings
1341 int iStandbyAction(GetSettingInt("standby_pc_on_tv_standby"));
1342 m_configuration.bPowerOffOnStandby = iStandbyAction == 13011 ? 1 : 0;
1343 m_configuration.bShutdownOnStandby = iStandbyAction == 13005 ? 1 : 0;
1345 // double tap prevention timeout in ms
1346 m_configuration.iDoubleTapTimeoutMs = GetSettingInt("double_tap_timeout_ms");
1349 void CPeripheralCecAdapter::ReadLogicalAddresses(const CStdString &strString, cec_logical_addresses &addresses)
1351 for (size_t iPtr = 0; iPtr < strString.size(); iPtr++)
1353 CStdString strDevice = strString.substr(iPtr, 1);
1354 StringUtils::Trim(strDevice);
1355 if (!strDevice.empty())
1358 if (sscanf(strDevice.c_str(), "%x", &iDevice) == 1 && iDevice >= 0 && iDevice <= 0xF)
1359 addresses.Set((cec_logical_address)iDevice);
1364 void CPeripheralCecAdapter::ReadLogicalAddresses(int iLocalisedId, cec_logical_addresses &addresses)
1367 switch (iLocalisedId)
1369 case LOCALISED_ID_TV:
1370 addresses.Set(CECDEVICE_TV);
1372 case LOCALISED_ID_AVR:
1373 addresses.Set(CECDEVICE_AUDIOSYSTEM);
1375 case LOCALISED_ID_TV_AVR:
1376 addresses.Set(CECDEVICE_TV);
1377 addresses.Set(CECDEVICE_AUDIOSYSTEM);
1379 case LOCALISED_ID_NONE:
1385 bool CPeripheralCecAdapter::WriteLogicalAddresses(const cec_logical_addresses& addresses, const string& strSettingName, const string& strAdvancedSettingName)
1387 bool bChanged(false);
1389 // only update the advanced setting if it was set by the user
1390 if (!GetSettingString(strAdvancedSettingName).empty())
1392 CStdString strPowerOffDevices;
1393 for (unsigned int iPtr = CECDEVICE_TV; iPtr <= CECDEVICE_BROADCAST; iPtr++)
1394 if (addresses[iPtr])
1395 strPowerOffDevices.AppendFormat(" %X", iPtr);
1396 StringUtils::Trim(strPowerOffDevices);
1397 bChanged = SetSetting(strAdvancedSettingName, strPowerOffDevices);
1400 int iSettingPowerOffDevices = LOCALISED_ID_NONE;
1401 if (addresses[CECDEVICE_TV] && addresses[CECDEVICE_AUDIOSYSTEM])
1402 iSettingPowerOffDevices = LOCALISED_ID_TV_AVR;
1403 else if (addresses[CECDEVICE_TV])
1404 iSettingPowerOffDevices = LOCALISED_ID_TV;
1405 else if (addresses[CECDEVICE_AUDIOSYSTEM])
1406 iSettingPowerOffDevices = LOCALISED_ID_AVR;
1407 return SetSetting(strSettingName, iSettingPowerOffDevices) || bChanged;
1410 CPeripheralCecAdapterUpdateThread::CPeripheralCecAdapterUpdateThread(CPeripheralCecAdapter *adapter, libcec_configuration *configuration) :
1411 CThread("CECAdapterUpdate"),
1413 m_configuration(*configuration),
1414 m_bNextConfigurationScheduled(false),
1417 m_nextConfiguration.Clear();
1421 CPeripheralCecAdapterUpdateThread::~CPeripheralCecAdapterUpdateThread(void)
1428 void CPeripheralCecAdapterUpdateThread::Signal(void)
1433 bool CPeripheralCecAdapterUpdateThread::UpdateConfiguration(libcec_configuration *configuration)
1435 CSingleLock lock(m_critSection);
1441 m_bNextConfigurationScheduled = true;
1442 m_nextConfiguration = *configuration;
1446 m_configuration = *configuration;
1452 bool CPeripheralCecAdapterUpdateThread::WaitReady(void)
1454 // don't wait if we're not powering up anything
1455 if (m_configuration.wakeDevices.IsEmpty() && m_configuration.bActivateSource == 0)
1458 // wait for the TV if we're configured to become the active source.
1459 // wait for the first device in the wake list otherwise.
1460 cec_logical_address waitFor = (m_configuration.bActivateSource == 1) ?
1462 m_configuration.wakeDevices.primary;
1464 cec_power_status powerStatus(CEC_POWER_STATUS_UNKNOWN);
1465 bool bContinue(true);
1466 while (bContinue && !m_adapter->m_bStop && !m_bStop && powerStatus != CEC_POWER_STATUS_ON)
1468 powerStatus = m_adapter->m_cecAdapter->GetDevicePowerStatus(waitFor);
1469 if (powerStatus != CEC_POWER_STATUS_ON)
1470 bContinue = !m_event.WaitMSec(1000);
1473 return powerStatus == CEC_POWER_STATUS_ON;
1476 void CPeripheralCecAdapterUpdateThread::UpdateMenuLanguage(void)
1478 // request the menu language of the TV
1479 if (m_configuration.bUseTVMenuLanguage == 1)
1481 CLog::Log(LOGDEBUG, "%s - requesting the menu language of the TV", __FUNCTION__);
1482 cec_menu_language language;
1483 if (m_adapter->m_cecAdapter->GetDeviceMenuLanguage(CECDEVICE_TV, &language))
1484 m_adapter->SetMenuLanguage(language.language);
1486 CLog::Log(LOGDEBUG, "%s - unknown menu language", __FUNCTION__);
1490 CLog::Log(LOGDEBUG, "%s - using TV menu language is disabled", __FUNCTION__);
1494 CStdString CPeripheralCecAdapterUpdateThread::UpdateAudioSystemStatus(void)
1496 CStdString strAmpName;
1498 /* disable the mute setting when an amp is found, because the amp handles the mute setting and
1499 set PCM output to 100% */
1500 if (m_adapter->m_cecAdapter->IsActiveDeviceType(CEC_DEVICE_TYPE_AUDIO_SYSTEM))
1502 // request the OSD name of the amp
1503 cec_osd_name ampName = m_adapter->m_cecAdapter->GetDeviceOSDName(CECDEVICE_AUDIOSYSTEM);
1504 CLog::Log(LOGDEBUG, "%s - CEC capable amplifier found (%s). volume will be controlled on the amp", __FUNCTION__, ampName.name);
1505 strAmpName.AppendFormat("%s", ampName.name);
1508 m_adapter->SetAudioSystemConnected(true);
1509 g_application.SetMute(false);
1510 g_application.SetVolume(VOLUME_MAXIMUM, false);
1515 CLog::Log(LOGDEBUG, "%s - no CEC capable amplifier found", __FUNCTION__);
1516 m_adapter->SetAudioSystemConnected(false);
1522 bool CPeripheralCecAdapterUpdateThread::SetInitialConfiguration(void)
1524 // the option to make XBMC the active source is set
1525 if (m_configuration.bActivateSource == 1)
1526 m_adapter->m_cecAdapter->SetActiveSource();
1528 // devices to wake are set
1529 cec_logical_addresses tvOnly;
1530 tvOnly.Clear(); tvOnly.Set(CECDEVICE_TV);
1531 if (!m_configuration.wakeDevices.IsEmpty() && (m_configuration.wakeDevices != tvOnly || m_configuration.bActivateSource == 0))
1532 m_adapter->m_cecAdapter->PowerOnDevices(CECDEVICE_BROADCAST);
1534 // wait until devices are powered up
1538 UpdateMenuLanguage();
1540 // request the OSD name of the TV
1541 CStdString strNotification;
1542 cec_osd_name tvName = m_adapter->m_cecAdapter->GetDeviceOSDName(CECDEVICE_TV);
1543 strNotification = StringUtils::Format("%s: %s", g_localizeStrings.Get(36016).c_str(), tvName.name);
1545 CStdString strAmpName = UpdateAudioSystemStatus();
1546 if (!strAmpName.empty())
1547 strNotification.AppendFormat("- %s", strAmpName.c_str());
1549 m_adapter->m_bIsReady = true;
1551 // and let the gui know that we're done
1552 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000), strNotification);
1554 CSingleLock lock(m_critSection);
1555 m_bIsUpdating = false;
1559 bool CPeripheralCecAdapter::IsRunning(void) const
1561 CSingleLock lock(m_critSection);
1562 return m_bIsRunning;
1565 void CPeripheralCecAdapterUpdateThread::Process(void)
1567 // set the initial configuration
1568 if (!SetInitialConfiguration())
1571 // and wait for updates
1572 bool bUpdate(false);
1576 if (bUpdate || m_event.WaitMSec(500))
1580 // set the new configuration
1581 libcec_configuration configuration;
1583 CSingleLock lock(m_critSection);
1584 configuration = m_configuration;
1585 m_bIsUpdating = false;
1588 CLog::Log(LOGDEBUG, "%s - updating the configuration", __FUNCTION__);
1589 bool bConfigSet(m_adapter->m_cecAdapter->SetConfiguration(&configuration));
1590 // display message: config updated / failed to update
1592 CLog::Log(LOGERROR, "%s - libCEC couldn't set the new configuration", __FUNCTION__);
1595 UpdateMenuLanguage();
1596 UpdateAudioSystemStatus();
1599 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000), g_localizeStrings.Get(bConfigSet ? 36023 : 36024));
1602 CSingleLock lock(m_critSection);
1603 if ((bUpdate = m_bNextConfigurationScheduled) == true)
1605 // another update is scheduled
1606 m_bNextConfigurationScheduled = false;
1607 m_configuration = m_nextConfiguration;
1611 // nothing left to do, wait for updates
1612 m_bIsUpdating = false;
1620 void CPeripheralCecAdapter::OnDeviceRemoved(void)
1622 CSingleLock lock(m_critSection);
1623 m_bDeviceRemoved = true;
1626 bool CPeripheralCecAdapter::ReopenConnection(void)
1628 // stop running thread
1630 CSingleLock lock(m_critSection);
1631 m_iExitCode = EXITCODE_RESTARTAPP;
1632 CAnnouncementManager::RemoveAnnouncer(this);
1637 // reset all members to their defaults
1640 // reopen the connection
1641 return InitialiseFeature(FEATURE_CEC);
1644 void CPeripheralCecAdapter::ActivateSource(void)
1646 CSingleLock lock(m_critSection);
1647 m_bActiveSourcePending = true;
1650 void CPeripheralCecAdapter::ProcessActivateSource(void)
1652 bool bActivate(false);
1655 CSingleLock lock(m_critSection);
1656 bActivate = m_bActiveSourcePending;
1657 m_bActiveSourcePending = false;
1661 m_cecAdapter->SetActiveSource();
1664 void CPeripheralCecAdapter::StandbyDevices(void)
1666 CSingleLock lock(m_critSection);
1667 m_bStandbyPending = true;
1670 void CPeripheralCecAdapter::ProcessStandbyDevices(void)
1672 bool bStandby(false);
1675 CSingleLock lock(m_critSection);
1676 bStandby = m_bStandbyPending;
1677 m_bStandbyPending = false;
1682 m_cecAdapter->StandbyDevices(CECDEVICE_BROADCAST);
1683 if (m_configuration.bSendInactiveSource == 1)
1685 CLog::Log(LOGDEBUG, "%s - sending inactive source commands", __FUNCTION__);
1686 m_cecAdapter->SetInactiveView();
1691 bool CPeripheralCecAdapter::ToggleDeviceState(CecStateChange mode /*= STATE_SWITCH_TOGGLE */, bool forceType /*= false */)
1695 if (m_cecAdapter->IsLibCECActiveSource() && (mode == STATE_SWITCH_TOGGLE || mode == STATE_STANDBY))
1697 CLog::Log(LOGDEBUG, "%s - putting CEC device on standby...", __FUNCTION__);
1698 m_screensaverLastActivated = CDateTime::GetCurrentDateTime();
1702 else if (mode == STATE_SWITCH_TOGGLE || mode == STATE_ACTIVATE_SOURCE)
1704 CLog::Log(LOGDEBUG, "%s - waking up CEC device...", __FUNCTION__);