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.IsPlaying() && !g_application.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").IsEmpty())
237 SetSettingVisible("wake_devices", false);
238 if (!GetSettingString("standby_devices_advanced").IsEmpty())
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;
273 strMessage.Format(g_localizeStrings.Get(36040).c_str(), m_cecAdapter ? m_configuration.serverVersion : -1, CEC_LIB_SUPPORTED_VERSION);
274 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(36000), strMessage);
277 m_dll->CECDestroy(m_cecAdapter);
285 CLog::Log(LOGDEBUG, "%s - using libCEC v%s", __FUNCTION__, m_cecAdapter->ToString((cec_server_version)m_configuration.serverVersion));
286 SetVersionInfo(m_configuration);
293 return CPeripheral::InitialiseFeature(feature);
296 void CPeripheralCecAdapter::SetVersionInfo(const libcec_configuration &configuration)
298 m_strVersionInfo.Format("libCEC %s - firmware v%d", m_cecAdapter->ToString((cec_server_version)configuration.serverVersion), configuration.iFirmwareVersion);
300 // append firmware build date
301 if (configuration.iFirmwareBuildDate != CEC_FW_BUILD_UNKNOWN)
303 CDateTime dt((time_t)configuration.iFirmwareBuildDate);
304 m_strVersionInfo.AppendFormat(" (%s)", dt.GetAsDBDate().c_str());
308 bool CPeripheralCecAdapter::OpenConnection(void)
312 if (!GetSettingBool("enabled"))
314 CLog::Log(LOGDEBUG, "%s - CEC adapter is disabled in peripheral settings", __FUNCTION__);
319 // open the CEC adapter
320 CLog::Log(LOGDEBUG, "%s - opening a connection to the CEC adapter: %s", __FUNCTION__, m_strComPort.c_str());
322 // scanning the CEC bus takes about 5 seconds, so display a notification to inform users that we're busy
323 CStdString strMessage;
324 strMessage.Format(g_localizeStrings.Get(21336), g_localizeStrings.Get(36000));
325 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000), strMessage);
327 bool bConnectionFailedDisplayed(false);
329 while (!m_bStop && !bIsOpen)
331 if ((bIsOpen = m_cecAdapter->Open(m_strComPort.c_str(), 10000)) == false)
333 // display warning: couldn't initialise libCEC
334 CLog::Log(LOGERROR, "%s - could not opening a connection to the CEC adapter", __FUNCTION__);
335 if (!bConnectionFailedDisplayed)
336 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(36000), g_localizeStrings.Get(36012));
337 bConnectionFailedDisplayed = true;
345 CLog::Log(LOGDEBUG, "%s - connection to the CEC adapter opened", __FUNCTION__);
347 // read the configuration
348 libcec_configuration config;
349 if (m_cecAdapter->GetCurrentConfiguration(&config))
351 // update the local configuration
352 CSingleLock lock(m_critSection);
353 SetConfigurationFromLibCEC(config);
360 void CPeripheralCecAdapter::Process(void)
362 if (!OpenConnection())
366 CSingleLock lock(m_critSection);
367 m_iExitCode = EXITCODE_QUIT;
368 m_bGoingToStandby = false;
370 m_bActiveSourceBeforeStandby = false;
373 CAnnouncementManager::AddAnnouncer(this);
375 m_queryThread = new CPeripheralCecAdapterUpdateThread(this, &m_configuration);
376 m_queryThread->Create(false);
381 ProcessVolumeChange();
384 ProcessActivateSource();
387 ProcessStandbyDevices();
393 m_queryThread->StopThread(true);
395 bool bSendStandbyCommands(false);
397 CSingleLock lock(m_critSection);
398 bSendStandbyCommands = m_iExitCode != EXITCODE_REBOOT &&
399 m_iExitCode != EXITCODE_RESTARTAPP &&
401 (!m_bGoingToStandby || GetSettingBool("standby_tv_on_pc_standby")) &&
402 GetSettingBool("enabled");
404 if (m_bGoingToStandby)
405 m_bActiveSourceBeforeStandby = m_cecAdapter->IsLibCECActiveSource();
408 if (bSendStandbyCommands)
410 if (m_cecAdapter->IsLibCECActiveSource())
412 if (!m_configuration.powerOffDevices.IsEmpty())
414 CLog::Log(LOGDEBUG, "%s - sending standby commands", __FUNCTION__);
415 m_cecAdapter->StandbyDevices();
417 else if (m_configuration.bSendInactiveSource == 1)
419 CLog::Log(LOGDEBUG, "%s - sending inactive source commands", __FUNCTION__);
420 m_cecAdapter->SetInactiveView();
425 CLog::Log(LOGDEBUG, "%s - XBMC is not the active source, not sending any standby commands", __FUNCTION__);
429 m_cecAdapter->Close();
431 CLog::Log(LOGDEBUG, "%s - CEC adapter processor thread ended", __FUNCTION__);
434 CSingleLock lock(m_critSection);
436 m_bIsRunning = false;
440 bool CPeripheralCecAdapter::HasAudioControl(void)
442 CSingleLock lock(m_critSection);
443 return m_bHasConnectedAudioSystem;
446 void CPeripheralCecAdapter::SetAudioSystemConnected(bool bSetTo)
448 CSingleLock lock(m_critSection);
449 m_bHasConnectedAudioSystem = bSetTo;
452 void CPeripheralCecAdapter::ProcessVolumeChange(void)
454 bool bSendRelease(false);
455 CecVolumeChange pendingVolumeChange = VOLUME_CHANGE_NONE;
457 CSingleLock lock(m_critSection);
458 if (m_volumeChangeQueue.size() > 0)
460 /* get the first change from the queue */
461 pendingVolumeChange = m_volumeChangeQueue.front();
462 m_volumeChangeQueue.pop();
464 /* remove all dupe entries */
465 while (m_volumeChangeQueue.size() > 0 && m_volumeChangeQueue.front() == pendingVolumeChange)
466 m_volumeChangeQueue.pop();
468 /* send another keypress after VOLUME_REFRESH_TIMEOUT ms */
469 bool bRefresh(m_lastKeypress + VOLUME_REFRESH_TIMEOUT < XbmcThreads::SystemClockMillis());
471 /* only send the keypress when it hasn't been sent yet */
472 if (pendingVolumeChange != m_lastChange)
474 m_lastKeypress = XbmcThreads::SystemClockMillis();
475 m_lastChange = pendingVolumeChange;
479 m_lastKeypress = XbmcThreads::SystemClockMillis();
480 pendingVolumeChange = m_lastChange;
483 pendingVolumeChange = VOLUME_CHANGE_NONE;
485 else if (m_lastKeypress > 0 && m_lastKeypress + VOLUME_CHANGE_TIMEOUT < XbmcThreads::SystemClockMillis())
487 /* send a key release */
490 m_lastChange = VOLUME_CHANGE_NONE;
494 switch (pendingVolumeChange)
496 case VOLUME_CHANGE_UP:
497 m_cecAdapter->SendKeypress(CECDEVICE_AUDIOSYSTEM, CEC_USER_CONTROL_CODE_VOLUME_UP, false);
499 case VOLUME_CHANGE_DOWN:
500 m_cecAdapter->SendKeypress(CECDEVICE_AUDIOSYSTEM, CEC_USER_CONTROL_CODE_VOLUME_DOWN, false);
502 case VOLUME_CHANGE_MUTE:
503 m_cecAdapter->SendKeypress(CECDEVICE_AUDIOSYSTEM, CEC_USER_CONTROL_CODE_MUTE, false);
505 CSingleLock lock(m_critSection);
506 m_bIsMuted = !m_bIsMuted;
509 case VOLUME_CHANGE_NONE:
511 m_cecAdapter->SendKeyRelease(CECDEVICE_AUDIOSYSTEM, false);
516 void CPeripheralCecAdapter::VolumeUp(void)
518 if (HasAudioControl())
520 CSingleLock lock(m_critSection);
521 m_volumeChangeQueue.push(VOLUME_CHANGE_UP);
525 void CPeripheralCecAdapter::VolumeDown(void)
527 if (HasAudioControl())
529 CSingleLock lock(m_critSection);
530 m_volumeChangeQueue.push(VOLUME_CHANGE_DOWN);
534 void CPeripheralCecAdapter::ToggleMute(void)
536 if (HasAudioControl())
538 CSingleLock lock(m_critSection);
539 m_volumeChangeQueue.push(VOLUME_CHANGE_MUTE);
543 bool CPeripheralCecAdapter::IsMuted(void)
545 if (HasAudioControl())
547 CSingleLock lock(m_critSection);
553 void CPeripheralCecAdapter::SetMenuLanguage(const char *strLanguage)
555 if (m_strMenuLanguage.Equals(strLanguage))
558 CStdString strGuiLanguage;
560 if (!strcmp(strLanguage, "bul"))
561 strGuiLanguage = "Bulgarian";
562 else if (!strcmp(strLanguage, "hrv"))
563 strGuiLanguage = "Croatian";
564 else if (!strcmp(strLanguage, "cze"))
565 strGuiLanguage = "Czech";
566 else if (!strcmp(strLanguage, "dan"))
567 strGuiLanguage = "Danish";
568 else if (!strcmp(strLanguage, "dut"))
569 strGuiLanguage = "Dutch";
570 else if (!strcmp(strLanguage, "eng"))
571 strGuiLanguage = "English";
572 else if (!strcmp(strLanguage, "fin"))
573 strGuiLanguage = "Finnish";
574 else if (!strcmp(strLanguage, "fre"))
575 strGuiLanguage = "French";
576 else if (!strcmp(strLanguage, "ger"))
577 strGuiLanguage = "German";
578 else if (!strcmp(strLanguage, "gre"))
579 strGuiLanguage = "Greek";
580 else if (!strcmp(strLanguage, "hun"))
581 strGuiLanguage = "Hungarian";
582 else if (!strcmp(strLanguage, "ita"))
583 strGuiLanguage = "Italian";
584 else if (!strcmp(strLanguage, "nor"))
585 strGuiLanguage = "Norwegian";
586 else if (!strcmp(strLanguage, "pol"))
587 strGuiLanguage = "Polish";
588 else if (!strcmp(strLanguage, "por"))
589 strGuiLanguage = "Portuguese";
590 else if (!strcmp(strLanguage, "rum"))
591 strGuiLanguage = "Romanian";
592 else if (!strcmp(strLanguage, "rus"))
593 strGuiLanguage = "Russian";
594 else if (!strcmp(strLanguage, "srp"))
595 strGuiLanguage = "Serbian";
596 else if (!strcmp(strLanguage, "slo"))
597 strGuiLanguage = "Slovenian";
598 else if (!strcmp(strLanguage, "spa"))
599 strGuiLanguage = "Spanish";
600 else if (!strcmp(strLanguage, "swe"))
601 strGuiLanguage = "Swedish";
602 else if (!strcmp(strLanguage, "tur"))
603 strGuiLanguage = "Turkish";
605 if (!strGuiLanguage.IsEmpty())
607 CApplicationMessenger::Get().SetGUILanguage(strGuiLanguage);
608 CLog::Log(LOGDEBUG, "%s - language set to '%s'", __FUNCTION__, strGuiLanguage.c_str());
611 CLog::Log(LOGWARNING, "%s - TV menu language set to unknown value '%s'", __FUNCTION__, strLanguage);
614 int CPeripheralCecAdapter::CecCommand(void *cbParam, const cec_command command)
616 CPeripheralCecAdapter *adapter = (CPeripheralCecAdapter *)cbParam;
620 if (adapter->m_bIsReady)
622 switch (command.opcode)
624 case CEC_OPCODE_STANDBY:
625 /* a device was put in standby mode */
626 if (command.initiator == CECDEVICE_TV &&
627 (adapter->m_configuration.bPowerOffOnStandby == 1 || adapter->m_configuration.bShutdownOnStandby == 1) &&
628 (!adapter->m_screensaverLastActivated.IsValid() || CDateTime::GetCurrentDateTime() - adapter->m_screensaverLastActivated > CDateTimeSpan(0, 0, 0, SCREENSAVER_TIMEOUT)))
630 adapter->m_bStarted = false;
631 if (adapter->m_configuration.bPowerOffOnStandby == 1)
632 CApplicationMessenger::Get().Suspend();
633 else if (adapter->m_configuration.bShutdownOnStandby == 1)
634 CApplicationMessenger::Get().Shutdown();
637 case CEC_OPCODE_SET_MENU_LANGUAGE:
638 if (adapter->m_configuration.bUseTVMenuLanguage == 1 && command.initiator == CECDEVICE_TV && command.parameters.size == 3)
640 char strNewLanguage[4];
641 for (int iPtr = 0; iPtr < 3; iPtr++)
642 strNewLanguage[iPtr] = command.parameters[iPtr];
643 strNewLanguage[3] = 0;
644 adapter->SetMenuLanguage(strNewLanguage);
647 case CEC_OPCODE_DECK_CONTROL:
648 if (command.initiator == CECDEVICE_TV &&
649 command.parameters.size == 1 &&
650 command.parameters[0] == CEC_DECK_CONTROL_MODE_STOP)
654 key.keycode = CEC_USER_CONTROL_CODE_STOP;
655 adapter->PushCecKeypress(key);
658 case CEC_OPCODE_PLAY:
659 if (command.initiator == CECDEVICE_TV &&
660 command.parameters.size == 1)
662 if (command.parameters[0] == CEC_PLAY_MODE_PLAY_FORWARD)
666 key.keycode = CEC_USER_CONTROL_CODE_PLAY;
667 adapter->PushCecKeypress(key);
669 else if (command.parameters[0] == CEC_PLAY_MODE_PLAY_STILL)
673 key.keycode = CEC_USER_CONTROL_CODE_PAUSE;
674 adapter->PushCecKeypress(key);
685 int CPeripheralCecAdapter::CecConfiguration(void *cbParam, const libcec_configuration config)
687 CPeripheralCecAdapter *adapter = (CPeripheralCecAdapter *)cbParam;
691 CSingleLock lock(adapter->m_critSection);
692 adapter->SetConfigurationFromLibCEC(config);
696 int CPeripheralCecAdapter::CecAlert(void *cbParam, const libcec_alert alert, const libcec_parameter data)
698 CPeripheralCecAdapter *adapter = (CPeripheralCecAdapter *)cbParam;
702 bool bReopenConnection(false);
706 case CEC_ALERT_SERVICE_DEVICE:
707 iAlertString = 36027;
709 case CEC_ALERT_CONNECTION_LOST:
710 iAlertString = 36030;
712 #if defined(CEC_ALERT_PERMISSION_ERROR)
713 case CEC_ALERT_PERMISSION_ERROR:
714 bReopenConnection = true;
715 iAlertString = 36031;
717 case CEC_ALERT_PORT_BUSY:
718 bReopenConnection = true;
719 iAlertString = 36032;
729 CStdString strLog(g_localizeStrings.Get(iAlertString));
730 if (data.paramType == CEC_PARAMETER_TYPE_STRING && data.paramData)
731 strLog.AppendFormat(" - %s", (const char *)data.paramData);
732 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000), strLog);
735 if (bReopenConnection)
736 adapter->ReopenConnection();
741 int CPeripheralCecAdapter::CecKeyPress(void *cbParam, const cec_keypress key)
743 CPeripheralCecAdapter *adapter = (CPeripheralCecAdapter *)cbParam;
747 adapter->PushCecKeypress(key);
751 void CPeripheralCecAdapter::GetNextKey(void)
753 CSingleLock lock(m_critSection);
754 m_bHasButton = false;
757 vector<CecButtonPress>::iterator it = m_buttonQueue.begin();
758 if (it != m_buttonQueue.end())
760 m_currentButton = (*it);
761 m_buttonQueue.erase(it);
767 void CPeripheralCecAdapter::PushCecKeypress(const CecButtonPress &key)
769 CLog::Log(LOGDEBUG, "%s - received key %2x duration %d", __FUNCTION__, key.iButton, key.iDuration);
771 CSingleLock lock(m_critSection);
772 if (key.iDuration > 0)
774 if (m_currentButton.iButton == key.iButton && m_currentButton.iDuration == 0)
776 // update the duration
778 m_currentButton.iDuration = key.iDuration;
779 // ignore this one, since it's already been handled by xbmc
782 // if we received a keypress with a duration set, try to find the same one without a duration set, and replace it
783 for (vector<CecButtonPress>::reverse_iterator it = m_buttonQueue.rbegin(); it != m_buttonQueue.rend(); it++)
785 if ((*it).iButton == key.iButton)
787 if ((*it).iDuration == 0)
789 // replace this entry
790 (*it).iDuration = key.iDuration;
799 m_buttonQueue.push_back(key);
802 void CPeripheralCecAdapter::PushCecKeypress(const cec_keypress &key)
804 CecButtonPress xbmcKey;
805 xbmcKey.iDuration = key.duration;
809 case CEC_USER_CONTROL_CODE_SELECT:
810 xbmcKey.iButton = XINPUT_IR_REMOTE_SELECT;
811 PushCecKeypress(xbmcKey);
813 case CEC_USER_CONTROL_CODE_UP:
814 xbmcKey.iButton = XINPUT_IR_REMOTE_UP;
815 PushCecKeypress(xbmcKey);
817 case CEC_USER_CONTROL_CODE_DOWN:
818 xbmcKey.iButton = XINPUT_IR_REMOTE_DOWN;
819 PushCecKeypress(xbmcKey);
821 case CEC_USER_CONTROL_CODE_LEFT:
822 xbmcKey.iButton = XINPUT_IR_REMOTE_LEFT;
823 PushCecKeypress(xbmcKey);
825 case CEC_USER_CONTROL_CODE_LEFT_UP:
826 xbmcKey.iButton = XINPUT_IR_REMOTE_LEFT;
827 PushCecKeypress(xbmcKey);
828 xbmcKey.iButton = XINPUT_IR_REMOTE_UP;
829 PushCecKeypress(xbmcKey);
831 case CEC_USER_CONTROL_CODE_LEFT_DOWN:
832 xbmcKey.iButton = XINPUT_IR_REMOTE_LEFT;
833 PushCecKeypress(xbmcKey);
834 xbmcKey.iButton = XINPUT_IR_REMOTE_DOWN;
835 PushCecKeypress(xbmcKey);
837 case CEC_USER_CONTROL_CODE_RIGHT:
838 xbmcKey.iButton = XINPUT_IR_REMOTE_RIGHT;
839 PushCecKeypress(xbmcKey);
841 case CEC_USER_CONTROL_CODE_RIGHT_UP:
842 xbmcKey.iButton = XINPUT_IR_REMOTE_RIGHT;
843 PushCecKeypress(xbmcKey);
844 xbmcKey.iButton = XINPUT_IR_REMOTE_UP;
845 PushCecKeypress(xbmcKey);
847 case CEC_USER_CONTROL_CODE_RIGHT_DOWN:
848 xbmcKey.iButton = XINPUT_IR_REMOTE_RIGHT;
849 PushCecKeypress(xbmcKey);
850 xbmcKey.iButton = XINPUT_IR_REMOTE_DOWN;
851 PushCecKeypress(xbmcKey);
853 case CEC_USER_CONTROL_CODE_SETUP_MENU:
854 xbmcKey.iButton = XINPUT_IR_REMOTE_TITLE;
855 PushCecKeypress(xbmcKey);
857 case CEC_USER_CONTROL_CODE_CONTENTS_MENU:
858 case CEC_USER_CONTROL_CODE_FAVORITE_MENU:
859 case CEC_USER_CONTROL_CODE_ROOT_MENU:
860 xbmcKey.iButton = XINPUT_IR_REMOTE_MENU;
861 PushCecKeypress(xbmcKey);
863 case CEC_USER_CONTROL_CODE_EXIT:
864 xbmcKey.iButton = XINPUT_IR_REMOTE_BACK;
865 PushCecKeypress(xbmcKey);
867 case CEC_USER_CONTROL_CODE_ENTER:
868 xbmcKey.iButton = XINPUT_IR_REMOTE_ENTER;
869 PushCecKeypress(xbmcKey);
871 case CEC_USER_CONTROL_CODE_CHANNEL_DOWN:
872 xbmcKey.iButton = XINPUT_IR_REMOTE_CHANNEL_MINUS;
873 PushCecKeypress(xbmcKey);
875 case CEC_USER_CONTROL_CODE_CHANNEL_UP:
876 xbmcKey.iButton = XINPUT_IR_REMOTE_CHANNEL_PLUS;
877 PushCecKeypress(xbmcKey);
879 case CEC_USER_CONTROL_CODE_PREVIOUS_CHANNEL:
880 xbmcKey.iButton = XINPUT_IR_REMOTE_TELETEXT;
881 PushCecKeypress(xbmcKey);
883 case CEC_USER_CONTROL_CODE_SOUND_SELECT:
884 xbmcKey.iButton = XINPUT_IR_REMOTE_LANGUAGE;
885 PushCecKeypress(xbmcKey);
887 case CEC_USER_CONTROL_CODE_POWER:
888 case CEC_USER_CONTROL_CODE_POWER_TOGGLE_FUNCTION:
889 case CEC_USER_CONTROL_CODE_POWER_OFF_FUNCTION:
890 xbmcKey.iButton = XINPUT_IR_REMOTE_POWER;
891 PushCecKeypress(xbmcKey);
893 case CEC_USER_CONTROL_CODE_VOLUME_UP:
894 xbmcKey.iButton = XINPUT_IR_REMOTE_VOLUME_PLUS;
895 PushCecKeypress(xbmcKey);
897 case CEC_USER_CONTROL_CODE_VOLUME_DOWN:
898 xbmcKey.iButton = XINPUT_IR_REMOTE_VOLUME_MINUS;
899 PushCecKeypress(xbmcKey);
901 case CEC_USER_CONTROL_CODE_MUTE:
902 case CEC_USER_CONTROL_CODE_MUTE_FUNCTION:
903 case CEC_USER_CONTROL_CODE_RESTORE_VOLUME_FUNCTION:
904 xbmcKey.iButton = XINPUT_IR_REMOTE_MUTE;
905 PushCecKeypress(xbmcKey);
907 case CEC_USER_CONTROL_CODE_PLAY:
908 xbmcKey.iButton = XINPUT_IR_REMOTE_PLAY;
909 PushCecKeypress(xbmcKey);
911 case CEC_USER_CONTROL_CODE_STOP:
912 xbmcKey.iButton = XINPUT_IR_REMOTE_STOP;
913 PushCecKeypress(xbmcKey);
915 case CEC_USER_CONTROL_CODE_PAUSE:
916 xbmcKey.iButton = XINPUT_IR_REMOTE_PAUSE;
917 PushCecKeypress(xbmcKey);
919 case CEC_USER_CONTROL_CODE_REWIND:
920 xbmcKey.iButton = XINPUT_IR_REMOTE_REVERSE;
921 PushCecKeypress(xbmcKey);
923 case CEC_USER_CONTROL_CODE_FAST_FORWARD:
924 xbmcKey.iButton = XINPUT_IR_REMOTE_FORWARD;
925 PushCecKeypress(xbmcKey);
927 case CEC_USER_CONTROL_CODE_NUMBER0:
928 xbmcKey.iButton = XINPUT_IR_REMOTE_0;
929 PushCecKeypress(xbmcKey);
931 case CEC_USER_CONTROL_CODE_NUMBER1:
932 xbmcKey.iButton = XINPUT_IR_REMOTE_1;
933 PushCecKeypress(xbmcKey);
935 case CEC_USER_CONTROL_CODE_NUMBER2:
936 xbmcKey.iButton = XINPUT_IR_REMOTE_2;
937 PushCecKeypress(xbmcKey);
939 case CEC_USER_CONTROL_CODE_NUMBER3:
940 xbmcKey.iButton = XINPUT_IR_REMOTE_3;
941 PushCecKeypress(xbmcKey);
943 case CEC_USER_CONTROL_CODE_NUMBER4:
944 xbmcKey.iButton = XINPUT_IR_REMOTE_4;
945 PushCecKeypress(xbmcKey);
947 case CEC_USER_CONTROL_CODE_NUMBER5:
948 xbmcKey.iButton = XINPUT_IR_REMOTE_5;
949 PushCecKeypress(xbmcKey);
951 case CEC_USER_CONTROL_CODE_NUMBER6:
952 xbmcKey.iButton = XINPUT_IR_REMOTE_6;
953 PushCecKeypress(xbmcKey);
955 case CEC_USER_CONTROL_CODE_NUMBER7:
956 xbmcKey.iButton = XINPUT_IR_REMOTE_7;
957 PushCecKeypress(xbmcKey);
959 case CEC_USER_CONTROL_CODE_NUMBER8:
960 xbmcKey.iButton = XINPUT_IR_REMOTE_8;
961 PushCecKeypress(xbmcKey);
963 case CEC_USER_CONTROL_CODE_NUMBER9:
964 xbmcKey.iButton = XINPUT_IR_REMOTE_9;
965 PushCecKeypress(xbmcKey);
967 case CEC_USER_CONTROL_CODE_RECORD:
968 xbmcKey.iButton = XINPUT_IR_REMOTE_RECORD;
969 PushCecKeypress(xbmcKey);
971 case CEC_USER_CONTROL_CODE_CLEAR:
972 xbmcKey.iButton = XINPUT_IR_REMOTE_CLEAR;
973 PushCecKeypress(xbmcKey);
975 case CEC_USER_CONTROL_CODE_DISPLAY_INFORMATION:
976 xbmcKey.iButton = XINPUT_IR_REMOTE_INFO;
977 PushCecKeypress(xbmcKey);
979 case CEC_USER_CONTROL_CODE_PAGE_UP:
980 xbmcKey.iButton = XINPUT_IR_REMOTE_CHANNEL_PLUS;
981 PushCecKeypress(xbmcKey);
983 case CEC_USER_CONTROL_CODE_PAGE_DOWN:
984 xbmcKey.iButton = XINPUT_IR_REMOTE_CHANNEL_MINUS;
985 PushCecKeypress(xbmcKey);
987 case CEC_USER_CONTROL_CODE_FORWARD:
988 xbmcKey.iButton = XINPUT_IR_REMOTE_SKIP_PLUS;
989 PushCecKeypress(xbmcKey);
991 case CEC_USER_CONTROL_CODE_BACKWARD:
992 xbmcKey.iButton = XINPUT_IR_REMOTE_SKIP_MINUS;
993 PushCecKeypress(xbmcKey);
995 case CEC_USER_CONTROL_CODE_F1_BLUE:
996 xbmcKey.iButton = XINPUT_IR_REMOTE_BLUE;
997 PushCecKeypress(xbmcKey);
999 case CEC_USER_CONTROL_CODE_F2_RED:
1000 xbmcKey.iButton = XINPUT_IR_REMOTE_RED;
1001 PushCecKeypress(xbmcKey);
1003 case CEC_USER_CONTROL_CODE_F3_GREEN:
1004 xbmcKey.iButton = XINPUT_IR_REMOTE_GREEN;
1005 PushCecKeypress(xbmcKey);
1007 case CEC_USER_CONTROL_CODE_F4_YELLOW:
1008 xbmcKey.iButton = XINPUT_IR_REMOTE_YELLOW;
1009 PushCecKeypress(xbmcKey);
1011 case CEC_USER_CONTROL_CODE_ELECTRONIC_PROGRAM_GUIDE:
1012 xbmcKey.iButton = XINPUT_IR_REMOTE_GUIDE;
1013 PushCecKeypress(xbmcKey);
1015 case CEC_USER_CONTROL_CODE_AN_CHANNELS_LIST:
1016 xbmcKey.iButton = XINPUT_IR_REMOTE_LIVE_TV;
1017 PushCecKeypress(xbmcKey);
1019 case CEC_USER_CONTROL_CODE_NEXT_FAVORITE:
1020 case CEC_USER_CONTROL_CODE_DOT:
1021 case CEC_USER_CONTROL_CODE_AN_RETURN:
1022 xbmcKey.iButton = XINPUT_IR_REMOTE_TITLE; // context menu
1023 PushCecKeypress(xbmcKey);
1025 case CEC_USER_CONTROL_CODE_DATA:
1026 xbmcKey.iButton = XINPUT_IR_REMOTE_TELETEXT;
1027 PushCecKeypress(xbmcKey);
1029 case CEC_USER_CONTROL_CODE_SUB_PICTURE:
1030 xbmcKey.iButton = XINPUT_IR_REMOTE_SUBTITLE;
1031 PushCecKeypress(xbmcKey);
1033 case CEC_USER_CONTROL_CODE_POWER_ON_FUNCTION:
1034 case CEC_USER_CONTROL_CODE_EJECT:
1035 case CEC_USER_CONTROL_CODE_INPUT_SELECT:
1036 case CEC_USER_CONTROL_CODE_INITIAL_CONFIGURATION:
1037 case CEC_USER_CONTROL_CODE_HELP:
1038 case CEC_USER_CONTROL_CODE_STOP_RECORD:
1039 case CEC_USER_CONTROL_CODE_PAUSE_RECORD:
1040 case CEC_USER_CONTROL_CODE_ANGLE:
1041 case CEC_USER_CONTROL_CODE_VIDEO_ON_DEMAND:
1042 case CEC_USER_CONTROL_CODE_TIMER_PROGRAMMING:
1043 case CEC_USER_CONTROL_CODE_PLAY_FUNCTION:
1044 case CEC_USER_CONTROL_CODE_PAUSE_PLAY_FUNCTION:
1045 case CEC_USER_CONTROL_CODE_RECORD_FUNCTION:
1046 case CEC_USER_CONTROL_CODE_PAUSE_RECORD_FUNCTION:
1047 case CEC_USER_CONTROL_CODE_STOP_FUNCTION:
1048 case CEC_USER_CONTROL_CODE_TUNE_FUNCTION:
1049 case CEC_USER_CONTROL_CODE_SELECT_MEDIA_FUNCTION:
1050 case CEC_USER_CONTROL_CODE_SELECT_AV_INPUT_FUNCTION:
1051 case CEC_USER_CONTROL_CODE_SELECT_AUDIO_INPUT_FUNCTION:
1052 case CEC_USER_CONTROL_CODE_F5:
1053 case CEC_USER_CONTROL_CODE_UNKNOWN:
1059 int CPeripheralCecAdapter::GetButton(void)
1061 CSingleLock lock(m_critSection);
1065 return m_bHasButton ? m_currentButton.iButton : 0;
1068 unsigned int CPeripheralCecAdapter::GetHoldTime(void)
1070 CSingleLock lock(m_critSection);
1074 return m_bHasButton ? m_currentButton.iDuration : 0;
1077 void CPeripheralCecAdapter::ResetButton(void)
1079 CSingleLock lock(m_critSection);
1080 m_bHasButton = false;
1082 // wait for the key release if the duration isn't 0
1083 if (m_currentButton.iDuration > 0)
1085 m_currentButton.iButton = 0;
1086 m_currentButton.iDuration = 0;
1090 void CPeripheralCecAdapter::OnSettingChanged(const CStdString &strChangedSetting)
1092 if (strChangedSetting.Equals("enabled"))
1094 bool bEnabled(GetSettingBool("enabled"));
1095 if (!bEnabled && IsRunning())
1097 CLog::Log(LOGDEBUG, "%s - closing the CEC connection", __FUNCTION__);
1100 else if (bEnabled && !IsRunning())
1102 CLog::Log(LOGDEBUG, "%s - starting the CEC connection", __FUNCTION__);
1103 SetConfigurationFromSettings();
1104 InitialiseFeature(FEATURE_CEC);
1107 else if (IsRunning())
1109 if (m_queryThread->IsRunning())
1111 CLog::Log(LOGDEBUG, "%s - sending the updated configuration to libCEC", __FUNCTION__);
1112 SetConfigurationFromSettings();
1113 m_queryThread->UpdateConfiguration(&m_configuration);
1118 CLog::Log(LOGDEBUG, "%s - restarting the CEC connection", __FUNCTION__);
1119 SetConfigurationFromSettings();
1120 InitialiseFeature(FEATURE_CEC);
1124 void CPeripheralCecAdapter::CecSourceActivated(void *cbParam, const CEC::cec_logical_address address, const uint8_t activated)
1126 CPeripheralCecAdapter *adapter = (CPeripheralCecAdapter *)cbParam;
1130 // wake up the screensaver, so the user doesn't switch to a black screen
1132 g_application.WakeUpScreenSaverAndDPMS();
1134 if (adapter->GetSettingBool("pause_playback_on_deactivate"))
1136 bool bShowingSlideshow = (g_windowManager.GetActiveWindow() == WINDOW_SLIDESHOW);
1137 CGUIWindowSlideShow *pSlideShow = bShowingSlideshow ? (CGUIWindowSlideShow *)g_windowManager.GetWindow(WINDOW_SLIDESHOW) : NULL;
1138 bool bPlayingAndDeactivated = activated == 0 && (
1139 (pSlideShow && pSlideShow->IsPlaying()) || g_application.IsPlaying());
1140 bool bPausedAndActivated = activated == 1 && adapter->m_bPlaybackPaused && (
1141 (pSlideShow && pSlideShow->IsPaused()) || g_application.IsPaused());
1142 if (bPlayingAndDeactivated)
1143 adapter->m_bPlaybackPaused = true;
1144 else if (bPausedAndActivated)
1145 adapter->m_bPlaybackPaused = false;
1147 if (bPlayingAndDeactivated || bPausedAndActivated)
1150 // pause/resume slideshow
1151 pSlideShow->OnAction(CAction(ACTION_PAUSE));
1153 // pause/resume player
1154 CApplicationMessenger::Get().MediaPause();
1159 int CPeripheralCecAdapter::CecLogMessage(void *cbParam, const cec_log_message message)
1161 CPeripheralCecAdapter *adapter = (CPeripheralCecAdapter *)cbParam;
1166 switch (message.level)
1171 case CEC_LOG_WARNING:
1172 iLevel = LOGWARNING;
1174 case CEC_LOG_NOTICE:
1177 case CEC_LOG_TRAFFIC:
1186 CLog::Log(iLevel, "%s - %s", __FUNCTION__, message.message);
1191 void CPeripheralCecAdapter::SetConfigurationFromLibCEC(const CEC::libcec_configuration &config)
1193 bool bChanged(false);
1195 // set the primary device type
1196 m_configuration.deviceTypes.Clear();
1197 m_configuration.deviceTypes.Add(config.deviceTypes[0]);
1199 // hide the "connected device" and "hdmi port number" settings when the PA was autodetected
1200 bool bPAAutoDetected(config.bAutodetectAddress == 1);
1202 SetSettingVisible("connected_device", !bPAAutoDetected);
1203 SetSettingVisible("cec_hdmi_port", !bPAAutoDetected);
1205 // set the connected device
1206 m_configuration.baseDevice = config.baseDevice;
1207 bChanged |= SetSetting("connected_device", config.baseDevice == CECDEVICE_AUDIOSYSTEM ? LOCALISED_ID_AVR : LOCALISED_ID_TV);
1209 // set the HDMI port number
1210 m_configuration.iHDMIPort = config.iHDMIPort;
1211 bChanged |= SetSetting("cec_hdmi_port", config.iHDMIPort);
1213 // set the physical address, when baseDevice or iHDMIPort are not set
1214 CStdString strPhysicalAddress("0");
1215 if (!bPAAutoDetected && (m_configuration.baseDevice == CECDEVICE_UNKNOWN ||
1216 m_configuration.iHDMIPort < CEC_MIN_HDMI_PORTNUMBER ||
1217 m_configuration.iHDMIPort > CEC_MAX_HDMI_PORTNUMBER))
1219 m_configuration.iPhysicalAddress = config.iPhysicalAddress;
1220 strPhysicalAddress.Format("%x", config.iPhysicalAddress);
1222 bChanged |= SetSetting("physical_address", strPhysicalAddress);
1224 // set the devices to wake when starting
1225 m_configuration.wakeDevices = config.wakeDevices;
1226 bChanged |= WriteLogicalAddresses(config.wakeDevices, "wake_devices", "wake_devices_advanced");
1228 // set the devices to power off when stopping
1229 m_configuration.powerOffDevices = config.powerOffDevices;
1230 bChanged |= WriteLogicalAddresses(config.powerOffDevices, "standby_devices", "standby_devices_advanced");
1232 // set the boolean settings
1233 m_configuration.bUseTVMenuLanguage = config.bUseTVMenuLanguage;
1234 bChanged |= SetSetting("use_tv_menu_language", m_configuration.bUseTVMenuLanguage == 1);
1236 m_configuration.bActivateSource = config.bActivateSource;
1237 bChanged |= SetSetting("activate_source", m_configuration.bActivateSource == 1);
1239 m_configuration.bPowerOffScreensaver = config.bPowerOffScreensaver;
1240 bChanged |= SetSetting("cec_standby_screensaver", m_configuration.bPowerOffScreensaver == 1);
1242 m_configuration.bPowerOnScreensaver = config.bPowerOnScreensaver;
1243 bChanged |= SetSetting("cec_wake_screensaver", m_configuration.bPowerOnScreensaver == 1);
1245 m_configuration.bPowerOffOnStandby = config.bPowerOffOnStandby;
1247 m_configuration.bSendInactiveSource = config.bSendInactiveSource;
1248 bChanged |= SetSetting("send_inactive_source", m_configuration.bSendInactiveSource == 1);
1250 m_configuration.iFirmwareVersion = config.iFirmwareVersion;
1251 m_configuration.bShutdownOnStandby = config.bShutdownOnStandby;
1253 memcpy(m_configuration.strDeviceLanguage, config.strDeviceLanguage, 3);
1254 m_configuration.iFirmwareBuildDate = config.iFirmwareBuildDate;
1256 SetVersionInfo(m_configuration);
1258 bChanged |= SetSetting("standby_pc_on_tv_standby",
1259 m_configuration.bPowerOffOnStandby == 1 ? 13011 :
1260 m_configuration.bShutdownOnStandby == 1 ? 13005 : 36028);
1263 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000), g_localizeStrings.Get(36023));
1266 void CPeripheralCecAdapter::SetConfigurationFromSettings(void)
1268 // use the same client version as libCEC version
1269 m_configuration.clientVersion = CEC_CLIENT_VERSION_CURRENT;
1271 // device name 'XBMC'
1272 snprintf(m_configuration.strDeviceName, 13, "%s", GetSettingString("device_name").c_str());
1274 // set the primary device type
1275 m_configuration.deviceTypes.Clear();
1276 int iDeviceType = GetSettingInt("device_type");
1277 if (iDeviceType != (int)CEC_DEVICE_TYPE_RECORDING_DEVICE &&
1278 iDeviceType != (int)CEC_DEVICE_TYPE_PLAYBACK_DEVICE &&
1279 iDeviceType != (int)CEC_DEVICE_TYPE_TUNER)
1280 iDeviceType = (int)CEC_DEVICE_TYPE_RECORDING_DEVICE;
1281 m_configuration.deviceTypes.Add((cec_device_type)iDeviceType);
1283 // always try to autodetect the address.
1284 // when the firmware supports this, it will override the physical address, connected device and hdmi port settings
1285 m_configuration.bAutodetectAddress = CEC_DEFAULT_SETTING_AUTODETECT_ADDRESS;
1287 // set the physical address
1288 // when set, it will override the connected device and hdmi port settings
1289 CStdString strPhysicalAddress = GetSettingString("physical_address");
1290 int iPhysicalAddress;
1291 if (sscanf(strPhysicalAddress.c_str(), "%x", &iPhysicalAddress) &&
1292 iPhysicalAddress >= CEC_PHYSICAL_ADDRESS_TV &&
1293 iPhysicalAddress <= CEC_MAX_PHYSICAL_ADDRESS)
1294 m_configuration.iPhysicalAddress = iPhysicalAddress;
1296 m_configuration.iPhysicalAddress = CEC_PHYSICAL_ADDRESS_TV;
1298 // set the connected device
1299 int iConnectedDevice = GetSettingInt("connected_device");
1300 if (iConnectedDevice == LOCALISED_ID_AVR)
1301 m_configuration.baseDevice = CECDEVICE_AUDIOSYSTEM;
1302 else if (iConnectedDevice == LOCALISED_ID_TV)
1303 m_configuration.baseDevice = CECDEVICE_TV;
1305 // set the HDMI port number
1306 int iHDMIPort = GetSettingInt("cec_hdmi_port");
1307 if (iHDMIPort >= CEC_MIN_HDMI_PORTNUMBER &&
1308 iHDMIPort <= CEC_MAX_HDMI_PORTNUMBER)
1309 m_configuration.iHDMIPort = iHDMIPort;
1311 // set the tv vendor override
1312 int iVendor = GetSettingInt("tv_vendor");
1313 if (iVendor >= CEC_MAX_VENDORID &&
1314 iVendor <= CEC_MAX_VENDORID)
1315 m_configuration.tvVendor = iVendor;
1317 // read the devices to wake when starting
1318 CStdString strWakeDevices = CStdString(GetSettingString("wake_devices_advanced")).Trim();
1319 m_configuration.wakeDevices.Clear();
1320 if (!strWakeDevices.IsEmpty())
1321 ReadLogicalAddresses(strWakeDevices, m_configuration.wakeDevices);
1323 ReadLogicalAddresses(GetSettingInt("wake_devices"), m_configuration.wakeDevices);
1325 // read the devices to power off when stopping
1326 CStdString strStandbyDevices = CStdString(GetSettingString("standby_devices_advanced")).Trim();
1327 m_configuration.powerOffDevices.Clear();
1328 if (!strStandbyDevices.IsEmpty())
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 = CStdString(strString.substr(iPtr, 1)).Trim();
1354 if (!strDevice.IsEmpty())
1357 if (sscanf(strDevice.c_str(), "%x", &iDevice) == 1 && iDevice >= 0 && iDevice <= 0xF)
1358 addresses.Set((cec_logical_address)iDevice);
1363 void CPeripheralCecAdapter::ReadLogicalAddresses(int iLocalisedId, cec_logical_addresses &addresses)
1366 switch (iLocalisedId)
1368 case LOCALISED_ID_TV:
1369 addresses.Set(CECDEVICE_TV);
1371 case LOCALISED_ID_AVR:
1372 addresses.Set(CECDEVICE_AUDIOSYSTEM);
1374 case LOCALISED_ID_TV_AVR:
1375 addresses.Set(CECDEVICE_TV);
1376 addresses.Set(CECDEVICE_AUDIOSYSTEM);
1378 case LOCALISED_ID_NONE:
1384 bool CPeripheralCecAdapter::WriteLogicalAddresses(const cec_logical_addresses& addresses, const string& strSettingName, const string& strAdvancedSettingName)
1386 bool bChanged(false);
1388 // only update the advanced setting if it was set by the user
1389 if (!GetSettingString(strAdvancedSettingName).IsEmpty())
1391 CStdString strPowerOffDevices;
1392 for (unsigned int iPtr = CECDEVICE_TV; iPtr <= CECDEVICE_BROADCAST; iPtr++)
1393 if (addresses[iPtr])
1394 strPowerOffDevices.AppendFormat(" %X", iPtr);
1395 bChanged = SetSetting(strAdvancedSettingName, strPowerOffDevices.Trim());
1398 int iSettingPowerOffDevices = LOCALISED_ID_NONE;
1399 if (addresses[CECDEVICE_TV] && addresses[CECDEVICE_AUDIOSYSTEM])
1400 iSettingPowerOffDevices = LOCALISED_ID_TV_AVR;
1401 else if (addresses[CECDEVICE_TV])
1402 iSettingPowerOffDevices = LOCALISED_ID_TV;
1403 else if (addresses[CECDEVICE_AUDIOSYSTEM])
1404 iSettingPowerOffDevices = LOCALISED_ID_AVR;
1405 return SetSetting(strSettingName, iSettingPowerOffDevices) || bChanged;
1408 CPeripheralCecAdapterUpdateThread::CPeripheralCecAdapterUpdateThread(CPeripheralCecAdapter *adapter, libcec_configuration *configuration) :
1409 CThread("CECAdapterUpdate"),
1411 m_configuration(*configuration),
1412 m_bNextConfigurationScheduled(false),
1415 m_nextConfiguration.Clear();
1419 CPeripheralCecAdapterUpdateThread::~CPeripheralCecAdapterUpdateThread(void)
1426 void CPeripheralCecAdapterUpdateThread::Signal(void)
1431 bool CPeripheralCecAdapterUpdateThread::UpdateConfiguration(libcec_configuration *configuration)
1433 CSingleLock lock(m_critSection);
1439 m_bNextConfigurationScheduled = true;
1440 m_nextConfiguration = *configuration;
1444 m_configuration = *configuration;
1450 bool CPeripheralCecAdapterUpdateThread::WaitReady(void)
1452 // don't wait if we're not powering up anything
1453 if (m_configuration.wakeDevices.IsEmpty() && m_configuration.bActivateSource == 0)
1456 // wait for the TV if we're configured to become the active source.
1457 // wait for the first device in the wake list otherwise.
1458 cec_logical_address waitFor = (m_configuration.bActivateSource == 1) ?
1460 m_configuration.wakeDevices.primary;
1462 cec_power_status powerStatus(CEC_POWER_STATUS_UNKNOWN);
1463 bool bContinue(true);
1464 while (bContinue && !m_adapter->m_bStop && !m_bStop && powerStatus != CEC_POWER_STATUS_ON)
1466 powerStatus = m_adapter->m_cecAdapter->GetDevicePowerStatus(waitFor);
1467 if (powerStatus != CEC_POWER_STATUS_ON)
1468 bContinue = !m_event.WaitMSec(1000);
1471 return powerStatus == CEC_POWER_STATUS_ON;
1474 void CPeripheralCecAdapterUpdateThread::UpdateMenuLanguage(void)
1476 // request the menu language of the TV
1477 if (m_configuration.bUseTVMenuLanguage == 1)
1479 CLog::Log(LOGDEBUG, "%s - requesting the menu language of the TV", __FUNCTION__);
1480 cec_menu_language language;
1481 if (m_adapter->m_cecAdapter->GetDeviceMenuLanguage(CECDEVICE_TV, &language))
1482 m_adapter->SetMenuLanguage(language.language);
1484 CLog::Log(LOGDEBUG, "%s - unknown menu language", __FUNCTION__);
1488 CLog::Log(LOGDEBUG, "%s - using TV menu language is disabled", __FUNCTION__);
1492 CStdString CPeripheralCecAdapterUpdateThread::UpdateAudioSystemStatus(void)
1494 CStdString strAmpName;
1496 /* disable the mute setting when an amp is found, because the amp handles the mute setting and
1497 set PCM output to 100% */
1498 if (m_adapter->m_cecAdapter->IsActiveDeviceType(CEC_DEVICE_TYPE_AUDIO_SYSTEM))
1500 // request the OSD name of the amp
1501 cec_osd_name ampName = m_adapter->m_cecAdapter->GetDeviceOSDName(CECDEVICE_AUDIOSYSTEM);
1502 CLog::Log(LOGDEBUG, "%s - CEC capable amplifier found (%s). volume will be controlled on the amp", __FUNCTION__, ampName.name);
1503 strAmpName.AppendFormat("%s", ampName.name);
1506 m_adapter->SetAudioSystemConnected(true);
1507 g_application.SetMute(false);
1508 g_application.SetVolume(VOLUME_MAXIMUM, false);
1513 CLog::Log(LOGDEBUG, "%s - no CEC capable amplifier found", __FUNCTION__);
1514 m_adapter->SetAudioSystemConnected(false);
1520 bool CPeripheralCecAdapterUpdateThread::SetInitialConfiguration(void)
1522 // the option to make XBMC the active source is set
1523 if (m_configuration.bActivateSource == 1)
1524 m_adapter->m_cecAdapter->SetActiveSource();
1526 // devices to wake are set
1527 cec_logical_addresses tvOnly;
1528 tvOnly.Clear(); tvOnly.Set(CECDEVICE_TV);
1529 if (!m_configuration.wakeDevices.IsEmpty() && (m_configuration.wakeDevices != tvOnly || m_configuration.bActivateSource == 0))
1530 m_adapter->m_cecAdapter->PowerOnDevices(CECDEVICE_BROADCAST);
1532 // wait until devices are powered up
1536 UpdateMenuLanguage();
1538 // request the OSD name of the TV
1539 CStdString strNotification;
1540 cec_osd_name tvName = m_adapter->m_cecAdapter->GetDeviceOSDName(CECDEVICE_TV);
1541 strNotification.Format("%s: %s", g_localizeStrings.Get(36016), tvName.name);
1543 CStdString strAmpName = UpdateAudioSystemStatus();
1544 if (!strAmpName.empty())
1545 strNotification.AppendFormat("- %s", strAmpName.c_str());
1547 m_adapter->m_bIsReady = true;
1549 // and let the gui know that we're done
1550 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000), strNotification);
1552 CSingleLock lock(m_critSection);
1553 m_bIsUpdating = false;
1557 bool CPeripheralCecAdapter::IsRunning(void) const
1559 CSingleLock lock(m_critSection);
1560 return m_bIsRunning;
1563 void CPeripheralCecAdapterUpdateThread::Process(void)
1565 // set the initial configuration
1566 if (!SetInitialConfiguration())
1569 // and wait for updates
1570 bool bUpdate(false);
1574 if (bUpdate || m_event.WaitMSec(500))
1578 // set the new configuration
1579 libcec_configuration configuration;
1581 CSingleLock lock(m_critSection);
1582 configuration = m_configuration;
1583 m_bIsUpdating = false;
1586 CLog::Log(LOGDEBUG, "%s - updating the configuration", __FUNCTION__);
1587 bool bConfigSet(m_adapter->m_cecAdapter->SetConfiguration(&configuration));
1588 // display message: config updated / failed to update
1590 CLog::Log(LOGERROR, "%s - libCEC couldn't set the new configuration", __FUNCTION__);
1593 UpdateMenuLanguage();
1594 UpdateAudioSystemStatus();
1597 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36000), g_localizeStrings.Get(bConfigSet ? 36023 : 36024));
1600 CSingleLock lock(m_critSection);
1601 if ((bUpdate = m_bNextConfigurationScheduled) == true)
1603 // another update is scheduled
1604 m_bNextConfigurationScheduled = false;
1605 m_configuration = m_nextConfiguration;
1609 // nothing left to do, wait for updates
1610 m_bIsUpdating = false;
1618 void CPeripheralCecAdapter::OnDeviceRemoved(void)
1620 CSingleLock lock(m_critSection);
1621 m_bDeviceRemoved = true;
1624 bool CPeripheralCecAdapter::ReopenConnection(void)
1626 // stop running thread
1628 CSingleLock lock(m_critSection);
1629 m_iExitCode = EXITCODE_RESTARTAPP;
1630 CAnnouncementManager::RemoveAnnouncer(this);
1635 // reset all members to their defaults
1638 // reopen the connection
1639 return InitialiseFeature(FEATURE_CEC);
1642 void CPeripheralCecAdapter::ActivateSource(void)
1644 CSingleLock lock(m_critSection);
1645 m_bActiveSourcePending = true;
1648 void CPeripheralCecAdapter::ProcessActivateSource(void)
1650 bool bActivate(false);
1653 CSingleLock lock(m_critSection);
1654 bActivate = m_bActiveSourcePending;
1655 m_bActiveSourcePending = false;
1659 m_cecAdapter->SetActiveSource();
1662 void CPeripheralCecAdapter::StandbyDevices(void)
1664 CSingleLock lock(m_critSection);
1665 m_bStandbyPending = true;
1668 void CPeripheralCecAdapter::ProcessStandbyDevices(void)
1670 bool bStandby(false);
1673 CSingleLock lock(m_critSection);
1674 bStandby = m_bStandbyPending;
1675 m_bStandbyPending = false;
1679 m_cecAdapter->StandbyDevices(CECDEVICE_BROADCAST);