File indexing completed on 2024-04-28 16:42:44
0001 /* 0002 * SPDX-FileCopyrightText: 2022 Bart Ribbers <bribbers@disroot.org> 0003 * SPDX-FileCopyrightText: 2022 Aditya Mehra <aix.m@outlook.com> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0006 */ 0007 0008 #include "ceccontroller.h" 0009 #include "../controllermanager.h" 0010 #include "../device.h" 0011 0012 #include <QDebug> 0013 #include <QDBusConnection> 0014 #include <KSharedConfig> 0015 #include <KConfigGroup> 0016 #include <KLocalizedString> 0017 0018 #include <Solid/DeviceNotifier> 0019 0020 #include <iostream> // Workaround for libcec bug 0021 #include <libcec/cec.h> 0022 #include <libcec/cecloader.h> 0023 #include <libcec/cectypes.h> 0024 #include <linux/input-event-codes.h> 0025 #include <unistd.h> 0026 0027 using namespace CEC; 0028 0029 QHash<int, int> CECController::m_keyCodeTranslation; 0030 bool CECController::m_catchNextInput = false; 0031 bool CECController::m_nativeNavMode = true; 0032 int CECController::m_caughtInput = -1; 0033 int CECController::m_hitcommand; 0034 0035 0036 void CECController::handleCecKeypress(void* param, const cec_keypress* key) 0037 { 0038 Q_UNUSED(param); 0039 // only handle complete event when we get the keycode, opcode for press event is always sent before keycode 0040 handleCompleteEvent(key->keycode, key->duration, m_hitcommand); 0041 } 0042 0043 void CECController::handleCommandReceived(void* param, const cec_command* command) 0044 { 0045 Q_UNUSED(param); 0046 m_hitcommand = command->opcode; 0047 } 0048 0049 void CECController::handleCompleteEvent(const int keycode, const int keyduration, const int opcode) 0050 { 0051 Q_UNUSED(keyduration); 0052 0053 if (m_catchNextInput) { 0054 m_caughtInput = keycode; 0055 0056 // check if m_caughtInput has changed 0057 if (m_caughtInput != -1) { 0058 m_catchNextInput = false; 0059 } 0060 } else if(m_nativeNavMode) { 0061 int nativeKeyCode = m_keyCodeTranslation.value(keycode, -1); 0062 0063 if (nativeKeyCode < 0) { 0064 qDebug() << "DEBUG: Received a keypress we do not handle!"; 0065 return; 0066 } 0067 0068 if(opcode == CEC_OPCODE_USER_CONTROL_PRESSED) { 0069 // send key press and key release events before cec key release event 0070 // otherwise the key release event will take 500 ms to be sent and cause the key to be stuck 0071 // in the pressed state 0072 ControllerManager::instance().emitKey(nativeKeyCode, 1); 0073 ControllerManager::instance().emitKey(nativeKeyCode, 0); 0074 } else if (opcode == CEC_OPCODE_USER_CONTROL_RELEASE) { 0075 0076 // not sure if this is actually needed, cec will send key pressed events even when it hasn't produced a key release event 0077 ControllerManager::instance().emitKey(nativeKeyCode, 0); 0078 } 0079 } else { 0080 return; 0081 } 0082 } 0083 0084 CECController::CECController() 0085 { 0086 QDBusConnection::sessionBus().registerService("org.kde.plasma.remotecontrollers"); 0087 QDBusConnection::sessionBus().registerObject("/CEC", this, QDBusConnection::ExportScriptableSlots); 0088 0089 KSharedConfigPtr config = KSharedConfig::openConfig(); 0090 KConfigGroup generalGroup = config->group("General"); 0091 0092 m_keyCodeTranslation = { 0093 { generalGroup.readEntry("ButtonPlay", (int) CEC_USER_CONTROL_CODE_PLAY), KEY_PLAY}, 0094 { generalGroup.readEntry("ButtonStop", (int) CEC_USER_CONTROL_CODE_STOP), KEY_STOP}, 0095 { generalGroup.readEntry("ButtonPause", (int) CEC_USER_CONTROL_CODE_PAUSE), KEY_PAUSE}, 0096 { generalGroup.readEntry("ButtonRewind", (int) CEC_USER_CONTROL_CODE_REWIND), KEY_REWIND}, 0097 { generalGroup.readEntry("ButtonFastforward", (int) CEC_USER_CONTROL_CODE_FAST_FORWARD), KEY_FASTFORWARD}, 0098 { generalGroup.readEntry("ButtonEnter", (int) CEC_USER_CONTROL_CODE_SELECT), KEY_ENTER}, 0099 { generalGroup.readEntry("ButtonUp", (int) CEC_USER_CONTROL_CODE_UP), KEY_UP}, 0100 { generalGroup.readEntry("ButtonDown", (int) CEC_USER_CONTROL_CODE_DOWN), KEY_DOWN}, 0101 { generalGroup.readEntry("ButtonLeft", (int) CEC_USER_CONTROL_CODE_LEFT), KEY_LEFT}, 0102 { generalGroup.readEntry("ButtonRight", (int) CEC_USER_CONTROL_CODE_RIGHT), KEY_RIGHT}, 0103 { generalGroup.readEntry("ButtonNumber0", (int) CEC_USER_CONTROL_CODE_NUMBER0), KEY_0}, 0104 { generalGroup.readEntry("ButtonNumber1", (int) CEC_USER_CONTROL_CODE_NUMBER1), KEY_1}, 0105 { generalGroup.readEntry("ButtonNumber2", (int) CEC_USER_CONTROL_CODE_NUMBER2), KEY_2}, 0106 { generalGroup.readEntry("ButtonNumber3", (int) CEC_USER_CONTROL_CODE_NUMBER3), KEY_3}, 0107 { generalGroup.readEntry("ButtonNumber4", (int) CEC_USER_CONTROL_CODE_NUMBER4), KEY_4}, 0108 { generalGroup.readEntry("ButtonNumber5", (int) CEC_USER_CONTROL_CODE_NUMBER5), KEY_5}, 0109 { generalGroup.readEntry("ButtonNumber6", (int) CEC_USER_CONTROL_CODE_NUMBER6), KEY_6}, 0110 { generalGroup.readEntry("ButtonNumber7", (int) CEC_USER_CONTROL_CODE_NUMBER7), KEY_7}, 0111 { generalGroup.readEntry("ButtonNumber8", (int) CEC_USER_CONTROL_CODE_NUMBER8), KEY_8}, 0112 { generalGroup.readEntry("ButtonNumber9", (int) CEC_USER_CONTROL_CODE_NUMBER9), KEY_9}, 0113 { generalGroup.readEntry("ButtonBlue", (int) CEC_USER_CONTROL_CODE_F1_BLUE), KEY_BLUE}, 0114 { generalGroup.readEntry("ButtonRed", (int) CEC_USER_CONTROL_CODE_F2_RED), KEY_RED}, 0115 { generalGroup.readEntry("ButtonGreen", (int) CEC_USER_CONTROL_CODE_F3_GREEN), KEY_GREEN}, 0116 { generalGroup.readEntry("ButtonYellow", (int) CEC_USER_CONTROL_CODE_F4_YELLOW), KEY_YELLOW}, 0117 { generalGroup.readEntry("ButtonChannelUp", (int) CEC_USER_CONTROL_CODE_CHANNEL_UP), KEY_CHANNELUP}, 0118 { generalGroup.readEntry("ButtonChannelDown", (int) CEC_USER_CONTROL_CODE_CHANNEL_DOWN), KEY_CHANNELDOWN}, 0119 { generalGroup.readEntry("ButtonExit", (int) CEC_USER_CONTROL_CODE_EXIT), KEY_EXIT}, 0120 { generalGroup.readEntry("ButtonBack", (int) CEC_USER_CONTROL_CODE_AN_RETURN), KEY_BACK}, 0121 { generalGroup.readEntry("ButtonHome", (int) CEC_USER_CONTROL_CODE_ROOT_MENU), KEY_HOMEPAGE}, 0122 { generalGroup.readEntry("ButtonSubtitle", (int) CEC_USER_CONTROL_CODE_SUB_PICTURE), KEY_SUBTITLE}, 0123 { generalGroup.readEntry("ButtonInfo", (int) CEC_USER_CONTROL_CODE_DISPLAY_INFORMATION), KEY_INFO}, 0124 }; 0125 0126 m_cecCallbacks.Clear(); 0127 m_cecCallbacks.keyPress = &CECController::handleCecKeypress; 0128 m_cecCallbacks.commandReceived = &CECController::handleCommandReceived; 0129 0130 0131 libcec_configuration cecConfig; 0132 cecConfig.Clear(); 0133 cecConfig.bActivateSource = 0; 0134 snprintf(cecConfig.strDeviceName, LIBCEC_OSD_NAME_SIZE, "%s", qPrintable(generalGroup.readEntry("OSDName", i18n("KDE Plasma")))); 0135 cecConfig.clientVersion = LIBCEC_VERSION_CURRENT; 0136 cecConfig.deviceTypes.Add(CEC_DEVICE_TYPE_RECORDING_DEVICE); 0137 cecConfig.callbacks = &m_cecCallbacks; 0138 0139 m_cecAdapter = LibCecInitialise(&cecConfig); 0140 0141 if (!m_cecAdapter) { 0142 qCritical() << "Could not create CEC adaptor with current config"; 0143 exit(1); 0144 } 0145 0146 // Init video on targets that need this 0147 m_cecAdapter->InitVideoStandalone(); 0148 0149 auto notifier = Solid::DeviceNotifier::instance(); 0150 connect(notifier, &Solid::DeviceNotifier::deviceAdded, this, &CECController::discoverDevices); 0151 discoverDevices(); 0152 } 0153 0154 void CECController::discoverDevices() { 0155 cec_adapter_descriptor devices[10]; 0156 int8_t deviceCount = m_cecAdapter->DetectAdapters(devices, 10, nullptr, true); 0157 0158 if (deviceCount <= 0) { 0159 qWarning() << "No CEC devices found"; 0160 return; 0161 } 0162 0163 for (int8_t i = 0; i < deviceCount; i++) { 0164 QString uniqueIdentifier = devices[i].strComName; 0165 if (ControllerManager::instance().isConnected(uniqueIdentifier)) 0166 continue; 0167 0168 if (!m_cecAdapter->Open(devices[i].strComName)) { 0169 qWarning() << "Could not open CEC device " << devices[i].strComPath << " " << devices[i].strComName; 0170 return; 0171 } 0172 0173 // TODO: detect and handle disconnects 0174 Device* device = new Device(DeviceCEC, "CEC Controller", devices[i].strComName); 0175 device->setUsedKeys(QSet<int>(m_keyCodeTranslation.cbegin(), m_keyCodeTranslation.cend())); 0176 ControllerManager::instance().newDevice(device); 0177 } 0178 } 0179 0180 CECController::~CECController() = default; 0181 0182 int CECController::sendNextKey() 0183 { 0184 m_catchNextInput = true; 0185 m_nativeNavMode = false; 0186 0187 // don't depend on caught input being set 0188 // enter key sends keycode 0 which is == false, keeps the loop running indefinitely 0189 0190 while (m_catchNextInput) { 0191 sleep(1); 0192 } 0193 0194 m_nativeNavMode = true; 0195 0196 return m_caughtInput; 0197 } 0198 0199 bool CECController::hdmiCecSupported() 0200 { 0201 cec_logical_addresses iFaceAddresses = m_cecAdapter->GetLogicalAddresses(); 0202 cec_power_status currentStatus; 0203 for (uint8_t i = 0; i < CECDEVICE_BROADCAST; i++) { 0204 if (static_cast<cec_logical_address>(iFaceAddresses[i]) != CECDEVICE_UNKNOWN) { 0205 currentStatus = m_cecAdapter->GetDevicePowerStatus(static_cast<cec_logical_address>(iFaceAddresses[i])); 0206 if (currentStatus == CEC_POWER_STATUS_ON) { 0207 return true; 0208 } else { 0209 return false; 0210 } 0211 } 0212 } 0213 return false; 0214 } 0215 0216 bool CECController::sendKey(uchar cecKeycode, cec_logical_address address) 0217 { 0218 if (!m_cecAdapter->SendKeypress(address, static_cast<cec_user_control_code>(cecKeycode), true)) { 0219 return false; 0220 } 0221 m_cecAdapter->SendKeyRelease(address); 0222 return true; 0223 } 0224 0225 bool CECController::powerOnDevices(cec_logical_address address) 0226 { 0227 return m_cecAdapter->PowerOnDevices(address); 0228 } 0229 0230 bool CECController::powerOffDevices(cec_logical_address address) 0231 { 0232 return m_cecAdapter->StandbyDevices(address); 0233 } 0234 0235 bool CECController::makeActiveSource() 0236 { 0237 return m_cecAdapter->SetActiveSource(); 0238 }