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"