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 }