File indexing completed on 2024-11-10 04:55:34
0001 /* 0002 SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org> 0003 SPDX-FileCopyrightText: 2016 Sebastian Kügler <sebas@kde.org> 0004 0005 Work sponsored by the LiMux project of the city of Munich: 0006 SPDX-FileCopyrightText: 2018 Kai Uwe Broulik <kde@broulik.de> 0007 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 #include "daemon.h" 0011 0012 #include "config.h" 0013 #include "device.h" 0014 #include "generator.h" 0015 #include "kscreen_daemon_debug.h" 0016 #include "osdservice_interface.h" 0017 0018 #include <kscreen/configmonitor.h> 0019 #include <kscreen/getconfigoperation.h> 0020 #include <kscreen/log.h> 0021 #include <kscreen/mode.h> 0022 #include <kscreen/output.h> 0023 #include <kscreen/screen.h> 0024 #include <kscreen/setconfigoperation.h> 0025 0026 #include <KPluginFactory> 0027 0028 #include <QGuiApplication> 0029 #include <QTimer> 0030 #include <QTransform> 0031 0032 #if HAVE_X11 0033 #include <QtGui/private/qtx11extras_p.h> 0034 #include <X11/Xatom.h> 0035 #include <X11/Xlib-xcb.h> 0036 #include <X11/extensions/XInput.h> 0037 #include <X11/extensions/XInput2.h> 0038 #endif 0039 0040 K_PLUGIN_CLASS_WITH_JSON(KScreenDaemon, "kscreen.json") 0041 0042 #if HAVE_X11 0043 struct DeviceListDeleter { 0044 void operator()(XDeviceInfo *p) 0045 { 0046 if (p) { 0047 XFreeDeviceList(p); 0048 } 0049 } 0050 }; 0051 0052 struct XDeleter { 0053 void operator()(void *p) 0054 { 0055 if (p) { 0056 XFree(p); 0057 } 0058 } 0059 }; 0060 #endif 0061 0062 KScreenDaemon::KScreenDaemon(QObject *parent, const QList<QVariant> &) 0063 : KDEDModule(parent) 0064 , m_monitoring(false) 0065 , m_changeCompressor(new QTimer(this)) 0066 , m_saveTimer(nullptr) 0067 , m_lidClosedTimer(new QTimer(this)) 0068 { 0069 KScreen::Log::instance(); 0070 qMetaTypeId<KScreen::OsdAction>(); 0071 QMetaObject::invokeMethod(this, "getInitialConfig", Qt::QueuedConnection); 0072 } 0073 0074 void KScreenDaemon::getInitialConfig() 0075 { 0076 connect(new KScreen::GetConfigOperation, &KScreen::GetConfigOperation::finished, this, [this](KScreen::ConfigOperation *op) { 0077 if (op->hasError()) { 0078 qCDebug(KSCREEN_KDED) << "Error getting initial configuration" << op->errorString(); 0079 return; 0080 } 0081 0082 m_monitoredConfig = std::unique_ptr<Config>(new Config(qobject_cast<KScreen::GetConfigOperation *>(op)->config())); 0083 m_monitoredConfig->setValidityFlags(KScreen::Config::ValidityFlag::RequireAtLeastOneEnabledScreen); 0084 qCDebug(KSCREEN_KDED) << "Config" << m_monitoredConfig->data().data() << "is ready"; 0085 KScreen::ConfigMonitor::instance()->addConfig(m_monitoredConfig->data()); 0086 0087 init(); 0088 }); 0089 } 0090 0091 KScreenDaemon::~KScreenDaemon() 0092 { 0093 Generator::destroy(); 0094 Device::destroy(); 0095 } 0096 0097 void KScreenDaemon::init() 0098 { 0099 const QString osdService = QStringLiteral("org.kde.kscreen.osdService"); 0100 const QString osdPath = QStringLiteral("/org/kde/kscreen/osdService"); 0101 m_osdServiceInterface = new OrgKdeKscreenOsdServiceInterface(osdService, osdPath, QDBusConnection::sessionBus(), this); 0102 // Set a longer timeout to not assume timeout while the osd is still shown 0103 m_osdServiceInterface->setTimeout(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::seconds(60)).count()); 0104 0105 m_changeCompressor->setInterval(10); 0106 m_changeCompressor->setSingleShot(true); 0107 connect(m_changeCompressor, &QTimer::timeout, this, &KScreenDaemon::applyConfig); 0108 0109 m_lidClosedTimer->setInterval(1000); 0110 m_lidClosedTimer->setSingleShot(true); 0111 connect(m_lidClosedTimer, &QTimer::timeout, this, &KScreenDaemon::disableLidOutput); 0112 0113 connect(Device::self(), &Device::lidClosedChanged, this, &KScreenDaemon::lidClosedChanged); 0114 connect(Device::self(), &Device::resumingFromSuspend, this, [this]() { 0115 KScreen::Log::instance()->setContext(QStringLiteral("resuming")); 0116 qCDebug(KSCREEN_KDED) << "Resumed from suspend, checking for screen changes"; 0117 // We don't care about the result, we just want to force the backend 0118 // to query XRandR so that it will detect possible changes that happened 0119 // while the computer was suspended, and will emit the change events. 0120 new KScreen::GetConfigOperation(KScreen::GetConfigOperation::NoEDID, this); 0121 }); 0122 connect(Device::self(), &Device::aboutToSuspend, this, [this]() { 0123 qCDebug(KSCREEN_KDED) << "System is going to suspend, won't be changing config (waited for " 0124 << (m_lidClosedTimer->interval() - m_lidClosedTimer->remainingTime()) << "ms)"; 0125 m_lidClosedTimer->stop(); 0126 }); 0127 0128 connect(Generator::self(), &Generator::ready, this, [this] { 0129 applyConfig(); 0130 0131 if (Device::self()->isLaptop() && Device::self()->isLidClosed()) { 0132 disableLidOutput(); 0133 } 0134 0135 m_startingUp = false; 0136 }); 0137 0138 Generator::self()->setCurrentConfig(m_monitoredConfig->data()); 0139 monitorConnectedChange(); 0140 } 0141 0142 void KScreenDaemon::doApplyConfig(const KScreen::ConfigPtr &config) 0143 { 0144 qCDebug(KSCREEN_KDED) << "Do set and apply specific config"; 0145 auto configWrapper = std::unique_ptr<Config>(new Config(config)); 0146 configWrapper->setValidityFlags(KScreen::Config::ValidityFlag::RequireAtLeastOneEnabledScreen); 0147 0148 doApplyConfig(std::move(configWrapper)); 0149 } 0150 0151 void KScreenDaemon::doApplyConfig(std::unique_ptr<Config> config) 0152 { 0153 m_monitoredConfig = std::move(config); 0154 0155 m_monitoredConfig->activateControlWatching(); 0156 0157 refreshConfig(); 0158 } 0159 0160 void KScreenDaemon::refreshConfig() 0161 { 0162 setMonitorForChanges(false); 0163 m_configDirty = false; 0164 KScreen::ConfigMonitor::instance()->addConfig(m_monitoredConfig->data()); 0165 0166 connect(new KScreen::SetConfigOperation(m_monitoredConfig->data()), &KScreen::SetConfigOperation::finished, this, [this]() { 0167 qCDebug(KSCREEN_KDED) << "Config applied"; 0168 if (m_configDirty) { 0169 // Config changed in the meantime again, apply. 0170 doApplyConfig(m_monitoredConfig->data()); 0171 } else { 0172 setMonitorForChanges(true); 0173 } 0174 }); 0175 } 0176 0177 void KScreenDaemon::applyConfig() 0178 { 0179 qCDebug(KSCREEN_KDED) << "Applying config"; 0180 if (m_monitoredConfig->fileExists()) { 0181 applyKnownConfig(); 0182 return; 0183 } 0184 applyIdealConfig(); 0185 } 0186 0187 void KScreenDaemon::applyKnownConfig() 0188 { 0189 qCDebug(KSCREEN_KDED) << "Applying known config"; 0190 0191 std::unique_ptr<Config> readInConfig = m_monitoredConfig->readFile(); 0192 if (readInConfig) { 0193 doApplyConfig(std::move(readInConfig)); 0194 } else { 0195 qCDebug(KSCREEN_KDED) << "Loading failed, falling back to the ideal config" << m_monitoredConfig->id(); 0196 applyIdealConfig(); 0197 } 0198 } 0199 0200 void KScreenDaemon::showOSD() 0201 { 0202 QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kscreen.osdService"), 0203 QStringLiteral("/org/kde/kscreen/osdService"), 0204 QStringLiteral("org.kde.kscreen.osdService"), 0205 QStringLiteral("showActionSelector")); 0206 QDBusConnection::sessionBus().asyncCall(message); 0207 } 0208 0209 void KScreenDaemon::applyIdealConfig() 0210 { 0211 const bool showOsd = m_monitoredConfig->data()->connectedOutputs().count() > 1 && !m_startingUp; 0212 0213 doApplyConfig(Generator::self()->idealConfig(m_monitoredConfig->data())); 0214 0215 if (showOsd) { 0216 qCDebug(KSCREEN_KDED) << "Getting ideal config from user via OSD..."; 0217 showOSD(); 0218 } else { 0219 m_osdServiceInterface->hideOsd(); 0220 } 0221 } 0222 0223 void KScreenDaemon::configChanged() 0224 { 0225 qCDebug(KSCREEN_KDED) << "Change detected"; 0226 m_monitoredConfig->log(); 0227 0228 // Modes may have changed, fix-up current mode id 0229 bool changed = false; 0230 const auto outputs = m_monitoredConfig->data()->outputs(); 0231 for (const KScreen::OutputPtr &output : outputs) { 0232 if (output->isConnected() && output->isEnabled() 0233 && (output->currentMode().isNull() || (output->followPreferredMode() && output->currentModeId() != output->preferredModeId()))) { 0234 qCDebug(KSCREEN_KDED) << "Current mode was" << output->currentModeId() << ", setting preferred mode" << output->preferredModeId(); 0235 output->setCurrentModeId(output->preferredModeId()); 0236 changed = true; 0237 } 0238 } 0239 if (changed) { 0240 refreshConfig(); 0241 } 0242 0243 // Reset timer, delay the writeback 0244 if (!m_saveTimer) { 0245 m_saveTimer = new QTimer(this); 0246 m_saveTimer->setInterval(300); 0247 m_saveTimer->setSingleShot(true); 0248 connect(m_saveTimer, &QTimer::timeout, this, &KScreenDaemon::saveCurrentConfig); 0249 } 0250 m_saveTimer->start(); 0251 #if HAVE_X11 0252 alignX11TouchScreen(); 0253 #endif 0254 } 0255 0256 #if HAVE_X11 0257 void KScreenDaemon::alignX11TouchScreen() 0258 { 0259 if (qGuiApp->platformName() != QStringLiteral("xcb")) { 0260 return; 0261 } 0262 auto *display = QX11Info::display(); 0263 if (!display) { 0264 return; 0265 } 0266 auto *connection = QX11Info::connection(); 0267 if (!connection) { 0268 return; 0269 } 0270 0271 const QRect totalRect(QPoint(0, 0), m_monitoredConfig->data()->screen()->currentSize()); 0272 QRect internalOutputRect; 0273 int touchScreenRotationAngle = 0; 0274 0275 for (const auto &output : m_monitoredConfig->data()->connectedOutputs()) { 0276 if (output->isEnabled() && output->type() == KScreen::Output::Panel) { 0277 internalOutputRect = output->geometry(); 0278 0279 switch (output->rotation()) { 0280 case KScreen::Output::Left: 0281 touchScreenRotationAngle = 90; 0282 break; 0283 case KScreen::Output::Right: 0284 touchScreenRotationAngle = 270; 0285 break; 0286 case KScreen::Output::Inverted: 0287 touchScreenRotationAngle = 180; 0288 break; 0289 default: 0290 touchScreenRotationAngle = 0; 0291 } 0292 } 0293 } 0294 0295 // Compute the transformation matrix for the 0296 QTransform transform; 0297 transform = transform.translate(float(internalOutputRect.x()) / float(totalRect.width()), float(internalOutputRect.y()) / float(totalRect.height())); 0298 transform = transform.scale(float(internalOutputRect.width()) / float(totalRect.width()), float(internalOutputRect.height()) / float(totalRect.height())); 0299 transform = transform.rotate(touchScreenRotationAngle); 0300 0301 // After rotation we need to make the matrix origin aligned with the workspace again 0302 // ____ ___ 0303 // |__| -> 90° clockwise -> ___ -> needs to be moved up -> | | 0304 // | | |_| 0305 // |_| 0306 switch (touchScreenRotationAngle) { 0307 case 90: 0308 transform = transform.translate(0, -1); 0309 break; 0310 case 270: 0311 transform = transform.translate(-1, 0); 0312 break; 0313 case 180: 0314 transform = transform.translate(-1, -1); 0315 break; 0316 default: 0317 break; 0318 } 0319 0320 auto getAtom = [](xcb_connection_t *connection, const char *name) { 0321 auto cookie = xcb_intern_atom(connection, true, strlen(name), name); 0322 auto reply = xcb_intern_atom_reply(connection, cookie, nullptr); 0323 if (reply) { 0324 return reply->atom; 0325 } else { 0326 return xcb_atom_t(0); 0327 } 0328 }; 0329 0330 int nDevices = 0; 0331 std::unique_ptr<XDeviceInfo, DeviceListDeleter> deviceInfo(XListInputDevices(display, &nDevices)); 0332 auto touchScreenAtom = getAtom(connection, XI_TOUCHSCREEN); 0333 if (touchScreenAtom == 0) { 0334 return; 0335 } 0336 auto matrixAtom = getAtom(connection, "Coordinate Transformation Matrix"); 0337 if (matrixAtom == 0) { 0338 return; 0339 } 0340 auto floatAtom = getAtom(connection, "FLOAT"); 0341 if (floatAtom == 0) { 0342 return; 0343 } 0344 0345 auto setMatrixAtom = [display, floatAtom](XDeviceInfo *info, Atom atom, const QTransform &transform) { 0346 Atom type; 0347 int format = 0; 0348 unsigned long nItems, bytesAfter; 0349 unsigned char *dataPtr = nullptr; 0350 0351 std::unique_ptr<unsigned char, XDeleter> data(dataPtr); 0352 XIGetProperty(display, info->id, atom, 0, 1000, False, AnyPropertyType, &type, &format, &nItems, &bytesAfter, &dataPtr); 0353 0354 if (nItems != 9) { 0355 return; 0356 } 0357 if (format != sizeof(float) * CHAR_BIT || type != floatAtom) { 0358 return; 0359 } 0360 0361 float *fData = reinterpret_cast<float *>(dataPtr); 0362 0363 fData[0] = transform.m11(); 0364 fData[1] = transform.m21(); 0365 fData[2] = transform.m31(); 0366 0367 fData[3] = transform.m12(); 0368 fData[4] = transform.m22(); 0369 fData[5] = transform.m32(); 0370 0371 fData[6] = transform.m13(); 0372 fData[7] = transform.m23(); 0373 fData[8] = transform.m33(); 0374 0375 XIChangeProperty(display, info->id, atom, type, format, PropModeReplace, dataPtr, nItems); 0376 }; 0377 0378 for (XDeviceInfo *info = deviceInfo.get(); info < deviceInfo.get() + nDevices; info++) { 0379 // Make sure device is touchscreen 0380 if (info->type != touchScreenAtom) { 0381 continue; 0382 } 0383 0384 int nProperties = 0; 0385 std::unique_ptr<Atom, XDeleter> properties(XIListProperties(display, info->id, &nProperties)); 0386 0387 bool matrixAtomFound = false; 0388 0389 Atom *atom = properties.get(); 0390 Atom *atomEnd = properties.get() + nProperties; 0391 for (; atom != atomEnd; atom++) { 0392 if (!internalOutputRect.isEmpty() && *atom == matrixAtom) { 0393 matrixAtomFound = true; 0394 } 0395 } 0396 0397 if (matrixAtomFound) { 0398 setMatrixAtom(info, matrixAtom, transform); 0399 } 0400 0401 // For now we assume there is only one touchscreen 0402 XFlush(display); 0403 break; 0404 } 0405 } 0406 #endif 0407 0408 void KScreenDaemon::saveCurrentConfig() 0409 { 0410 qCDebug(KSCREEN_KDED) << "Saving current config to file"; 0411 0412 // We assume the config is valid, since it's what we got, but we are interested 0413 // in the "at least one enabled screen" check 0414 0415 if (m_monitoredConfig->canBeApplied()) { 0416 m_monitoredConfig->writeFile(); 0417 m_monitoredConfig->log(); 0418 } else { 0419 qCWarning(KSCREEN_KDED) << "Config does not have at least one screen enabled, WILL NOT save this config, this is not what user wants."; 0420 m_monitoredConfig->log(); 0421 } 0422 } 0423 0424 void KScreenDaemon::lidClosedChanged(bool lidIsClosed) 0425 { 0426 // Ignore this when we don't have any external monitors, we can't turn off our 0427 // only screen 0428 if (m_monitoredConfig->data()->connectedOutputs().count() == 1) { 0429 return; 0430 } 0431 0432 if (lidIsClosed) { 0433 // Lid is closed, now we wait for couple seconds to find out whether it 0434 // will trigger a suspend (see Device::aboutToSuspend), or whether we should 0435 // turn off the screen 0436 qCDebug(KSCREEN_KDED) << "Lid closed, waiting to see if the computer goes to sleep..."; 0437 m_lidClosedTimer->start(); 0438 return; 0439 } else { 0440 qCDebug(KSCREEN_KDED) << "Lid opened!"; 0441 // We should have a config with "_lidOpened" suffix lying around. If not, 0442 // then the configuration has changed while the lid was closed and we just 0443 // use applyConfig() and see what we can do ... 0444 if (auto openCfg = m_monitoredConfig->readOpenLidFile()) { 0445 doApplyConfig(std::move(openCfg)); 0446 } 0447 } 0448 } 0449 0450 void KScreenDaemon::disableLidOutput() 0451 { 0452 // Make sure nothing has changed in the past second... :-) 0453 if (!Device::self()->isLidClosed()) { 0454 return; 0455 } 0456 0457 // If we are here, it means that closing the lid did not result in suspend 0458 // action. 0459 // FIXME: This could be because the suspend took longer than m_lidClosedTimer 0460 // timeout. Ideally we need to be able to look into PowerDevil config to see 0461 // what's the configured action for lid events, but there's no API to do that 0462 // and I'm not parsing PowerDevil's configs... 0463 0464 qCDebug(KSCREEN_KDED) << "Lid closed, finding lid to disable"; 0465 for (KScreen::OutputPtr &output : m_monitoredConfig->data()->outputs()) { 0466 if (output->type() == KScreen::Output::Panel) { 0467 if (output->isConnected() && output->isEnabled()) { 0468 // Save the current config with opened lid, just so that we know 0469 // how to restore it later 0470 m_monitoredConfig->writeOpenLidFile(); 0471 disableOutput(output); 0472 refreshConfig(); 0473 return; 0474 } 0475 } 0476 } 0477 } 0478 0479 void KScreenDaemon::outputConnectedChanged() 0480 { 0481 if (!m_changeCompressor->isActive()) { 0482 m_changeCompressor->start(); 0483 } 0484 0485 KScreen::Output *output = qobject_cast<KScreen::Output *>(sender()); 0486 qCDebug(KSCREEN_KDED) << "outputConnectedChanged():" << output->name(); 0487 } 0488 0489 void KScreenDaemon::outputAddedSlot(const KScreen::OutputPtr &output) 0490 { 0491 if (output->isConnected()) { 0492 m_changeCompressor->start(); 0493 } 0494 connect(output.data(), &KScreen::Output::isConnectedChanged, this, &KScreenDaemon::outputConnectedChanged, Qt::UniqueConnection); 0495 } 0496 0497 void KScreenDaemon::monitorConnectedChange() 0498 { 0499 const KScreen::OutputList outputs = m_monitoredConfig->data()->outputs(); 0500 for (const KScreen::OutputPtr &output : outputs) { 0501 connect(output.data(), &KScreen::Output::isConnectedChanged, this, &KScreenDaemon::outputConnectedChanged, Qt::UniqueConnection); 0502 } 0503 connect(m_monitoredConfig->data().data(), &KScreen::Config::outputAdded, this, &KScreenDaemon::outputAddedSlot, Qt::UniqueConnection); 0504 connect(m_monitoredConfig->data().data(), 0505 &KScreen::Config::outputRemoved, 0506 this, 0507 &KScreenDaemon::applyConfig, 0508 static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::UniqueConnection)); 0509 } 0510 0511 void KScreenDaemon::setMonitorForChanges(bool enabled) 0512 { 0513 if (m_monitoring == enabled) { 0514 return; 0515 } 0516 0517 qCDebug(KSCREEN_KDED) << "Monitor for changes: " << enabled; 0518 m_monitoring = enabled; 0519 if (m_monitoring) { 0520 connect(KScreen::ConfigMonitor::instance(), &KScreen::ConfigMonitor::configurationChanged, this, &KScreenDaemon::configChanged, Qt::UniqueConnection); 0521 } else { 0522 disconnect(KScreen::ConfigMonitor::instance(), &KScreen::ConfigMonitor::configurationChanged, this, &KScreenDaemon::configChanged); 0523 } 0524 } 0525 0526 void KScreenDaemon::disableOutput(const KScreen::OutputPtr &output) 0527 { 0528 const QRect geom = output->geometry(); 0529 qCDebug(KSCREEN_KDED) << "Laptop geometry:" << geom << output->pos() << (output->currentMode() ? output->currentMode()->size() : QSize()); 0530 0531 // Move all outputs right from the @p output to left 0532 for (KScreen::OutputPtr &otherOutput : m_monitoredConfig->data()->outputs()) { 0533 if (otherOutput == output || !otherOutput->isConnected() || !otherOutput->isEnabled()) { 0534 continue; 0535 } 0536 0537 QPoint otherPos = otherOutput->pos(); 0538 if (otherPos.x() >= geom.right() && otherPos.y() >= geom.top() && otherPos.y() <= geom.bottom()) { 0539 otherPos.setX(otherPos.x() - geom.width()); 0540 } 0541 qCDebug(KSCREEN_KDED) << "Moving" << otherOutput->name() << "from" << otherOutput->pos() << "to" << otherPos; 0542 otherOutput->setPos(otherPos); 0543 } 0544 0545 // Disable the output 0546 output->setEnabled(false); 0547 } 0548 0549 #include "daemon.moc"