InputCEC.cpp 14 KB

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