diff --git a/kded/daemon.cpp b/kded/daemon.cpp index 56f8f38..f535f70 100644 --- a/kded/daemon.cpp +++ b/kded/daemon.cpp @@ -1,441 +1,446 @@ /************************************************************************************* * Copyright (C) 2012 by Alejandro Fiestas Olivares * * Copyright 2016 by Sebastian Kügler * * Copyright (c) 2018 Kai Uwe Broulik * * Work sponsored by the LiMux project of * * the city of Munich. * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #include "daemon.h" #include "config.h" #include "generator.h" #include "device.h" #include "kscreenadaptor.h" #include "kscreen_daemon_debug.h" #include "osdmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_CLASS_WITH_JSON(KScreenDaemon, "kscreen.json") KScreenDaemon::KScreenDaemon(QObject* parent, const QList< QVariant >& ) : KDEDModule(parent) , m_monitoring(false) , m_changeCompressor(new QTimer(this)) , m_saveTimer(nullptr) , m_lidClosedTimer(new QTimer(this)) { KScreen::Log::instance(); QMetaObject::invokeMethod(this, "getInitialConfig", Qt::QueuedConnection); } void KScreenDaemon::getInitialConfig() { connect(new KScreen::GetConfigOperation, &KScreen::GetConfigOperation::finished, this, [this](KScreen::ConfigOperation* op) { if (op->hasError()) { return; } m_monitoredConfig = std::unique_ptr(new Config(qobject_cast(op)->config())); m_monitoredConfig->setValidityFlags(KScreen::Config::ValidityFlag::RequireAtLeastOneEnabledScreen); qCDebug(KSCREEN_KDED) << "Config" << m_monitoredConfig->data().data() << "is ready"; KScreen::ConfigMonitor::instance()->addConfig(m_monitoredConfig->data()); init(); }); } KScreenDaemon::~KScreenDaemon() { Generator::destroy(); Device::destroy(); } void KScreenDaemon::init() { KActionCollection *coll = new KActionCollection(this); QAction* action = coll->addAction(QStringLiteral("display")); action->setText(i18n("Switch Display" )); QList switchDisplayShortcuts({Qt::Key_Display, Qt::MetaModifier + Qt::Key_P}); KGlobalAccel::self()->setGlobalShortcut(action, switchDisplayShortcuts); connect(action, &QAction::triggered, this, &KScreenDaemon::displayButton); new KScreenAdaptor(this); // Initialize OSD manager to register its dbus interface m_osdManager = new KScreen::OsdManager(this); m_changeCompressor->setInterval(10); m_changeCompressor->setSingleShot(true); connect(m_changeCompressor, &QTimer::timeout, this, &KScreenDaemon::applyConfig); m_lidClosedTimer->setInterval(1000); m_lidClosedTimer->setSingleShot(true); connect(m_lidClosedTimer, &QTimer::timeout, this, &KScreenDaemon::lidClosedTimeout); connect(Device::self(), &Device::lidClosedChanged, this, &KScreenDaemon::lidClosedChanged); connect(Device::self(), &Device::resumingFromSuspend, this, [&]() { KScreen::Log::instance()->setContext(QStringLiteral("resuming")); qCDebug(KSCREEN_KDED) << "Resumed from suspend, checking for screen changes"; // We don't care about the result, we just want to force the backend // to query XRandR so that it will detect possible changes that happened // while the computer was suspended, and will emit the change events. new KScreen::GetConfigOperation(KScreen::GetConfigOperation::NoEDID, this); }); connect(Device::self(), &Device::aboutToSuspend, this, [&]() { qCDebug(KSCREEN_KDED) << "System is going to suspend, won't be changing config (waited for " << (m_lidClosedTimer->interval() - m_lidClosedTimer->remainingTime()) << "ms)"; m_lidClosedTimer->stop(); }); - connect(Generator::self(), &Generator::ready, - this, &KScreenDaemon::applyConfig); + connect(Generator::self(), &Generator::ready, this, [this] { + applyConfig(); + m_startingUp = false; + }); Generator::self()->setCurrentConfig(m_monitoredConfig->data()); monitorConnectedChange(); } void KScreenDaemon::doApplyConfig(const KScreen::ConfigPtr& config) { qCDebug(KSCREEN_KDED) << "Do set and apply specific config"; auto configWrapper = std::unique_ptr(new Config(config)); configWrapper->setValidityFlags(KScreen::Config::ValidityFlag::RequireAtLeastOneEnabledScreen); doApplyConfig(std::move(configWrapper)); } void KScreenDaemon::doApplyConfig(std::unique_ptr config) { setMonitorForChanges(false); // TODO: remove? m_monitoredConfig = std::move(config); refreshConfig(); } void KScreenDaemon::refreshConfig() { setMonitorForChanges(false); KScreen::ConfigMonitor::instance()->addConfig(m_monitoredConfig->data()); connect(new KScreen::SetConfigOperation(m_monitoredConfig->data()), &KScreen::SetConfigOperation::finished, this, [&]() { qCDebug(KSCREEN_KDED) << "Config applied"; setMonitorForChanges(true); }); } void KScreenDaemon::applyConfig() { qCDebug(KSCREEN_KDED) << "Applying config"; if (m_monitoredConfig->fileExists()) { applyKnownConfig(); return; } applyIdealConfig(); } void KScreenDaemon::applyKnownConfig() { qCDebug(KSCREEN_KDED) << "Applying known config"; std::unique_ptr readInConfig = m_monitoredConfig->readFile(); if (readInConfig) { doApplyConfig(std::move(readInConfig)); } else { // loading not succesful, fall back to ideal config applyIdealConfig(); } } void KScreenDaemon::applyLayoutPreset(const QString &presetName) { const QMetaEnum actionEnum = QMetaEnum::fromType(); Q_ASSERT(actionEnum.isValid()); bool ok; auto action = static_cast(actionEnum.keyToValue(qPrintable(presetName), &ok)); if (!ok) { qCWarning(KSCREEN_KDED) << "Cannot apply unknown screen layout preset named" << presetName; return; } applyOsdAction(action); } void KScreenDaemon::applyOsdAction(KScreen::OsdAction::Action action) { switch (action) { case KScreen::OsdAction::NoAction: qCDebug(KSCREEN_KDED) << "OSD: no action"; return; case KScreen::OsdAction::SwitchToInternal: qCDebug(KSCREEN_KDED) << "OSD: switch to internal"; doApplyConfig(Generator::self()->displaySwitch(Generator::TurnOffExternal)); return; case KScreen::OsdAction::SwitchToExternal: qCDebug(KSCREEN_KDED) << "OSD: switch to external"; doApplyConfig(Generator::self()->displaySwitch(Generator::TurnOffEmbedded)); return; case KScreen::OsdAction::ExtendLeft: qCDebug(KSCREEN_KDED) << "OSD: extend left"; doApplyConfig(Generator::self()->displaySwitch(Generator::ExtendToLeft)); return; case KScreen::OsdAction::ExtendRight: qCDebug(KSCREEN_KDED) << "OSD: extend right"; doApplyConfig(Generator::self()->displaySwitch(Generator::ExtendToRight)); return; case KScreen::OsdAction::Clone: qCDebug(KSCREEN_KDED) << "OSD: clone"; doApplyConfig(Generator::self()->displaySwitch(Generator::Clone)); return; } Q_UNREACHABLE(); } void KScreenDaemon::applyIdealConfig() { - if (m_monitoredConfig->data()->connectedOutputs().count() < 2) { - m_osdManager->hideOsd(); - doApplyConfig(Generator::self()->idealConfig(m_monitoredConfig->data())); - } else { + const bool showOsd = m_monitoredConfig->data()->connectedOutputs().count() > 1 && !m_startingUp; + + doApplyConfig(Generator::self()->idealConfig(m_monitoredConfig->data())); + + if (showOsd) { qCDebug(KSCREEN_KDED) << "Getting ideal config from user via OSD..."; auto action = m_osdManager->showActionSelector(); connect(action, &KScreen::OsdAction::selected, this, &KScreenDaemon::applyOsdAction); + } else { + m_osdManager->hideOsd(); } } void KScreenDaemon::configChanged() { qCDebug(KSCREEN_KDED) << "Change detected"; m_monitoredConfig->log(); // Modes may have changed, fix-up current mode id bool changed = false; Q_FOREACH(const KScreen::OutputPtr &output, m_monitoredConfig->data()->outputs()) { if (output->isConnected() && output->isEnabled() && (output->currentMode().isNull() || (output->followPreferredMode() && output->currentModeId() != output->preferredModeId()))) { qCDebug(KSCREEN_KDED) << "Current mode was" << output->currentModeId() << ", setting preferred mode" << output->preferredModeId(); output->setCurrentModeId(output->preferredModeId()); changed = true; } } if (changed) { refreshConfig(); } // Reset timer, delay the writeback if (!m_saveTimer) { m_saveTimer = new QTimer(this); m_saveTimer->setInterval(300); m_saveTimer->setSingleShot(true); connect(m_saveTimer, &QTimer::timeout, this, &KScreenDaemon::saveCurrentConfig); } m_saveTimer->start(); } void KScreenDaemon::saveCurrentConfig() { qCDebug(KSCREEN_KDED) << "Saving current config to file"; // We assume the config is valid, since it's what we got, but we are interested // in the "at least one enabled screen" check if (m_monitoredConfig->canBeApplied()) { m_monitoredConfig->writeFile(); m_monitoredConfig->log(); } else { qCWarning(KSCREEN_KDED) << "Config does not have at least one screen enabled, WILL NOT save this config, this is not what user wants."; m_monitoredConfig->log(); } } void KScreenDaemon::showOsd(const QString &icon, const QString &text) { QDBusMessage msg = QDBusMessage::createMethodCall( QLatin1Literal("org.kde.plasmashell"), QLatin1Literal("/org/kde/osdService"), QLatin1Literal("org.kde.osdService"), QLatin1Literal("showText") ); msg << icon << text; QDBusConnection::sessionBus().asyncCall(msg); } void KScreenDaemon::showOutputIdentifier() { m_osdManager->showOutputIdentifiers(); } void KScreenDaemon::displayButton() { qCDebug(KSCREEN_KDED) << "displayBtn triggered"; auto action = m_osdManager->showActionSelector(); connect(action, &KScreen::OsdAction::selected, this, &KScreenDaemon::applyOsdAction); } void KScreenDaemon::lidClosedChanged(bool lidIsClosed) { // Ignore this when we don't have any external monitors, we can't turn off our // only screen if (m_monitoredConfig->data()->connectedOutputs().count() == 1) { return; } if (lidIsClosed) { // Lid is closed, now we wait for couple seconds to find out whether it // will trigger a suspend (see Device::aboutToSuspend), or whether we should // turn off the screen qCDebug(KSCREEN_KDED) << "Lid closed, waiting to see if the computer goes to sleep..."; m_lidClosedTimer->start(); return; } else { qCDebug(KSCREEN_KDED) << "Lid opened!"; // We should have a config with "_lidOpened" suffix lying around. If not, // then the configuration has changed while the lid was closed and we just // use applyConfig() and see what we can do ... if (auto openCfg = m_monitoredConfig->readOpenLidFile()) { doApplyConfig(std::move(openCfg)); } } } void KScreenDaemon::lidClosedTimeout() { // Make sure nothing has changed in the past second... :-) if (!Device::self()->isLidClosed()) { return; } // If we are here, it means that closing the lid did not result in suspend // action. // FIXME: This could be because the suspend took longer than m_lidClosedTimer // timeout. Ideally we need to be able to look into PowerDevil config to see // what's the configured action for lid events, but there's no API to do that // and I'm not parsing PowerDevil's configs... qCDebug(KSCREEN_KDED) << "Lid closed without system going to suspend -> turning off the screen"; for (KScreen::OutputPtr &output : m_monitoredConfig->data()->outputs()) { if (output->type() == KScreen::Output::Panel) { if (output->isConnected() && output->isEnabled()) { // Save the current config with opened lid, just so that we know // how to restore it later m_monitoredConfig->writeOpenLidFile(); disableOutput(output); refreshConfig(); return; } } } } void KScreenDaemon::outputConnectedChanged() { if (!m_changeCompressor->isActive()) { m_changeCompressor->start(); } KScreen::Output *output = qobject_cast(sender()); qCDebug(KSCREEN_KDED) << "outputConnectedChanged():" << output->name(); if (output->isConnected()) { Q_EMIT outputConnected(output->name()); if (!m_monitoredConfig->fileExists()) { Q_EMIT unknownOutputConnected(output->name()); } } } void KScreenDaemon::monitorConnectedChange() { KScreen::OutputList outputs = m_monitoredConfig->data()->outputs(); Q_FOREACH(const KScreen::OutputPtr &output, outputs) { connect(output.data(), &KScreen::Output::isConnectedChanged, this, &KScreenDaemon::outputConnectedChanged, Qt::UniqueConnection); } connect(m_monitoredConfig->data().data(), &KScreen::Config::outputAdded, this, [this] (const KScreen::OutputPtr output) { if (output->isConnected()) { m_changeCompressor->start(); } connect(output.data(), &KScreen::Output::isConnectedChanged, this, &KScreenDaemon::outputConnectedChanged, Qt::UniqueConnection); }, Qt::UniqueConnection ); connect(m_monitoredConfig->data().data(), &KScreen::Config::outputRemoved, this, &KScreenDaemon::applyConfig, static_cast(Qt::QueuedConnection | Qt::UniqueConnection)); } void KScreenDaemon::setMonitorForChanges(bool enabled) { if (m_monitoring == enabled) { return; } qCDebug(KSCREEN_KDED) << "Monitor for changes: " << enabled; m_monitoring = enabled; if (m_monitoring) { connect(KScreen::ConfigMonitor::instance(), &KScreen::ConfigMonitor::configurationChanged, this, &KScreenDaemon::configChanged, Qt::UniqueConnection); } else { disconnect(KScreen::ConfigMonitor::instance(), &KScreen::ConfigMonitor::configurationChanged, this, &KScreenDaemon::configChanged); } } void KScreenDaemon::disableOutput(KScreen::OutputPtr &output) { const QRect geom = output->geometry(); qCDebug(KSCREEN_KDED) << "Laptop geometry:" << geom << output->pos() << (output->currentMode() ? output->currentMode()->size() : QSize()); // Move all outputs right from the @p output to left for (KScreen::OutputPtr &otherOutput : m_monitoredConfig->data()->outputs()) { if (otherOutput == output || !otherOutput->isConnected() || !otherOutput->isEnabled()) { continue; } QPoint otherPos = otherOutput->pos(); if (otherPos.x() >= geom.right() && otherPos.y() >= geom.top() && otherPos.y() <= geom.bottom()) { otherPos.setX(otherPos.x() - geom.width()); } qCDebug(KSCREEN_KDED) << "Moving" << otherOutput->name() << "from" << otherOutput->pos() << "to" << otherPos; otherOutput->setPos(otherPos); } // Disable the output output->setEnabled(false); } #include "daemon.moc" diff --git a/kded/daemon.h b/kded/daemon.h index 84484c2..cfd15d3 100644 --- a/kded/daemon.h +++ b/kded/daemon.h @@ -1,92 +1,93 @@ /************************************************************************************ * Copyright (C) 2012 by Alejandro Fiestas Olivares * * Copyright 2018 Roman Gilg * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #ifndef KSCREEN_DAEMON_H #define KSCREEN_DAEMON_H #include "osdaction.h" #include #include #include #include class Config; namespace KScreen { class OsdManager; } class QTimer; class KScreenDaemon : public KDEDModule { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.KScreen") public: KScreenDaemon(QObject *parent, const QList&); ~KScreenDaemon() override; public Q_SLOTS: // DBus void applyLayoutPreset(const QString &presetName); Q_SIGNALS: // DBus void outputConnected(const QString &outputName); void unknownOutputConnected(const QString &outputName); private: Q_INVOKABLE void getInitialConfig(); void init(); void applyConfig(); void applyKnownConfig(); void applyIdealConfig(); void configChanged(); void saveCurrentConfig(); void displayButton(); void lidClosedChanged(bool lidIsClosed); void lidClosedTimeout(); void setMonitorForChanges(bool enabled); void outputConnectedChanged(); void showOutputIdentifier(); void applyOsdAction(KScreen::OsdAction::Action action); void doApplyConfig(const KScreen::ConfigPtr &config); void doApplyConfig(std::unique_ptr config); void refreshConfig(); void monitorConnectedChange(); void disableOutput(KScreen::OutputPtr &output); void showOsd(const QString &icon, const QString &text); std::unique_ptr m_monitoredConfig; bool m_monitoring; QTimer* m_changeCompressor; QTimer* m_saveTimer; QTimer* m_lidClosedTimer; KScreen::OsdManager *m_osdManager; + bool m_startingUp = true; }; #endif /*KSCREEN_DAEMON_H*/