File indexing completed on 2024-04-28 16:45:08

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 "../common/orientation_sensor.h"
0013 #include "config.h"
0014 #include "device.h"
0015 #include "generator.h"
0016 #include "kscreen_daemon_debug.h"
0017 #include "kscreenadaptor.h"
0018 #include "osdservice_interface.h"
0019 
0020 #include <kscreen/configmonitor.h>
0021 #include <kscreen/getconfigoperation.h>
0022 #include <kscreen/log.h>
0023 #include <kscreen/mode.h>
0024 #include <kscreen/output.h>
0025 #include <kscreen/screen.h>
0026 #include <kscreen/setconfigoperation.h>
0027 #include <kscreendpms/dpms.h>
0028 
0029 #include <KActionCollection>
0030 #include <KGlobalAccel>
0031 #include <KLocalizedString>
0032 #include <KPluginFactory>
0033 
0034 #include <QAction>
0035 #include <QGuiApplication>
0036 #include <QOrientationReading>
0037 #include <QScreen>
0038 #include <QShortcut>
0039 #include <QTimer>
0040 
0041 #if HAVE_X11
0042 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0043 #include <QX11Info>
0044 #else
0045 #include <private/qtx11extras_p.h>
0046 #endif
0047 #include <X11/Xatom.h>
0048 #include <X11/Xlib-xcb.h>
0049 #include <X11/extensions/XInput.h>
0050 #include <X11/extensions/XInput2.h>
0051 #endif
0052 
0053 K_PLUGIN_CLASS_WITH_JSON(KScreenDaemon, "kscreen.json")
0054 
0055 #if HAVE_X11
0056 struct DeviceListDeleter {
0057     void operator()(XDeviceInfo *p)
0058     {
0059         if (p) {
0060             XFreeDeviceList(p);
0061         }
0062     }
0063 };
0064 
0065 struct XDeleter {
0066     void operator()(void *p)
0067     {
0068         if (p) {
0069             XFree(p);
0070         }
0071     }
0072 };
0073 #endif
0074 
0075 KScreenDaemon::KScreenDaemon(QObject *parent, const QList<QVariant> &)
0076     : KDEDModule(parent)
0077     , m_monitoring(false)
0078     , m_changeCompressor(new QTimer(this))
0079     , m_saveTimer(nullptr)
0080     , m_lidClosedTimer(new QTimer(this))
0081     , m_orientationSensor(new OrientationSensor(this))
0082 {
0083     connect(m_orientationSensor, &OrientationSensor::availableChanged, this, &KScreenDaemon::updateOrientation);
0084     connect(m_orientationSensor, &OrientationSensor::valueChanged, this, &KScreenDaemon::updateOrientation);
0085 
0086     KScreen::Log::instance();
0087     qMetaTypeId<KScreen::OsdAction>();
0088     QMetaObject::invokeMethod(this, "getInitialConfig", Qt::QueuedConnection);
0089 
0090     auto dpms = new KScreen::Dpms(this);
0091     connect(dpms, &KScreen::Dpms::modeChanged, this, [this](KScreen::Dpms::Mode mode, QScreen *screen) {
0092         if (m_monitoredConfig && m_monitoredConfig->data() && screen->geometry() == m_monitoredConfig->data()->primaryOutput()->geometry()) {
0093             if (mode == KScreen::Dpms::On) {
0094                 m_orientationSensor->setEnabled(m_monitoredConfig->autoRotationRequested());
0095             } else {
0096                 m_orientationSensor->setEnabled(false);
0097             }
0098         }
0099     });
0100 }
0101 
0102 void KScreenDaemon::getInitialConfig()
0103 {
0104     connect(new KScreen::GetConfigOperation, &KScreen::GetConfigOperation::finished, this, [this](KScreen::ConfigOperation *op) {
0105         if (op->hasError()) {
0106             qCDebug(KSCREEN_KDED) << "Error getting initial configuration" << op->errorString();
0107             return;
0108         }
0109 
0110         m_monitoredConfig = std::unique_ptr<Config>(new Config(qobject_cast<KScreen::GetConfigOperation *>(op)->config()));
0111         m_monitoredConfig->setValidityFlags(KScreen::Config::ValidityFlag::RequireAtLeastOneEnabledScreen);
0112         qCDebug(KSCREEN_KDED) << "Config" << m_monitoredConfig->data().data() << "is ready";
0113         KScreen::ConfigMonitor::instance()->addConfig(m_monitoredConfig->data());
0114 
0115         init();
0116     });
0117 }
0118 
0119 KScreenDaemon::~KScreenDaemon()
0120 {
0121     Generator::destroy();
0122     Device::destroy();
0123 }
0124 
0125 void KScreenDaemon::init()
0126 {
0127     KActionCollection *coll = new KActionCollection(this);
0128     QAction *action = coll->addAction(QStringLiteral("display"));
0129     action->setText(i18n("Switch Display"));
0130     QList<QKeySequence> switchDisplayShortcuts({Qt::Key_Display, Qt::MetaModifier + Qt::Key_P});
0131     KGlobalAccel::self()->setGlobalShortcut(action, switchDisplayShortcuts);
0132     connect(action, &QAction::triggered, this, &KScreenDaemon::displayButton);
0133 
0134     new KScreenAdaptor(this);
0135 
0136     const QString osdService = QStringLiteral("org.kde.kscreen.osdService");
0137     const QString osdPath = QStringLiteral("/org/kde/kscreen/osdService");
0138     m_osdServiceInterface = new OrgKdeKscreenOsdServiceInterface(osdService, osdPath, QDBusConnection::sessionBus(), this);
0139     // Set a longer timeout to not assume timeout while the osd is still shown
0140     m_osdServiceInterface->setTimeout(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::seconds(60)).count());
0141 
0142     m_changeCompressor->setInterval(10);
0143     m_changeCompressor->setSingleShot(true);
0144     connect(m_changeCompressor, &QTimer::timeout, this, &KScreenDaemon::applyConfig);
0145 
0146     m_lidClosedTimer->setInterval(1000);
0147     m_lidClosedTimer->setSingleShot(true);
0148     connect(m_lidClosedTimer, &QTimer::timeout, this, &KScreenDaemon::disableLidOutput);
0149 
0150     connect(Device::self(), &Device::lidClosedChanged, this, &KScreenDaemon::lidClosedChanged);
0151     connect(Device::self(), &Device::resumingFromSuspend, this, [this]() {
0152         KScreen::Log::instance()->setContext(QStringLiteral("resuming"));
0153         m_orientationSensor->setEnabled(m_monitoredConfig->autoRotationRequested());
0154         qCDebug(KSCREEN_KDED) << "Resumed from suspend, checking for screen changes";
0155         // We don't care about the result, we just want to force the backend
0156         // to query XRandR so that it will detect possible changes that happened
0157         // while the computer was suspended, and will emit the change events.
0158         new KScreen::GetConfigOperation(KScreen::GetConfigOperation::NoEDID, this);
0159     });
0160     connect(Device::self(), &Device::aboutToSuspend, this, [this]() {
0161         qCDebug(KSCREEN_KDED) << "System is going to suspend, won't be changing config (waited for "
0162                               << (m_lidClosedTimer->interval() - m_lidClosedTimer->remainingTime()) << "ms)";
0163         m_lidClosedTimer->stop();
0164         m_orientationSensor->setEnabled(false);
0165     });
0166 
0167     connect(Generator::self(), &Generator::ready, this, [this] {
0168         applyConfig();
0169 
0170         if (Device::self()->isLaptop() && Device::self()->isLidClosed()) {
0171             disableLidOutput();
0172         }
0173 
0174         m_startingUp = false;
0175     });
0176 
0177     Generator::self()->setCurrentConfig(m_monitoredConfig->data());
0178     monitorConnectedChange();
0179 }
0180 
0181 void KScreenDaemon::updateOrientation()
0182 {
0183     if (!m_monitoredConfig) {
0184         return;
0185     }
0186     const auto features = m_monitoredConfig->data()->supportedFeatures();
0187     if (!features.testFlag(KScreen::Config::Feature::AutoRotation) || !features.testFlag(KScreen::Config::Feature::TabletMode)) {
0188         return;
0189     }
0190 
0191     if (!m_orientationSensor->available() || !m_orientationSensor->enabled()) {
0192         return;
0193     }
0194 
0195     const auto orientation = m_orientationSensor->value();
0196     if (orientation == QOrientationReading::Undefined) {
0197         // Orientation sensor went off. Do not change current orientation.
0198         return;
0199     }
0200     if (orientation == QOrientationReading::FaceUp || orientation == QOrientationReading::FaceDown) {
0201         // We currently don't do anything with FaceUp/FaceDown, but in the future we could use them
0202         // to shut off and switch on again a display when display is facing downwards/upwards.
0203         return;
0204     }
0205 
0206     m_monitoredConfig->setDeviceOrientation(orientation);
0207     if (m_monitoring) {
0208         doApplyConfig(m_monitoredConfig->data());
0209     } else {
0210         m_configDirty = true;
0211     }
0212 }
0213 
0214 void KScreenDaemon::doApplyConfig(const KScreen::ConfigPtr &config)
0215 {
0216     qCDebug(KSCREEN_KDED) << "Do set and apply specific config";
0217     auto configWrapper = std::unique_ptr<Config>(new Config(config));
0218     configWrapper->setValidityFlags(KScreen::Config::ValidityFlag::RequireAtLeastOneEnabledScreen);
0219 
0220     doApplyConfig(std::move(configWrapper));
0221 }
0222 
0223 void KScreenDaemon::doApplyConfig(std::unique_ptr<Config> config)
0224 {
0225     m_monitoredConfig = std::move(config);
0226 
0227     m_monitoredConfig->activateControlWatching();
0228     m_orientationSensor->setEnabled(m_monitoredConfig->autoRotationRequested());
0229 
0230     connect(m_monitoredConfig.get(), &Config::controlChanged, this, [this]() {
0231         m_orientationSensor->setEnabled(m_monitoredConfig->autoRotationRequested());
0232         updateOrientation();
0233     });
0234 
0235     refreshConfig();
0236 }
0237 
0238 void KScreenDaemon::refreshConfig()
0239 {
0240     setMonitorForChanges(false);
0241     m_configDirty = false;
0242     KScreen::ConfigMonitor::instance()->addConfig(m_monitoredConfig->data());
0243 
0244     connect(new KScreen::SetConfigOperation(m_monitoredConfig->data()), &KScreen::SetConfigOperation::finished, this, [this]() {
0245         qCDebug(KSCREEN_KDED) << "Config applied";
0246         if (m_configDirty) {
0247             // Config changed in the meantime again, apply.
0248             doApplyConfig(m_monitoredConfig->data());
0249         } else {
0250             setMonitorForChanges(true);
0251         }
0252     });
0253 }
0254 
0255 void KScreenDaemon::applyConfig()
0256 {
0257     qCDebug(KSCREEN_KDED) << "Applying config";
0258     if (m_monitoredConfig->fileExists()) {
0259         applyKnownConfig();
0260         return;
0261     }
0262     applyIdealConfig();
0263 }
0264 
0265 void KScreenDaemon::applyKnownConfig()
0266 {
0267     qCDebug(KSCREEN_KDED) << "Applying known config";
0268 
0269     std::unique_ptr<Config> readInConfig = m_monitoredConfig->readFile();
0270     if (readInConfig) {
0271         doApplyConfig(std::move(readInConfig));
0272     } else {
0273         qCDebug(KSCREEN_KDED) << "Loading failed, falling back to the ideal config" << m_monitoredConfig->id();
0274         applyIdealConfig();
0275     }
0276 }
0277 
0278 void KScreenDaemon::applyLayoutPreset(const QString &presetName)
0279 {
0280     const QMetaEnum actionEnum = QMetaEnum::fromType<KScreen::OsdAction::Action>();
0281     Q_ASSERT(actionEnum.isValid());
0282 
0283     bool ok;
0284     auto action = static_cast<KScreen::OsdAction::Action>(actionEnum.keyToValue(qPrintable(presetName), &ok));
0285     if (!ok) {
0286         qCWarning(KSCREEN_KDED) << "Cannot apply unknown screen layout preset named" << presetName;
0287         return;
0288     }
0289     applyOsdAction(action);
0290 }
0291 
0292 bool KScreenDaemon::getAutoRotate()
0293 {
0294     return m_monitoredConfig->getAutoRotate();
0295 }
0296 
0297 void KScreenDaemon::setAutoRotate(bool value)
0298 {
0299     if (!m_monitoredConfig) {
0300         return;
0301     }
0302     m_monitoredConfig->setAutoRotate(value);
0303     m_orientationSensor->setEnabled(value);
0304 }
0305 
0306 bool KScreenDaemon::isAutoRotateAvailable()
0307 {
0308     return m_orientationSensor->available();
0309 }
0310 
0311 void KScreenDaemon::showOSD()
0312 {
0313     auto call = m_osdServiceInterface->showActionSelector();
0314     auto watcher = new QDBusPendingCallWatcher(call);
0315     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, watcher] {
0316         watcher->deleteLater();
0317         QDBusReply<int> reply = *watcher;
0318         if (!reply.isValid()) {
0319             return;
0320         }
0321         applyOsdAction(static_cast<KScreen::OsdAction::Action>(reply.value()));
0322     });
0323 }
0324 
0325 void KScreenDaemon::applyOsdAction(KScreen::OsdAction::Action action)
0326 {
0327     switch (action) {
0328     case KScreen::OsdAction::NoAction:
0329         qCDebug(KSCREEN_KDED) << "OSD: no action";
0330         return;
0331     case KScreen::OsdAction::SwitchToInternal:
0332         qCDebug(KSCREEN_KDED) << "OSD: switch to internal";
0333         doApplyConfig(Generator::self()->displaySwitch(Generator::TurnOffExternal));
0334         return;
0335     case KScreen::OsdAction::SwitchToExternal:
0336         qCDebug(KSCREEN_KDED) << "OSD: switch to external";
0337         doApplyConfig(Generator::self()->displaySwitch(Generator::TurnOffEmbedded));
0338         return;
0339     case KScreen::OsdAction::ExtendLeft:
0340         qCDebug(KSCREEN_KDED) << "OSD: extend left";
0341         doApplyConfig(Generator::self()->displaySwitch(Generator::ExtendToLeft));
0342         return;
0343     case KScreen::OsdAction::ExtendRight:
0344         qCDebug(KSCREEN_KDED) << "OSD: extend right";
0345         doApplyConfig(Generator::self()->displaySwitch(Generator::ExtendToRight));
0346         return;
0347     case KScreen::OsdAction::Clone:
0348         qCDebug(KSCREEN_KDED) << "OSD: clone";
0349         doApplyConfig(Generator::self()->displaySwitch(Generator::Clone));
0350         return;
0351     }
0352     Q_UNREACHABLE();
0353 }
0354 
0355 void KScreenDaemon::applyIdealConfig()
0356 {
0357     const bool showOsd = m_monitoredConfig->data()->connectedOutputs().count() > 1 && !m_startingUp;
0358 
0359     doApplyConfig(Generator::self()->idealConfig(m_monitoredConfig->data()));
0360 
0361     if (showOsd) {
0362         qCDebug(KSCREEN_KDED) << "Getting ideal config from user via OSD...";
0363         showOSD();
0364     } else {
0365         m_osdServiceInterface->hideOsd();
0366     }
0367 }
0368 
0369 void KScreenDaemon::configChanged()
0370 {
0371     qCDebug(KSCREEN_KDED) << "Change detected";
0372     m_monitoredConfig->log();
0373 
0374     // Modes may have changed, fix-up current mode id
0375     bool changed = false;
0376     const auto outputs = m_monitoredConfig->data()->outputs();
0377     for (const KScreen::OutputPtr &output : outputs) {
0378         if (output->isConnected() && output->isEnabled()
0379             && (output->currentMode().isNull() || (output->followPreferredMode() && output->currentModeId() != output->preferredModeId()))) {
0380             qCDebug(KSCREEN_KDED) << "Current mode was" << output->currentModeId() << ", setting preferred mode" << output->preferredModeId();
0381             output->setCurrentModeId(output->preferredModeId());
0382             changed = true;
0383         }
0384     }
0385     if (changed) {
0386         refreshConfig();
0387     }
0388 
0389     // Reset timer, delay the writeback
0390     if (!m_saveTimer) {
0391         m_saveTimer = new QTimer(this);
0392         m_saveTimer->setInterval(300);
0393         m_saveTimer->setSingleShot(true);
0394         connect(m_saveTimer, &QTimer::timeout, this, &KScreenDaemon::saveCurrentConfig);
0395     }
0396     m_saveTimer->start();
0397 #if HAVE_X11
0398     alignX11TouchScreen();
0399 #endif
0400 }
0401 
0402 #if HAVE_X11
0403 void KScreenDaemon::alignX11TouchScreen()
0404 {
0405     if (qGuiApp->platformName() != QStringLiteral("xcb")) {
0406         return;
0407     }
0408     auto *display = QX11Info::display();
0409     if (!display) {
0410         return;
0411     }
0412     auto *connection = QX11Info::connection();
0413     if (!connection) {
0414         return;
0415     }
0416 
0417     const QRect totalRect(QPoint(0, 0), m_monitoredConfig->data()->screen()->currentSize());
0418     QRect internalOutputRect;
0419     int touchScreenRotationAngle = 0;
0420 
0421     for (const auto &output : m_monitoredConfig->data()->connectedOutputs()) {
0422         if (output->isEnabled() && output->type() == KScreen::Output::Panel) {
0423             internalOutputRect = output->geometry();
0424 
0425             switch (output->rotation()) {
0426             case KScreen::Output::Left:
0427                 touchScreenRotationAngle = 90;
0428                 break;
0429             case KScreen::Output::Right:
0430                 touchScreenRotationAngle = 270;
0431                 break;
0432             case KScreen::Output::Inverted:
0433                 touchScreenRotationAngle = 180;
0434                 break;
0435             default:
0436                 touchScreenRotationAngle = 0;
0437             }
0438         }
0439     }
0440 
0441     // Compute the transformation matrix for the
0442     QTransform transform;
0443     transform = transform.translate(float(internalOutputRect.x()) / float(totalRect.width()), float(internalOutputRect.y()) / float(totalRect.height()));
0444     transform = transform.scale(float(internalOutputRect.width()) / float(totalRect.width()), float(internalOutputRect.height()) / float(totalRect.height()));
0445     transform = transform.rotate(touchScreenRotationAngle);
0446 
0447     // After rotation we need to make the matrix origin aligned with the workspace again
0448     // ____                                                      ___
0449     // |__|  -> 90° clockwise -> ___  -> needs to be moved up -> | |
0450     //                           | |                             |_|
0451     //                           |_|
0452     switch (touchScreenRotationAngle) {
0453     case 90:
0454         transform = transform.translate(0, -1);
0455         break;
0456     case 270:
0457         transform = transform.translate(-1, 0);
0458         break;
0459     case 180:
0460         transform = transform.translate(-1, -1);
0461         break;
0462     default:
0463         break;
0464     }
0465 
0466     auto getAtom = [](xcb_connection_t *connection, const char *name) {
0467         auto cookie = xcb_intern_atom(connection, true, strlen(name), name);
0468         auto reply = xcb_intern_atom_reply(connection, cookie, nullptr);
0469         if (reply) {
0470             return reply->atom;
0471         } else {
0472             return xcb_atom_t(0);
0473         }
0474     };
0475 
0476     int nDevices = 0;
0477     std::unique_ptr<XDeviceInfo, DeviceListDeleter> deviceInfo(XListInputDevices(display, &nDevices));
0478     auto touchScreenAtom = getAtom(connection, XI_TOUCHSCREEN);
0479     if (touchScreenAtom == 0) {
0480         return;
0481     }
0482     auto matrixAtom = getAtom(connection, "Coordinate Transformation Matrix");
0483     if (matrixAtom == 0) {
0484         return;
0485     }
0486     auto floatAtom = getAtom(connection, "FLOAT");
0487     if (floatAtom == 0) {
0488         return;
0489     }
0490 
0491     auto setMatrixAtom = [display, floatAtom](XDeviceInfo *info, Atom atom, const QTransform &transform) {
0492         Atom type;
0493         int format = 0;
0494         unsigned long nItems, bytesAfter;
0495         unsigned char *dataPtr = nullptr;
0496 
0497         std::unique_ptr<unsigned char, XDeleter> data(dataPtr);
0498         XIGetProperty(display, info->id, atom, 0, 1000, False, AnyPropertyType, &type, &format, &nItems, &bytesAfter, &dataPtr);
0499 
0500         if (nItems != 9) {
0501             return;
0502         }
0503         if (format != sizeof(float) * CHAR_BIT || type != floatAtom) {
0504             return;
0505         }
0506 
0507         float *fData = reinterpret_cast<float *>(dataPtr);
0508 
0509         fData[0] = transform.m11();
0510         fData[1] = transform.m21();
0511         fData[2] = transform.m31();
0512 
0513         fData[3] = transform.m12();
0514         fData[4] = transform.m22();
0515         fData[5] = transform.m32();
0516 
0517         fData[6] = transform.m13();
0518         fData[7] = transform.m23();
0519         fData[8] = transform.m33();
0520 
0521         XIChangeProperty(display, info->id, atom, type, format, PropModeReplace, dataPtr, nItems);
0522     };
0523 
0524     for (XDeviceInfo *info = deviceInfo.get(); info < deviceInfo.get() + nDevices; info++) {
0525         // Make sure device is touchscreen
0526         if (info->type != touchScreenAtom) {
0527             continue;
0528         }
0529 
0530         int nProperties = 0;
0531         std::unique_ptr<Atom, XDeleter> properties(XIListProperties(display, info->id, &nProperties));
0532 
0533         bool matrixAtomFound = false;
0534 
0535         Atom *atom = properties.get();
0536         Atom *atomEnd = properties.get() + nProperties;
0537         for (; atom != atomEnd; atom++) {
0538             if (!internalOutputRect.isEmpty() && *atom == matrixAtom) {
0539                 matrixAtomFound = true;
0540             }
0541         }
0542 
0543         if (matrixAtomFound) {
0544             setMatrixAtom(info, matrixAtom, transform);
0545         }
0546 
0547         // For now we assume there is only one touchscreen
0548         XFlush(display);
0549         break;
0550     }
0551 }
0552 #endif
0553 
0554 void KScreenDaemon::saveCurrentConfig()
0555 {
0556     qCDebug(KSCREEN_KDED) << "Saving current config to file";
0557 
0558     // We assume the config is valid, since it's what we got, but we are interested
0559     // in the "at least one enabled screen" check
0560 
0561     if (m_monitoredConfig->canBeApplied()) {
0562         m_monitoredConfig->writeFile();
0563         m_monitoredConfig->log();
0564     } else {
0565         qCWarning(KSCREEN_KDED) << "Config does not have at least one screen enabled, WILL NOT save this config, this is not what user wants.";
0566         m_monitoredConfig->log();
0567     }
0568 }
0569 
0570 void KScreenDaemon::displayButton()
0571 {
0572     qCDebug(KSCREEN_KDED) << "displayBtn triggered";
0573     showOSD();
0574 }
0575 
0576 void KScreenDaemon::lidClosedChanged(bool lidIsClosed)
0577 {
0578     // Ignore this when we don't have any external monitors, we can't turn off our
0579     // only screen
0580     if (m_monitoredConfig->data()->connectedOutputs().count() == 1) {
0581         return;
0582     }
0583 
0584     if (lidIsClosed) {
0585         // Lid is closed, now we wait for couple seconds to find out whether it
0586         // will trigger a suspend (see Device::aboutToSuspend), or whether we should
0587         // turn off the screen
0588         qCDebug(KSCREEN_KDED) << "Lid closed, waiting to see if the computer goes to sleep...";
0589         m_lidClosedTimer->start();
0590         return;
0591     } else {
0592         qCDebug(KSCREEN_KDED) << "Lid opened!";
0593         // We should have a config with "_lidOpened" suffix lying around. If not,
0594         // then the configuration has changed while the lid was closed and we just
0595         // use applyConfig() and see what we can do ...
0596         if (auto openCfg = m_monitoredConfig->readOpenLidFile()) {
0597             doApplyConfig(std::move(openCfg));
0598         }
0599     }
0600 }
0601 
0602 void KScreenDaemon::disableLidOutput()
0603 {
0604     // Make sure nothing has changed in the past second... :-)
0605     if (!Device::self()->isLidClosed()) {
0606         return;
0607     }
0608 
0609     // If we are here, it means that closing the lid did not result in suspend
0610     // action.
0611     // FIXME: This could be because the suspend took longer than m_lidClosedTimer
0612     // timeout. Ideally we need to be able to look into PowerDevil config to see
0613     // what's the configured action for lid events, but there's no API to do that
0614     // and I'm not parsing PowerDevil's configs...
0615 
0616     qCDebug(KSCREEN_KDED) << "Lid closed, finding lid to disable";
0617     for (KScreen::OutputPtr &output : m_monitoredConfig->data()->outputs()) {
0618         if (output->type() == KScreen::Output::Panel) {
0619             if (output->isConnected() && output->isEnabled()) {
0620                 // Save the current config with opened lid, just so that we know
0621                 // how to restore it later
0622                 m_monitoredConfig->writeOpenLidFile();
0623                 disableOutput(output);
0624                 refreshConfig();
0625                 return;
0626             }
0627         }
0628     }
0629 }
0630 
0631 void KScreenDaemon::outputConnectedChanged()
0632 {
0633     if (!m_changeCompressor->isActive()) {
0634         m_changeCompressor->start();
0635     }
0636 
0637     KScreen::Output *output = qobject_cast<KScreen::Output *>(sender());
0638     qCDebug(KSCREEN_KDED) << "outputConnectedChanged():" << output->name();
0639 
0640     if (output->isConnected()) {
0641         Q_EMIT outputConnected(output->name());
0642 
0643         if (!m_monitoredConfig->fileExists()) {
0644             Q_EMIT unknownOutputConnected(output->name());
0645         }
0646     }
0647 }
0648 
0649 void KScreenDaemon::monitorConnectedChange()
0650 {
0651     const KScreen::OutputList outputs = m_monitoredConfig->data()->outputs();
0652     for (const KScreen::OutputPtr &output : outputs) {
0653         connect(output.data(), &KScreen::Output::isConnectedChanged, this, &KScreenDaemon::outputConnectedChanged, Qt::UniqueConnection);
0654     }
0655     connect(
0656         m_monitoredConfig->data().data(),
0657         &KScreen::Config::outputAdded,
0658         this,
0659         [this](const KScreen::OutputPtr &output) {
0660             if (output->isConnected()) {
0661                 m_changeCompressor->start();
0662             }
0663             connect(output.data(), &KScreen::Output::isConnectedChanged, this, &KScreenDaemon::outputConnectedChanged, Qt::UniqueConnection);
0664         },
0665         Qt::UniqueConnection);
0666     connect(m_monitoredConfig->data().data(),
0667             &KScreen::Config::outputRemoved,
0668             this,
0669             &KScreenDaemon::applyConfig,
0670             static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::UniqueConnection));
0671 }
0672 
0673 void KScreenDaemon::setMonitorForChanges(bool enabled)
0674 {
0675     if (m_monitoring == enabled) {
0676         return;
0677     }
0678 
0679     qCDebug(KSCREEN_KDED) << "Monitor for changes: " << enabled;
0680     m_monitoring = enabled;
0681     if (m_monitoring) {
0682         connect(KScreen::ConfigMonitor::instance(), &KScreen::ConfigMonitor::configurationChanged, this, &KScreenDaemon::configChanged, Qt::UniqueConnection);
0683     } else {
0684         disconnect(KScreen::ConfigMonitor::instance(), &KScreen::ConfigMonitor::configurationChanged, this, &KScreenDaemon::configChanged);
0685     }
0686 }
0687 
0688 void KScreenDaemon::disableOutput(const KScreen::OutputPtr &output)
0689 {
0690     const QRect geom = output->geometry();
0691     qCDebug(KSCREEN_KDED) << "Laptop geometry:" << geom << output->pos() << (output->currentMode() ? output->currentMode()->size() : QSize());
0692 
0693     // Move all outputs right from the @p output to left
0694     for (KScreen::OutputPtr &otherOutput : m_monitoredConfig->data()->outputs()) {
0695         if (otherOutput == output || !otherOutput->isConnected() || !otherOutput->isEnabled()) {
0696             continue;
0697         }
0698 
0699         QPoint otherPos = otherOutput->pos();
0700         if (otherPos.x() >= geom.right() && otherPos.y() >= geom.top() && otherPos.y() <= geom.bottom()) {
0701             otherPos.setX(otherPos.x() - geom.width());
0702         }
0703         qCDebug(KSCREEN_KDED) << "Moving" << otherOutput->name() << "from" << otherOutput->pos() << "to" << otherPos;
0704         otherOutput->setPos(otherPos);
0705     }
0706 
0707     // Disable the output
0708     output->setEnabled(false);
0709 }
0710 
0711 #include "daemon.moc"