File indexing completed on 2024-04-14 15:31:34
0001 /* 0002 * SPDX-FileCopyrightText: 2022 Bart Ribbers <bribbers@disroot.org> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "controllermanager.h" 0008 #include "notificationsmanager.h" 0009 #include "uinputsystem.h" 0010 #include "kwinfakeinputsystem.h" 0011 0012 #include <KSharedConfig> 0013 #include <KConfigGroup> 0014 #include <KLocalizedString> 0015 #include <KStatusNotifierItem> 0016 #include <KWindowSystem> 0017 #include <QAction> 0018 #include <QMenu> 0019 0020 #include <taskmanager/tasksmodel.h> 0021 #include <taskmanager/abstracttasksmodel.h> 0022 0023 #include "remotecontrollers.h" 0024 #include "screensaver_interface.h" 0025 #include "plasmarc-debug.h" 0026 0027 class NoOpInputSystem : public AbstractSystem 0028 { 0029 public: 0030 bool init() override { return true; } 0031 void emitKey(int key, bool pressed) override { 0032 Q_UNUSED(key) 0033 Q_UNUSED(pressed) 0034 } 0035 }; 0036 0037 ControllerManager::ControllerManager(QObject *parent) 0038 : QObject(parent) 0039 , m_dbusInterface(new ControllerManagerDBusInterface(this)) 0040 , m_settings(new RemoteControllersSettings) 0041 { 0042 // Setup notifications 0043 QObject::connect(this, &ControllerManager::deviceConnected, 0044 &NotificationsManager::instance(), &NotificationsManager::notifyNewDevice); 0045 QObject::connect(this, &ControllerManager::deviceDisconnected, 0046 &NotificationsManager::instance(), &NotificationsManager::notifyDisconnectedDevice); 0047 0048 resetInputSystem(); 0049 0050 m_sni = new KStatusNotifierItem(QStringLiteral("org.kde.plasma.remotecontrollers"), this); 0051 m_sni->setIconByName("input-gamepad"); 0052 m_sni->setStandardActionsEnabled(false); 0053 m_sni->setContextMenu(new QMenu); 0054 m_sni->setTitle(i18n("Remote Controllers")); 0055 m_sni->setToolTipTitle(m_sni->title()); 0056 m_sni->setToolTipSubTitle(i18n("Configure Inhibited Apps")); 0057 0058 auto section = new QAction(i18n("Inhibitions")); 0059 section->setEnabled(false); 0060 m_sni->contextMenu()->addAction(section); 0061 0062 m_appsModel = new TaskManager::TasksModel(this); 0063 connect(m_appsModel, &TaskManager::TasksModel::activeTaskChanged, this, [this] { 0064 const QString appId = m_appsModel->activeTask().data(TaskManager::AbstractTasksModel::AppId).toString(); 0065 m_enabled = !appInhibited(appId); 0066 }); 0067 connect(m_appsModel, &TaskManager::TasksModel::rowsAboutToBeRemoved, this, &ControllerManager::refreshApps); 0068 connect(m_appsModel, &TaskManager::TasksModel::rowsInserted, this, &ControllerManager::refreshApps); 0069 connect(m_appsModel, &TaskManager::TasksModel::modelReset, this, &ControllerManager::refreshApps); 0070 0071 // Mark the SNI as passive after 10' without usage 0072 m_lastUsed.setInterval(10 * 60 * 1000); 0073 m_lastUsed.setSingleShot(true); 0074 connect(&m_lastUsed, &QTimer::timeout, this, [this] { 0075 m_sni->setStatus(KStatusNotifierItem::Passive); 0076 }); 0077 0078 refreshApps(); 0079 } 0080 0081 ControllerManager &ControllerManager::instance() 0082 { 0083 static ControllerManager _instance; 0084 return _instance; 0085 } 0086 0087 void ControllerManager::newDevice(Device *device) 0088 { 0089 qInfo() << "New device connected:" << device->getName(); 0090 0091 m_usedKeys += device->usedKeys(); 0092 m_inputSystem->setSupportedKeys(m_usedKeys); 0093 0094 device->setIndex(m_connectedDevices.size()); 0095 0096 connect(device, &Device::deviceDisconnected, this, &ControllerManager::removeDevice); 0097 0098 m_connectedDevices.append(device); 0099 0100 // Don't send notifications for CEC devices, since we expect them to always be available 0101 if (device->getDeviceType() != DeviceCEC) { 0102 emit deviceConnected(device); 0103 emit m_dbusInterface->deviceConnected(device->getUniqueIdentifier()); 0104 } 0105 m_lastUsed.start(); 0106 m_sni->setStatus(KStatusNotifierItem::Active); 0107 } 0108 0109 void ControllerManager::deviceRemoved(Device *device) 0110 { 0111 qInfo() << "Device disconnected:" << device->getName(); 0112 emit deviceDisconnected(device); 0113 m_connectedDevices.removeOne(device); 0114 for (int i = 0; i < m_connectedDevices.size(); i++) { 0115 m_connectedDevices[i]->setIndex(i); 0116 } 0117 emit m_dbusInterface->deviceDisconnected(device->getUniqueIdentifier()); 0118 0119 m_sni->setStatus(m_connectedDevices.count() > 0 ? KStatusNotifierItem::Active : KStatusNotifierItem::Passive); 0120 m_lastUsed.start(); 0121 } 0122 0123 void ControllerManager::removeDevice(int deviceIndex) 0124 { 0125 Device *removedDevice = m_connectedDevices.at(deviceIndex); 0126 m_connectedDevices.remove(deviceIndex); 0127 0128 qInfo() << "Device disconnected:" << removedDevice->getName(); 0129 0130 emit deviceDisconnected(removedDevice); 0131 delete removedDevice; 0132 0133 // Reset indexes 0134 for (int i = 0; i < m_connectedDevices.size(); i++) 0135 m_connectedDevices.at(i)->setIndex(i); 0136 } 0137 0138 bool ControllerManager::isConnected(QString uniqueIdentifier) 0139 { 0140 if (m_connectedDevices.size() < 1) 0141 return false; 0142 0143 return std::find_if(m_connectedDevices.begin(), m_connectedDevices.end(), [&uniqueIdentifier](Device *other) { 0144 return other->getUniqueIdentifier() == uniqueIdentifier; 0145 }) != m_connectedDevices.end(); 0146 } 0147 0148 QVector<Device*> ControllerManager::getDevicesByType(DeviceType deviceType) 0149 { 0150 QVector<Device*> devices; 0151 0152 for (int i = 0; i < m_connectedDevices.size(); i++) 0153 if (m_connectedDevices.at(i)->getDeviceType() == deviceType) 0154 devices.append(m_connectedDevices.at(i)); 0155 0156 return devices; 0157 } 0158 0159 QVector<Device*> ControllerManager::connectedDevices() 0160 { 0161 return m_connectedDevices; 0162 } 0163 0164 void ControllerManager::emitKey(int key, bool pressed) 0165 { 0166 m_sni->setStatus(KStatusNotifierItem::Active); 0167 m_lastUsed.start(); 0168 if (!m_enabled) { 0169 // Make sure the system knows that it's being interacted with 0170 simulateUserActivity(); 0171 return; 0172 } 0173 0174 m_inputSystem->emitKey(key, pressed); 0175 } 0176 0177 ControllerManager::~ControllerManager() 0178 { 0179 m_connectedDevices.clear(); 0180 } 0181 0182 void ControllerManager::noopInput() 0183 { 0184 m_inputSystem.reset(new NoOpInputSystem); 0185 } 0186 0187 void ControllerManager::resetInputSystem() 0188 { 0189 m_inputSystem.reset(); 0190 std::unique_ptr<AbstractSystem> inputSystem(new UInputSystem); 0191 if (inputSystem->init()) { 0192 m_inputSystem.reset(inputSystem.release()); 0193 } 0194 0195 if (!m_inputSystem && KWindowSystem::isPlatformWayland()) { 0196 std::unique_ptr<AbstractSystem> inputSystem(new KWinFakeInputSystem); 0197 if (inputSystem->init()) { 0198 m_inputSystem.reset(inputSystem.release()); 0199 } 0200 } 0201 if (!m_inputSystem) { 0202 m_inputSystem.reset(new NoOpInputSystem); 0203 qCWarning(PLASMARC) << "Could not find an input system, plasma-remotecontrollers will not be able to send events"; 0204 } 0205 } 0206 0207 void ControllerManager::refreshApps() 0208 { 0209 auto menu = m_sni->contextMenu(); 0210 auto actions = menu->actions(); 0211 for (int i = 0, c = m_appsModel->rowCount(); i < c; ++i) { 0212 const auto task = m_appsModel->index(i, 0); 0213 auto appId = task.data(TaskManager::AbstractTasksModel::AppId).toString(); 0214 auto &action = m_appActions[appId]; 0215 if (action) { 0216 actions.removeAll(action); 0217 } else { 0218 QString appName = task.data(TaskManager::AbstractTasksModel::AppName).toString(); 0219 if (appName.isNull()) { 0220 appName = task.data(Qt::DisplayRole).toString(); 0221 } 0222 action = new QAction(appName); 0223 action->setCheckable(true); 0224 action->setChecked(appInhibited(appId)); 0225 action->setProperty("appId", appId); 0226 connect(action, &QAction::toggled, this, [this, action] (bool checked) { 0227 if (checked) { 0228 auto apps = m_settings->applications(); 0229 apps += action->property("appId").toString(); 0230 m_settings->setApplications(apps); 0231 } else { 0232 auto apps = m_settings->applications(); 0233 apps.removeAll(action->property("appId").toString()); 0234 m_settings->setApplications(apps); 0235 } 0236 m_settings->save(); 0237 }); 0238 m_appActions[appId] = action; 0239 menu->addAction(action); 0240 } 0241 } 0242 for (auto action : actions) { 0243 menu->removeAction(action); 0244 delete m_appActions.take(action->property("appId").toString()); 0245 } 0246 } 0247 0248 bool ControllerManager::appInhibited(const QString &appId) const 0249 { 0250 const auto inhibitedApps = m_settings->applications(); 0251 for (const auto &inhibitedApp : inhibitedApps) { 0252 const QRegularExpression rx(QRegularExpression::wildcardToRegularExpression(inhibitedApp)); 0253 const auto match = rx.match(appId); 0254 if (match.hasMatch()) { 0255 return true; 0256 } 0257 } 0258 return false; 0259 } 0260 0261 void ControllerManager::simulateUserActivity() 0262 { 0263 const auto current = QDateTime::currentDateTimeUtc(); 0264 if (!m_lastSimulated.isNull() && m_lastSimulated.secsTo(current) < 60) { 0265 return; 0266 } 0267 0268 m_lastSimulated = current; 0269 OrgFreedesktopScreenSaverInterface iface(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QDBusConnection::sessionBus()); 0270 iface.SimulateUserActivity(); 0271 }