File indexing completed on 2024-05-12 05:27:54
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 <QDBusMetaType> 0015 #include <KSharedConfig> 0016 #include <KConfigGroup> 0017 #include <KLocalizedString> 0018 0019 #include <Solid/DeviceNotifier> 0020 0021 #include <iostream> // Workaround for libcec bug 0022 #include <libcec/cec.h> 0023 #include <libcec/cecloader.h> 0024 #include <libcec/cectypes.h> 0025 #include <linux/input-event-codes.h> 0026 #include <unistd.h> 0027 0028 using namespace CEC; 0029 0030 QHash<int, int> CECController::m_keyCodeTranslation; 0031 bool CECController::m_catchNextInput = false; 0032 bool CECController::m_nativeNavMode = true; 0033 int CECController::m_caughtInput = -1; 0034 int CECController::m_hitcommand; 0035 0036 0037 QDBusArgument& operator<<(QDBusArgument& arg, const cec_logical_address& address) { 0038 arg.beginStructure(); 0039 arg << static_cast<uchar>(address); 0040 arg.endStructure(); 0041 return arg; 0042 } 0043 0044 const QDBusArgument& operator>>(const QDBusArgument& arg, cec_logical_address& address) { 0045 uchar value; 0046 arg.beginStructure(); 0047 arg >> value; 0048 arg.endStructure(); 0049 address = static_cast<cec_logical_address>(value); 0050 return arg; 0051 } 0052 0053 void CECController::handleCecKeypress(void* param, const cec_keypress* key) 0054 { 0055 Q_UNUSED(param); 0056 // only handle complete event when we get the keycode, opcode for press event is always sent before keycode 0057 handleCompleteEvent(key->keycode, key->duration, m_hitcommand); 0058 } 0059 0060 void CECController::handleCommandReceived(void* param, const cec_command* command) 0061 { 0062 CECController* self = static_cast<CECController*>(param); 0063 m_hitcommand = command->opcode; 0064 if (m_hitcommand == CEC_OPCODE_STANDBY) { 0065 QMetaObject::invokeMethod(self, "enterStandby"); 0066 } 0067 } 0068 0069 void CECController::handleSourceActivated(void* param, const cec_logical_address address, uint8_t activated) 0070 { 0071 Q_UNUSED(address); 0072 CECController* self = static_cast<CECController*>(param); 0073 QMetaObject::invokeMethod(self, "sourceActivated", Q_ARG(bool, activated)); 0074 } 0075 0076 void CECController::handleCompleteEvent(const int keycode, const int keyduration, const int opcode) 0077 { 0078 Q_UNUSED(keyduration); 0079 0080 if (m_catchNextInput) { 0081 m_caughtInput = keycode; 0082 0083 // check if m_caughtInput has changed 0084 if (m_caughtInput != -1) { 0085 m_catchNextInput = false; 0086 } 0087 } else if(m_nativeNavMode) { 0088 int nativeKeyCode = m_keyCodeTranslation.value(keycode, -1); 0089 0090 if (nativeKeyCode < 0) { 0091 qDebug() << "DEBUG: Received a keypress we do not handle!"; 0092 return; 0093 } 0094 0095 if(opcode == CEC_OPCODE_USER_CONTROL_PRESSED) { 0096 // send key press and key release events before cec key release event 0097 // otherwise the key release event will take 500 ms to be sent and cause the key to be stuck 0098 // in the pressed state 0099 ControllerManager::instance().emitKey(nativeKeyCode, 1); 0100 ControllerManager::instance().emitKey(nativeKeyCode, 0); 0101 } else if (opcode == CEC_OPCODE_USER_CONTROL_RELEASE) { 0102 0103 // not sure if this is actually needed, cec will send key pressed events even when it hasn't produced a key release event 0104 ControllerManager::instance().emitKey(nativeKeyCode, 0); 0105 } 0106 } else { 0107 return; 0108 } 0109 } 0110 0111 CECController::CECController() 0112 { 0113 qDBusRegisterMetaType<cec_logical_address>(); 0114 QDBusConnection::sessionBus().registerService("org.kde.plasma.remotecontrollers"); 0115 QDBusConnection::sessionBus().registerObject("/CEC", this, QDBusConnection::ExportScriptableSlots | QDBusConnection::ExportScriptableSignals); 0116 0117 KSharedConfigPtr config = KSharedConfig::openConfig(); 0118 KConfigGroup generalGroup = config->group("General"); 0119 0120 auto configPair = [&generalGroup](const char* name, int cecKey, int evKey) { 0121 return std::make_pair<int, int>(generalGroup.readEntry(QString("Button") + name, cecKey), generalGroup.readEntry(QString("Key") + name, evKey)); 0122 }; 0123 0124 m_keyCodeTranslation = { 0125 configPair("Play", CEC_USER_CONTROL_CODE_PLAY, KEY_PLAY), 0126 configPair("Stop", CEC_USER_CONTROL_CODE_STOP, KEY_STOP), 0127 configPair("Pause", CEC_USER_CONTROL_CODE_PAUSE, KEY_PAUSE), 0128 configPair("Rewind", CEC_USER_CONTROL_CODE_REWIND, KEY_REWIND), 0129 configPair("Fastforward", CEC_USER_CONTROL_CODE_FAST_FORWARD, KEY_FASTFORWARD), 0130 configPair("Enter", CEC_USER_CONTROL_CODE_SELECT, KEY_ENTER), 0131 configPair("Up", CEC_USER_CONTROL_CODE_UP, KEY_UP), 0132 configPair("Down", CEC_USER_CONTROL_CODE_DOWN, KEY_DOWN), 0133 configPair("Left", CEC_USER_CONTROL_CODE_LEFT, KEY_LEFT), 0134 configPair("Right", CEC_USER_CONTROL_CODE_RIGHT, KEY_RIGHT), 0135 configPair("Number0", CEC_USER_CONTROL_CODE_NUMBER0, KEY_0), 0136 configPair("Number1", CEC_USER_CONTROL_CODE_NUMBER1, KEY_1), 0137 configPair("Number2", CEC_USER_CONTROL_CODE_NUMBER2, KEY_2), 0138 configPair("Number3", CEC_USER_CONTROL_CODE_NUMBER3, KEY_3), 0139 configPair("Number4", CEC_USER_CONTROL_CODE_NUMBER4, KEY_4), 0140 configPair("Number5", CEC_USER_CONTROL_CODE_NUMBER5, KEY_5), 0141 configPair("Number6", CEC_USER_CONTROL_CODE_NUMBER6, KEY_6), 0142 configPair("Number7", CEC_USER_CONTROL_CODE_NUMBER7, KEY_7), 0143 configPair("Number8", CEC_USER_CONTROL_CODE_NUMBER8, KEY_8), 0144 configPair("Number9", CEC_USER_CONTROL_CODE_NUMBER9, KEY_9), 0145 configPair("Blue", CEC_USER_CONTROL_CODE_F1_BLUE, KEY_BLUE), 0146 configPair("Red", CEC_USER_CONTROL_CODE_F2_RED, KEY_RED), 0147 configPair("Green", CEC_USER_CONTROL_CODE_F3_GREEN, KEY_GREEN), 0148 configPair("Yellow", CEC_USER_CONTROL_CODE_F4_YELLOW, KEY_YELLOW), 0149 configPair("ChannelUp", CEC_USER_CONTROL_CODE_CHANNEL_UP, KEY_CHANNELUP), 0150 configPair("ChannelDown", CEC_USER_CONTROL_CODE_CHANNEL_DOWN, KEY_CHANNELDOWN), 0151 configPair("Exit", CEC_USER_CONTROL_CODE_EXIT, KEY_EXIT), 0152 configPair("Back", CEC_USER_CONTROL_CODE_AN_RETURN, KEY_BACK), 0153 configPair("Home", CEC_USER_CONTROL_CODE_ROOT_MENU, KEY_HOMEPAGE), 0154 configPair("Subtitle", CEC_USER_CONTROL_CODE_SUB_PICTURE, KEY_SUBTITLE), 0155 configPair("Info", CEC_USER_CONTROL_CODE_DISPLAY_INFORMATION, KEY_INFO), 0156 }; 0157 0158 m_cecCallbacks.Clear(); 0159 m_cecCallbacks.keyPress = &CECController::handleCecKeypress; 0160 m_cecCallbacks.commandReceived = &CECController::handleCommandReceived; 0161 m_cecCallbacks.sourceActivated = &CECController::handleSourceActivated; 0162 0163 libcec_configuration cecConfig; 0164 cecConfig.Clear(); 0165 cecConfig.bActivateSource = 0; 0166 snprintf(cecConfig.strDeviceName, LIBCEC_OSD_NAME_SIZE, "%s", qPrintable(generalGroup.readEntry("OSDName", i18n("KDE Plasma")))); 0167 cecConfig.clientVersion = LIBCEC_VERSION_CURRENT; 0168 cecConfig.deviceTypes.Add(CEC_DEVICE_TYPE_RECORDING_DEVICE); 0169 cecConfig.callbacks = &m_cecCallbacks; 0170 cecConfig.callbackParam = this; 0171 0172 m_cecAdapter = LibCecInitialise(&cecConfig); 0173 0174 if (!m_cecAdapter) { 0175 qCritical() << "Could not create CEC adaptor with current config"; 0176 exit(1); 0177 } 0178 0179 // Init video on targets that need this 0180 m_cecAdapter->InitVideoStandalone(); 0181 0182 auto notifier = Solid::DeviceNotifier::instance(); 0183 connect(notifier, &Solid::DeviceNotifier::deviceAdded, this, &CECController::discoverDevices); 0184 discoverDevices(); 0185 } 0186 0187 void CECController::discoverDevices() { 0188 cec_adapter_descriptor devices[10]; 0189 int8_t deviceCount = m_cecAdapter->DetectAdapters(devices, 10, nullptr, true); 0190 0191 if (deviceCount <= 0) { 0192 qWarning() << "No CEC devices found"; 0193 return; 0194 } 0195 0196 for (int8_t i = 0; i < deviceCount; i++) { 0197 QString uniqueIdentifier = devices[i].strComName; 0198 if (ControllerManager::instance().isConnected(uniqueIdentifier)) 0199 continue; 0200 0201 if (!m_cecAdapter->Open(devices[i].strComName)) { 0202 qWarning() << "Could not open CEC device " << devices[i].strComPath << " " << devices[i].strComName; 0203 return; 0204 } 0205 0206 // TODO: detect and handle disconnects 0207 Device* device = new Device(DeviceCEC, "CEC Controller", devices[i].strComName); 0208 device->setUsedKeys(QSet<int>(m_keyCodeTranslation.cbegin(), m_keyCodeTranslation.cend())); 0209 ControllerManager::instance().newDevice(device); 0210 } 0211 } 0212 0213 CECController::~CECController() = default; 0214 0215 int CECController::sendNextKey() 0216 { 0217 m_catchNextInput = true; 0218 m_nativeNavMode = false; 0219 0220 // don't depend on caught input being set 0221 // enter key sends keycode 0 which is == false, keeps the loop running indefinitely 0222 0223 while (m_catchNextInput) { 0224 sleep(1); 0225 } 0226 0227 m_nativeNavMode = true; 0228 0229 return m_caughtInput; 0230 } 0231 0232 bool CECController::hdmiCecSupported() 0233 { 0234 cec_logical_addresses iFaceAddresses = m_cecAdapter->GetLogicalAddresses(); 0235 cec_power_status currentStatus; 0236 for (uint8_t i = 0; i < CECDEVICE_BROADCAST; i++) { 0237 if (static_cast<cec_logical_address>(iFaceAddresses[i]) != CECDEVICE_UNKNOWN) { 0238 currentStatus = m_cecAdapter->GetDevicePowerStatus(static_cast<cec_logical_address>(iFaceAddresses[i])); 0239 if (currentStatus == CEC_POWER_STATUS_ON) { 0240 return true; 0241 } else { 0242 return false; 0243 } 0244 } 0245 } 0246 return false; 0247 } 0248 0249 bool CECController::sendKey(uchar cecKeycode, cec_logical_address address) 0250 { 0251 if (!m_cecAdapter->SendKeypress(address, static_cast<cec_user_control_code>(cecKeycode), true)) { 0252 return false; 0253 } 0254 m_cecAdapter->SendKeyRelease(address); 0255 return true; 0256 } 0257 0258 bool CECController::powerOnDevices(cec_logical_address address) 0259 { 0260 return m_cecAdapter->PowerOnDevices(address); 0261 } 0262 0263 bool CECController::powerOffDevices(cec_logical_address address) 0264 { 0265 return m_cecAdapter->StandbyDevices(address); 0266 } 0267 0268 bool CECController::makeActiveSource() 0269 { 0270 return m_cecAdapter->SetActiveSource(); 0271 } 0272 0273 bool CECController::setOSDName(const QString& name) 0274 { 0275 libcec_configuration cecConfig; 0276 if (!m_cecAdapter->GetCurrentConfiguration(&cecConfig)) { 0277 return false; 0278 } 0279 snprintf(cecConfig.strDeviceName, LIBCEC_OSD_NAME_SIZE, "%s", qPrintable(name)); 0280 return m_cecAdapter->SetConfiguration(&cecConfig); 0281 } 0282 0283 #include "moc_ceccontroller.cpp"