InputCEC.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. #include <QDebug>
  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. InputCEC::~InputCEC()
  63. {
  64. QMetaObject::invokeMethod(m_cecWorker, "closeCec", Qt::BlockingQueuedConnection);
  65. m_cecThread->exit(0);
  66. m_cecThread->wait();
  67. delete m_cecWorker;
  68. }
  69. /////////////////////////////////////////////////////////////////////////////////////////
  70. bool InputCECWorker::init()
  71. {
  72. m_configuration.Clear();
  73. m_callbacks.Clear();
  74. m_verboseLogging = SettingsComponent::Get().value(SETTINGS_SECTION_CEC, "verbose_logging").toBool();
  75. m_configuration.clientVersion = LIBCEC_VERSION_CURRENT;
  76. qstrcpy(m_configuration.strDeviceName, "Jellyfin");
  77. m_configuration.bActivateSource = 0;
  78. m_callbacks.logMessage = &CecLogMessage;
  79. m_callbacks.commandReceived = &CecCommand;
  80. m_callbacks.alert = &CecAlert;
  81. m_configuration.callbackParam = this;
  82. m_configuration.callbacks = &m_callbacks;
  83. m_configuration.deviceTypes.Add(CEC_DEVICE_TYPE_RECORDING_DEVICE);
  84. m_configuration.bAutodetectAddress = CEC_DEFAULT_SETTING_AUTODETECT_ADDRESS;
  85. m_configuration.iPhysicalAddress = CEC_PHYSICAL_ADDRESS_TV;
  86. m_configuration.baseDevice = CECDEVICE_AUDIOSYSTEM;
  87. m_configuration.bActivateSource = (uint8_t)SettingsComponent::Get().value(SETTINGS_SECTION_CEC, "activatesource").toBool();
  88. m_configuration.iHDMIPort = (quint8)SettingsComponent::Get().value(SETTINGS_SECTION_CEC, "hdmiport").toInt();
  89. // open libcec
  90. m_adapter = (ICECAdapter*)CECInitialise(&m_configuration);
  91. if (!m_adapter)
  92. {
  93. qCritical() << "Unable to initialize libCEC.";
  94. return false;
  95. }
  96. qInfo() << "libCEC was successfully initialized, found version"
  97. << m_configuration.serverVersion;
  98. // init video on targets that need this
  99. m_adapter->InitVideoStandalone();
  100. // check for attached adapters
  101. checkAdapter();
  102. // Start a timer to keep track of attached/removed adapters
  103. m_timer = new QTimer(nullptr);
  104. m_timer->setInterval(10 * 1000);
  105. connect(m_timer, &QTimer::timeout, this, &InputCECWorker::checkAdapter);
  106. m_timer->start();
  107. return true;
  108. }
  109. //////////////////////////////////////////////////////////////////////////////////////////////////
  110. void InputCECWorker::closeCec()
  111. {
  112. if (m_timer->isActive())
  113. {
  114. m_timer->stop();
  115. delete m_timer;
  116. }
  117. if (m_adapter)
  118. {
  119. qDebug() << "Closing libCEC.";
  120. closeAdapter();
  121. CECDestroy(m_adapter);
  122. }
  123. }
  124. //////////////////////////////////////////////////////////////////////////////////////////////////
  125. bool InputCECWorker::openAdapter()
  126. {
  127. bool ret = false;
  128. // try to find devices
  129. cec_adapter_descriptor devices[10];
  130. int devicesCount = m_adapter->DetectAdapters(devices, 10, nullptr, false);
  131. if (devicesCount > 0)
  132. {
  133. // list devices
  134. qInfo() << "libCEC found" << devicesCount << "CEC adapters.";
  135. // open first adapter
  136. m_adapterPort = devices[0].strComName;
  137. if (m_adapter->Open(m_adapterPort.toStdString().c_str()))
  138. {
  139. qInfo() << "Device " << devices[0].strComName << "was successfully openned";
  140. ret = true;
  141. }
  142. else
  143. {
  144. qCritical() << "Opening device" << devices[0].strComName << "failed";
  145. ret = false;
  146. }
  147. }
  148. return ret;
  149. }
  150. //////////////////////////////////////////////////////////////////////////////////////////////////
  151. void InputCECWorker::closeAdapter()
  152. {
  153. m_adapterPort.clear();
  154. }
  155. ///////////////////////////////////////////////////////////////////////////////////////////////////
  156. void InputCECWorker::checkAdapter()
  157. {
  158. if (m_adapterPort.isEmpty())
  159. {
  160. if (m_adapter)
  161. m_adapter->Close();
  162. openAdapter();
  163. }
  164. }
  165. ///////////////////////////////////////////////////////////////////////////////////////////////////
  166. void InputCECWorker::sendReceivedInput(const QString &source, const QString &keycode, InputBase::InputkeyState keyState)
  167. {
  168. emit receivedInput(source, keycode, keyState);
  169. }
  170. ///////////////////////////////////////////////////////////////////////////////////////////////////
  171. QString InputCECWorker::getCommandString(cec_user_control_code code)
  172. {
  173. QString key;
  174. if (g_cecKeyMap.contains(code))
  175. {
  176. KeyAction keyaction = g_cecKeyMap[code];
  177. key = keyaction.action;
  178. }
  179. return key;
  180. }
  181. ///////////////////////////////////////////////////////////////////////////////////////////////////
  182. void InputCECWorker::CecLogMessage(void* cbParam, const cec_log_message *message)
  183. {
  184. auto *cec = static_cast<InputCECWorker*>(cbParam);
  185. Q_ASSERT(cec);
  186. switch (message->level)
  187. {
  188. case CEC_LOG_ERROR:
  189. qCritical() << "libCEC ERROR:" << message->message;
  190. break;
  191. case CEC_LOG_WARNING:
  192. qWarning() << "libCEC WARNING:" << message->message;
  193. break;
  194. case CEC_LOG_NOTICE:
  195. qInfo() << "libCEC NOTICE:" << message->message;
  196. break;
  197. case CEC_LOG_DEBUG:
  198. if (cec->m_verboseLogging)
  199. {
  200. qDebug() << "libCEC DEBUG:" << message->message;
  201. }
  202. break;
  203. case CEC_LOG_TRAFFIC:
  204. break;
  205. default:
  206. break;
  207. }
  208. return;
  209. }
  210. ///////////////////////////////////////////////////////////////////////////////////////////////////
  211. QString InputCECWorker::getCommandParamsList(const cec_command *command)
  212. {
  213. QString output = QString("%1 parameter(s) :").arg(command->parameters.size);
  214. if (command->parameters.size)
  215. {
  216. for (int i=0; i<command->parameters.size; i++)
  217. output += QString("[%1]=%2").arg(i).arg(QString::number(command->parameters[i], 16).toUpper());
  218. }
  219. return output;
  220. }
  221. ///////////////////////////////////////////////////////////////////////////////////////////////////
  222. void InputCECWorker::CecCommand(void *cbParam, const cec_command *command)
  223. {
  224. QString cmdString, keyCode;
  225. bool useUpDown = SettingsComponent::Get().value(SETTINGS_SECTION_CEC, "usekeyupdown").toBool();
  226. auto cec = static_cast<InputCECWorker*>(cbParam);
  227. Q_ASSERT(cec);
  228. if (cec->m_verboseLogging)
  229. {
  230. qDebug() << "CecCommand received " << QString::number(command->opcode, 16).toUpper() << "," << cec->getCommandParamsList(command);
  231. }
  232. switch(command->opcode)
  233. {
  234. case CEC_OPCODE_PLAY:
  235. cec->sendReceivedInput(CEC_INPUT_NAME, INPUT_KEY_PLAY, InputBase::KeyPressed);
  236. break;
  237. case CEC_OPCODE_DECK_CONTROL:
  238. if (command->parameters.size)
  239. {
  240. switch(command->parameters[0])
  241. {
  242. case CEC_DECK_CONTROL_MODE_SKIP_FORWARD_WIND:
  243. keyCode = INPUT_KEY_SEEKFWD;
  244. break;
  245. case CEC_DECK_CONTROL_MODE_SKIP_REVERSE_REWIND:
  246. keyCode = INPUT_KEY_SEEKBCK;
  247. break;
  248. case CEC_DECK_CONTROL_MODE_STOP:
  249. keyCode = INPUT_KEY_STOP;
  250. break;
  251. default:
  252. break;
  253. }
  254. if (!keyCode.isEmpty())
  255. {
  256. // We don't have up & down events for those special keys
  257. // so we just fake them
  258. cec->sendReceivedInput(CEC_INPUT_NAME, keyCode, InputBase::KeyPressed);
  259. }
  260. }
  261. break;
  262. case CEC_OPCODE_VENDOR_REMOTE_BUTTON_DOWN:
  263. case CEC_OPCODE_USER_CONTROL_PRESSED:
  264. case CEC_OPCODE_USER_CONTROL_RELEASE:
  265. case CEC_OPCODE_VENDOR_REMOTE_BUTTON_UP:
  266. {
  267. bool down = (command->opcode == CEC_OPCODE_VENDOR_REMOTE_BUTTON_DOWN) ||
  268. (command->opcode == CEC_OPCODE_USER_CONTROL_PRESSED);
  269. if (cec->m_verboseLogging)
  270. {
  271. qDebug() << "CecCommand button (Down= " << down << ")" << cec->getCommandParamsList(command);
  272. }
  273. if (command->parameters.size && down)
  274. {
  275. switch(command->parameters[0])
  276. {
  277. // samsung Return key
  278. case CEC_USER_CONTROL_CODE_AN_RETURN:
  279. if (useUpDown)
  280. cec->sendReceivedInput(CEC_INPUT_NAME, INPUT_KEY_BACK, down ? InputBase::KeyDown : InputBase::KeyUp);
  281. else if (down)
  282. cec->sendReceivedInput(CEC_INPUT_NAME, INPUT_KEY_BACK, InputBase::KeyPressed);
  283. return;
  284. break;
  285. default:
  286. break;
  287. }
  288. }
  289. cmdString = cec->getCommandString((cec_user_control_code)command->parameters[0]);
  290. if (!cmdString.isEmpty())
  291. {
  292. if (useUpDown)
  293. cec->sendReceivedInput(CEC_INPUT_NAME, cmdString, down ? InputBase::KeyDown : InputBase::KeyUp);
  294. else if (down)
  295. cec->sendReceivedInput(CEC_INPUT_NAME, cmdString, InputBase::KeyPressed);
  296. }
  297. }
  298. break;
  299. case CEC_OPCODE_GIVE_OSD_NAME: // ignore those known commands (only pollng from TV)
  300. case CEC_OPCODE_GIVE_PHYSICAL_ADDRESS:
  301. break;
  302. case CEC_OPCODE_STANDBY:
  303. qDebug() << "CecCommand : Got a standby Request";
  304. if ((SettingsComponent::Get().value(SETTINGS_SECTION_CEC, "suspendonstandby").toBool()) && PowerComponent::Get().canSuspend())
  305. {
  306. PowerComponent::Get().Suspend();
  307. }
  308. else if ((SettingsComponent::Get().value(SETTINGS_SECTION_CEC, "poweroffonstandby").toBool()) && PowerComponent::Get().canPowerOff())
  309. {
  310. PowerComponent::Get().PowerOff();
  311. }
  312. break;
  313. default:
  314. qDebug() << "Unhandled CEC command " << command->opcode << ", " << cec->getCommandParamsList(command);
  315. break;
  316. }
  317. return;
  318. }
  319. ///////////////////////////////////////////////////////////////////////////////////////////////////
  320. void InputCECWorker::CecAlert(void *cbParam, const libcec_alert type, const libcec_parameter param)
  321. {
  322. bool reopen = false;
  323. switch (type)
  324. {
  325. case CEC_ALERT_SERVICE_DEVICE:
  326. qCritical() << "libCEC : Alert CEC_ALERT_SERVICE_DEVICE";
  327. break;
  328. case CEC_ALERT_CONNECTION_LOST:
  329. qCritical() << "libCEC : Alert CEC_ALERT_CONNECTION_LOST";
  330. reopen = true;
  331. break;
  332. case CEC_ALERT_PERMISSION_ERROR:
  333. qCritical() << "libCEC : Alert CEC_ALERT_PERMISSION_ERROR";
  334. reopen = true;
  335. break;
  336. case CEC_ALERT_PORT_BUSY:
  337. qCritical() << "libCEC : Alert CEC_ALERT_PORT_BUSY";
  338. reopen = true;
  339. break;
  340. default:
  341. break;
  342. }
  343. if (reopen)
  344. {
  345. qDebug() << "libCEC : Reopenning adapter";
  346. auto cec = static_cast<InputCECWorker*>(cbParam);
  347. if (cec)
  348. cec->closeAdapter();
  349. }
  350. return;
  351. }