InputCEC.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. #include "QsLog.h"
  2. #include "InputCEC.h"
  3. #include "settings/SettingsComponent.h"
  4. #include "power/PowerComponent.h"
  5. class KeyAction
  6. {
  7. public:
  8. QString m_action;
  9. bool m_hasLongPress;
  10. };
  11. static QMap<int, KeyAction> g_cecKeyMap { \
  12. { CEC_USER_CONTROL_CODE_SELECT , { INPUT_KEY_SELECT , false } } , \
  13. { CEC_USER_CONTROL_CODE_UP , { INPUT_KEY_UP , false } } , \
  14. { CEC_USER_CONTROL_CODE_DOWN , { INPUT_KEY_DOWN , false } } , \
  15. { CEC_USER_CONTROL_CODE_LEFT , { INPUT_KEY_LEFT , false } } , \
  16. { CEC_USER_CONTROL_CODE_RIGHT , { INPUT_KEY_RIGHT , false } } , \
  17. { CEC_USER_CONTROL_CODE_SETUP_MENU , { INPUT_KEY_MENU , false } } , \
  18. { CEC_USER_CONTROL_CODE_PLAY , { INPUT_KEY_PLAY , false } } , \
  19. { CEC_USER_CONTROL_CODE_PAUSE , { INPUT_KEY_PAUSE , false } } , \
  20. { CEC_USER_CONTROL_CODE_STOP , { INPUT_KEY_STOP , false } } , \
  21. { CEC_USER_CONTROL_CODE_EXIT , { INPUT_KEY_BACK , false } } , \
  22. { CEC_USER_CONTROL_CODE_FAST_FORWARD , { INPUT_KEY_SEEKFWD , false } } , \
  23. { CEC_USER_CONTROL_CODE_REWIND , { INPUT_KEY_SEEKBCK , false } } , \
  24. { CEC_USER_CONTROL_CODE_DISPLAY_INFORMATION , { INPUT_KEY_INFO , false } } , \
  25. { CEC_USER_CONTROL_CODE_FORWARD , { INPUT_KEY_NEXT , false } } , \
  26. { CEC_USER_CONTROL_CODE_BACKWARD , { INPUT_KEY_PREV , false } } , \
  27. { CEC_USER_CONTROL_CODE_F1_BLUE , { INPUT_KEY_BLUE , false } } , \
  28. { CEC_USER_CONTROL_CODE_F2_RED , { INPUT_KEY_RED , false } } , \
  29. { CEC_USER_CONTROL_CODE_F3_GREEN , { INPUT_KEY_GREEN , false } } , \
  30. { CEC_USER_CONTROL_CODE_F4_YELLOW , { INPUT_KEY_YELLOW , false } } , \
  31. { CEC_USER_CONTROL_CODE_SUB_PICTURE, { INPUT_KEY_SUBTITLES , false } } , \
  32. { CEC_USER_CONTROL_CODE_ROOT_MENU, { INPUT_KEY_HOME , false } }, \
  33. { CEC_USER_CONTROL_CODE_NUMBER0, { INPUT_KEY_0 , false } } , \
  34. { CEC_USER_CONTROL_CODE_NUMBER1, { INPUT_KEY_1 , false } } , \
  35. { CEC_USER_CONTROL_CODE_NUMBER2, { INPUT_KEY_2 , false } } , \
  36. { CEC_USER_CONTROL_CODE_NUMBER3, { INPUT_KEY_3 , false } } , \
  37. { CEC_USER_CONTROL_CODE_NUMBER4, { INPUT_KEY_4 , false } } , \
  38. { CEC_USER_CONTROL_CODE_NUMBER5, { INPUT_KEY_5 , false } } , \
  39. { CEC_USER_CONTROL_CODE_NUMBER6, { INPUT_KEY_6 , false } } , \
  40. { CEC_USER_CONTROL_CODE_NUMBER7, { INPUT_KEY_7 , false } } , \
  41. { CEC_USER_CONTROL_CODE_NUMBER8, { INPUT_KEY_8 , false } } , \
  42. { CEC_USER_CONTROL_CODE_NUMBER9, { INPUT_KEY_9 , false } } , \
  43. { CEC_USER_CONTROL_CODE_ELECTRONIC_PROGRAM_GUIDE, { INPUT_KEY_GUIDE , false } } \
  44. };
  45. //////////////////////////////////////////////////////////////////////////////////////////////////
  46. InputCEC::InputCEC(QObject *parent) : InputBase(parent)
  47. {
  48. m_cecThread = new QThread(this);
  49. m_cecThread->setObjectName("InputCEC");
  50. m_cecWorker = new InputCECWorker(nullptr);
  51. m_cecWorker->moveToThread(m_cecThread);
  52. m_cecThread->start(QThread::LowPriority);
  53. connect(m_cecWorker, &InputCECWorker::receivedInput, this, &InputCEC::receivedInput);
  54. }
  55. //////////////////////////////////////////////////////////////////////////////////////////////////
  56. bool InputCEC::initInput()
  57. {
  58. bool retVal;
  59. QMetaObject::invokeMethod(m_cecWorker, "init", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, retVal));
  60. return retVal;
  61. }
  62. /////////////////////////////////////////////////////////////////////////////////////////
  63. bool InputCECWorker::init()
  64. {
  65. m_configuration.Clear();
  66. m_callbacks.Clear();
  67. m_verboseLogging = SettingsComponent::Get().value(SETTINGS_SECTION_CEC, "verbose_logging").toBool();
  68. m_configuration.clientVersion = LIBCEC_VERSION_CURRENT;
  69. qstrcpy(m_configuration.strDeviceName, "Plex");
  70. m_configuration.bActivateSource = 0;
  71. m_callbacks.CBCecLogMessage = &CecLogMessage;
  72. m_callbacks.CBCecKeyPress = &CecKeyPress;
  73. m_callbacks.CBCecCommand = &CecCommand;
  74. m_callbacks.CBCecAlert = &CecAlert;
  75. m_configuration.callbackParam = this;
  76. m_configuration.callbacks = &m_callbacks;
  77. m_configuration.deviceTypes.Add(CEC_DEVICE_TYPE_RECORDING_DEVICE);
  78. m_configuration.bAutodetectAddress = CEC_DEFAULT_SETTING_AUTODETECT_ADDRESS;
  79. m_configuration.iPhysicalAddress = CEC_PHYSICAL_ADDRESS_TV;
  80. m_configuration.baseDevice = CECDEVICE_AUDIOSYSTEM;
  81. m_configuration.bActivateSource = (uint8_t)SettingsComponent::Get().value(SETTINGS_SECTION_CEC, "activatesource").toBool();
  82. m_configuration.iHDMIPort = (quint8)SettingsComponent::Get().value(SETTINGS_SECTION_CEC, "hdmiport").toInt();
  83. // open libcec
  84. m_adapter = (ICECAdapter*)CECInitialise(&m_configuration);
  85. if (!m_adapter)
  86. {
  87. QLOG_ERROR() << "Unable to initialize libCEC.";
  88. return false;
  89. }
  90. QLOG_INFO() << "libCEC was successfully initialized, found version"
  91. << m_configuration.serverVersion;
  92. // init video on targets that need this
  93. m_adapter->InitVideoStandalone();
  94. // check for attached adapters
  95. checkAdapter();
  96. // Start a timer to keep track of attached/removed adapters
  97. m_timer = new QTimer(this);
  98. m_timer->setInterval(10 * 1000);
  99. connect(m_timer, &QTimer::timeout, this, &InputCECWorker::checkAdapter);
  100. m_timer->start();
  101. return true;
  102. }
  103. ///////////////////////////////////////////////////////////////////////////////////////////////////
  104. InputCECWorker::~InputCECWorker()
  105. {
  106. m_timer->stop();
  107. closeCec();
  108. }
  109. //////////////////////////////////////////////////////////////////////////////////////////////////
  110. void InputCECWorker::closeCec()
  111. {
  112. if (m_adapter)
  113. {
  114. QLOG_DEBUG() << "Closing libCEC.";
  115. closeAdapter();
  116. CECDestroy(m_adapter);
  117. }
  118. }
  119. //////////////////////////////////////////////////////////////////////////////////////////////////
  120. bool InputCECWorker::openAdapter()
  121. {
  122. bool ret = false;
  123. // try to find devices
  124. cec_adapter_descriptor devices[10];
  125. int devicesCount = m_adapter->DetectAdapters(devices, 10, nullptr, false);
  126. if (devicesCount > 0)
  127. {
  128. // list devices
  129. QLOG_INFO() << "libCEC found" << devicesCount << "CEC adapters.";
  130. // open first adapter
  131. m_adapterPort = devices[0].strComName;
  132. if (m_adapter->Open(m_adapterPort.toStdString().c_str()))
  133. {
  134. QLOG_INFO() << "Device " << devices[0].strComName << "was successfully openned";
  135. ret = true;
  136. }
  137. else
  138. {
  139. QLOG_ERROR() << "Opening device" << devices[0].strComName << "failed";
  140. ret = false;
  141. }
  142. }
  143. return ret;
  144. }
  145. //////////////////////////////////////////////////////////////////////////////////////////////////
  146. void InputCECWorker::closeAdapter()
  147. {
  148. m_adapterPort.clear();
  149. }
  150. ///////////////////////////////////////////////////////////////////////////////////////////////////
  151. void InputCECWorker::checkAdapter()
  152. {
  153. if (m_adapterPort.isEmpty())
  154. {
  155. if (m_adapter)
  156. m_adapter->Close();
  157. openAdapter();
  158. }
  159. }
  160. ///////////////////////////////////////////////////////////////////////////////////////////////////
  161. void InputCECWorker::sendReceivedInput(const QString &source, const QString &keycode, bool pressDown)
  162. {
  163. emit receivedInput(source, keycode, true);
  164. }
  165. ///////////////////////////////////////////////////////////////////////////////////////////////////
  166. QString InputCECWorker::getCommandString(cec_user_control_code code, unsigned int duration)
  167. {
  168. QString key;
  169. if (g_cecKeyMap.contains(code))
  170. {
  171. KeyAction keyaction = g_cecKeyMap[code];
  172. key = keyaction.m_action;
  173. if ((duration > CEC_LONGPRESS_DURATION) && keyaction.m_hasLongPress)
  174. {
  175. key += "_LONG";
  176. }
  177. }
  178. return key;
  179. }
  180. ///////////////////////////////////////////////////////////////////////////////////////////////////
  181. int InputCECWorker::CecLogMessage(void* cbParam, const cec_log_message message)
  182. {
  183. auto *cec = static_cast<InputCECWorker*>(cbParam);
  184. Q_ASSERT(cec);
  185. switch (message.level)
  186. {
  187. case CEC_LOG_ERROR:
  188. QLOG_ERROR() << "libCEC ERROR:" << message.message;
  189. break;
  190. case CEC_LOG_WARNING:
  191. QLOG_WARN() << "libCEC WARNING:" << message.message;
  192. break;
  193. case CEC_LOG_NOTICE:
  194. QLOG_INFO() << "libCEC NOTICE:" << message.message;
  195. break;
  196. case CEC_LOG_DEBUG:
  197. if (cec->m_verboseLogging)
  198. {
  199. QLOG_DEBUG() << "libCEC DEBUG:" << message.message;
  200. }
  201. break;
  202. case CEC_LOG_TRAFFIC:
  203. break;
  204. default:
  205. break;
  206. }
  207. return 0;
  208. }
  209. ///////////////////////////////////////////////////////////////////////////////////////////////////
  210. int InputCECWorker::CecKeyPress(void *cbParam, const cec_keypress key)
  211. {
  212. static int lastKeyPressed = -1;
  213. auto *cec = static_cast<InputCECWorker*>(cbParam);
  214. Q_ASSERT(cec);
  215. if (cec->m_verboseLogging)
  216. {
  217. QLOG_DEBUG() << QString("CecKeyPress : Got key %1 (%2), last was %3").arg(key.keycode).arg(key.duration).arg(lastKeyPressed);
  218. }
  219. if (cec && lastKeyPressed != key.keycode)
  220. {
  221. QString cmdString = cec->getCommandString(key.keycode, key.duration);
  222. if (!cmdString.isEmpty())
  223. cec->sendReceivedInput(CEC_INPUT_NAME, cmdString);
  224. else
  225. QLOG_DEBUG() << "Unknown keycode in keypress" << key.keycode;
  226. lastKeyPressed = key.keycode;
  227. }
  228. // reset the last key on a up info (duration > 0)
  229. if (key.duration)
  230. lastKeyPressed = -1;
  231. return 0;
  232. }
  233. ///////////////////////////////////////////////////////////////////////////////////////////////////
  234. QString InputCECWorker::getCommandParamsList(cec_command command)
  235. {
  236. QString output = QString("%1 parameter(s) :").arg(command.parameters.size);
  237. if (command.parameters.size)
  238. {
  239. for (int i=0; i<command.parameters.size; i++)
  240. output += QString("[%1]=%2 ").arg(i).arg(command.parameters[i]);
  241. }
  242. return output;
  243. }
  244. ///////////////////////////////////////////////////////////////////////////////////////////////////
  245. int InputCECWorker::CecCommand(void *cbParam, const cec_command command)
  246. {
  247. auto cec = static_cast<InputCECWorker*>(cbParam);
  248. Q_ASSERT(cec);
  249. if (cec->m_verboseLogging)
  250. {
  251. QLOG_DEBUG() << "CecCommand received " << cec->getCommandParamsList(command);
  252. }
  253. switch(command.opcode)
  254. {
  255. case CEC_OPCODE_PLAY:
  256. cec->sendReceivedInput(CEC_INPUT_NAME, INPUT_KEY_PLAY);
  257. break;
  258. case CEC_OPCODE_DECK_CONTROL:
  259. if (command.parameters.size)
  260. {
  261. switch(command.parameters[0])
  262. {
  263. case CEC_DECK_CONTROL_MODE_SKIP_FORWARD_WIND:
  264. cec->sendReceivedInput(CEC_INPUT_NAME, INPUT_KEY_SEEKFWD);
  265. break;
  266. case CEC_DECK_CONTROL_MODE_SKIP_REVERSE_REWIND:
  267. cec->sendReceivedInput(CEC_INPUT_NAME, INPUT_KEY_SEEKBCK);
  268. break;
  269. case CEC_DECK_CONTROL_MODE_STOP:
  270. cec->sendReceivedInput(CEC_INPUT_NAME, INPUT_KEY_STOP);
  271. break;
  272. default:
  273. break;
  274. }
  275. }
  276. break;
  277. case CEC_OPCODE_VENDOR_REMOTE_BUTTON_DOWN:
  278. if (command.parameters.size)
  279. {
  280. switch(command.parameters[0])
  281. {
  282. // samsung Return key
  283. case CEC_USER_CONTROL_CODE_AN_RETURN:
  284. cec->sendReceivedInput(CEC_INPUT_NAME, INPUT_KEY_BACK);
  285. return 1;
  286. break;
  287. default:
  288. break;
  289. }
  290. }
  291. break;
  292. case CEC_OPCODE_VENDOR_REMOTE_BUTTON_UP: // ignore those commands as they are handled in CecKeypress
  293. case CEC_OPCODE_USER_CONTROL_PRESSED:
  294. case CEC_OPCODE_USER_CONTROL_RELEASE:
  295. case CEC_OPCODE_GIVE_OSD_NAME: // ignore those known commands (only pollng from TV)
  296. case CEC_OPCODE_GIVE_PHYSICAL_ADDRESS:
  297. break;
  298. case CEC_OPCODE_STANDBY:
  299. QLOG_DEBUG() << "CecCommand : Got a standby Request";
  300. if ((SettingsComponent::Get().value(SETTINGS_SECTION_CEC, "suspendonstandby").toBool()) && PowerComponent::Get().canSuspend())
  301. {
  302. PowerComponent::Get().Suspend();
  303. }
  304. else if ((SettingsComponent::Get().value(SETTINGS_SECTION_CEC, "poweroffonstandby").toBool()) && PowerComponent::Get().canPowerOff())
  305. {
  306. PowerComponent::Get().PowerOff();
  307. }
  308. break;
  309. default:
  310. QLOG_DEBUG() << "Unhandled CEC command " << command.opcode << ", " << cec->getCommandParamsList(command);
  311. break;
  312. }
  313. return 1;
  314. }
  315. ///////////////////////////////////////////////////////////////////////////////////////////////////
  316. int InputCECWorker::CecAlert(void *cbParam, const libcec_alert type, const libcec_parameter param)
  317. {
  318. bool reopen = false;
  319. switch (type)
  320. {
  321. case CEC_ALERT_SERVICE_DEVICE:
  322. QLOG_ERROR() << "libCEC : Alert CEC_ALERT_SERVICE_DEVICE";
  323. break;
  324. case CEC_ALERT_CONNECTION_LOST:
  325. QLOG_ERROR() << "libCEC : Alert CEC_ALERT_CONNECTION_LOST";
  326. reopen = true;
  327. break;
  328. case CEC_ALERT_PERMISSION_ERROR:
  329. QLOG_ERROR() << "libCEC : Alert CEC_ALERT_PERMISSION_ERROR";
  330. reopen = true;
  331. break;
  332. case CEC_ALERT_PORT_BUSY:
  333. QLOG_ERROR() << "libCEC : Alert CEC_ALERT_PORT_BUSY";
  334. reopen = true;
  335. break;
  336. default:
  337. break;
  338. }
  339. if (reopen)
  340. {
  341. QLOG_DEBUG() << "libCEC : Reopenning adapter";
  342. auto cec = static_cast<InputCECWorker*>(cbParam);
  343. if (cec)
  344. cec->closeAdapter();
  345. }
  346. return 0;
  347. }