diff --git a/kded/config.h b/kded/config.h --- a/kded/config.h +++ b/kded/config.h @@ -33,6 +33,8 @@ QString id() const; bool fileExists() const; + bool openLidFileExists() const; + std::unique_ptr readFile(); std::unique_ptr readOpenLidFile(); bool writeFile(); diff --git a/kded/config.cpp b/kded/config.cpp --- a/kded/config.cpp +++ b/kded/config.cpp @@ -58,37 +58,37 @@ if (!m_data) { return QString(); } - return m_data->connectedOutputsHash(); + if (Device::self()->isLaptop() && !Device::self()->isLidClosed()) { + return m_data->connectedOutputsHash() % QStringLiteral("_lidOpened"); + } else { + return m_data->connectedOutputsHash(); + } } bool Config::fileExists() const { return (QFile::exists(configsDirPath() % id()) || QFile::exists(configsDirPath() % s_fixedConfigFileName)); } -std::unique_ptr Config::readFile() +bool Config::openLidFileExists() const { if (Device::self()->isLaptop() && !Device::self()->isLidClosed()) { - // We may look for a config that has been set when the lid was closed, Bug: 353029 - const QString lidOpenedFilePath(filePath() % QStringLiteral("_lidOpened")); - const QFile srcFile(lidOpenedFilePath); - - if (srcFile.exists()) { - QFile::remove(filePath()); - if (QFile::copy(lidOpenedFilePath, filePath())) { - QFile::remove(lidOpenedFilePath); - qCDebug(KSCREEN_KDED) << "Restored lid opened config to" << id(); - } - } + return QFile::exists(configsDirPath() % id()); // id() already returns "_lidOpened" + } else { + return QFile::exists(configsDirPath() % id() % QStringLiteral("_lidOpened")); } +} + +std::unique_ptr Config::readFile() +{ return readFile(id()); } std::unique_ptr Config::readOpenLidFile() { - const QString openLidFilePath = filePath() % QStringLiteral("_lidOpened"); + const QString openLidFilePath = filePath() % // filePath() (and id()) already return "_lidOpened" + ((Device::self()->isLaptop() && !Device::self()->isLidClosed()) ? QString() : QStringLiteral("_lidOpened")); auto config = readFile(openLidFilePath); - QFile::remove(openLidFilePath); return config; } @@ -160,7 +160,11 @@ bool Config::writeOpenLidFile() { - return writeFile(filePath() % QStringLiteral("_lidOpened")); + if (Device::self()->isLaptop() && !Device::self()->isLidClosed()) { + return writeFile(filePath()); // filePath() (and id()) already return "_lidOpened" + } else { + return writeFile(filePath() % QStringLiteral("_lidOpened")); + } } bool Config::writeFile(const QString &filePath) diff --git a/kded/daemon.cpp b/kded/daemon.cpp --- a/kded/daemon.cpp +++ b/kded/daemon.cpp @@ -163,7 +163,6 @@ void KScreenDaemon::applyKnownConfig() { qCDebug(KSCREEN_KDED) << "Applying known config"; - std::unique_ptr readInConfig = m_monitoredConfig->readFile(); if (readInConfig) { doApplyConfig(std::move(readInConfig)); @@ -219,14 +218,55 @@ void KScreenDaemon::applyIdealConfig() { + m_osdManager->hideOsd(); if (m_monitoredConfig->data()->connectedOutputs().count() < 2) { - m_osdManager->hideOsd(); doApplyConfig(Generator::self()->idealConfig(m_monitoredConfig->data())); } else { - qCDebug(KSCREEN_KDED) << "Getting ideal config from user via OSD..."; - auto action = m_osdManager->showActionSelector(); - connect(action, &KScreen::OsdAction::selected, - this, &KScreenDaemon::applyOsdAction); + bool lidIsOpen = !Device::self()->isLaptop() + || (Device::self()->isLaptop() && !Device::self()->isLidClosed()); + + if (!lidIsOpen) { + for (KScreen::OutputPtr &output : m_monitoredConfig->data()->outputs()) { + if (output->type() == KScreen::Output::Panel) { + if (output->isConnected() && output->isEnabled()) { + disableOutput(output); + refreshConfig(); + break; + } + } + } + } + else { + for (KScreen::OutputPtr &output : m_monitoredConfig->data()->outputs()) { + if (output->type() == KScreen::Output::Panel) { + if (output->isConnected() && !output->isEnabled()) { + output->setEnabled(true); + refreshConfig(); + break; + } + } + } + } + + if ( (m_monitoredConfig->data()->connectedOutputs().count() > 2) + || (m_monitoredConfig->data()->connectedOutputs().count() == 2 && lidIsOpen) ) { + + if (m_monitoredConfig->data()->connectedOutputs().count() > 2) + qCDebug(KSCREEN_KDED) << "More than one external screen is connected"; + + if (m_monitoredConfig->data()->connectedOutputs().count() > 2 && !lidIsOpen) + qCDebug(KSCREEN_KDED) << "Lid is closed and more than one external screen is connected, disabled integrated panel before showing OSD"; + + if (m_monitoredConfig->data()->connectedOutputs().count() == 2 && lidIsOpen) + qCDebug(KSCREEN_KDED) << "Lid is open and only one external screen is connected"; + + qCDebug(KSCREEN_KDED) << "Getting ideal config from user via OSD..."; + auto action = m_osdManager->showActionSelector(lidIsOpen); + connect(action, &KScreen::OsdAction::selected, + this, &KScreenDaemon::applyOsdAction); + } else { + qCDebug(KSCREEN_KDED) << "Lid is closed and only one external screen is connected, disabled integrated panel, not showing OSD"; + } } } @@ -295,7 +335,10 @@ { qCDebug(KSCREEN_KDED) << "displayBtn triggered"; - auto action = m_osdManager->showActionSelector(); + bool lidIsOpen = !Device::self()->isLaptop() + || (Device::self()->isLaptop() && !Device::self()->isLidClosed()); + + auto action = m_osdManager->showActionSelector(lidIsOpen); connect(action, &KScreen::OsdAction::selected, this, &KScreenDaemon::applyOsdAction); } @@ -320,9 +363,12 @@ // 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()) { + if (auto openCfg = m_monitoredConfig->readFile()) { doApplyConfig(std::move(openCfg)); } + else { + applyConfig(); + } } } @@ -346,9 +392,12 @@ 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(); + if (!m_monitoredConfig->openLidFileExists()) { + m_monitoredConfig->writeOpenLidFile(); + } disableOutput(output); refreshConfig(); + applyConfig(); return; } } @@ -417,6 +466,9 @@ void KScreenDaemon::disableOutput(KScreen::OutputPtr &output) { const QRect geom = output->geometry(); + const bool disabledWasPrimary = output->isPrimary(); + bool primaryFound = false; + KScreen::OutputPtr firstOther = KScreen::OutputPtr(); qCDebug(KSCREEN_KDED) << "Laptop geometry:" << geom << output->pos() << (output->currentMode() ? output->currentMode()->size() : QSize()); // Move all outputs right from the @p output to left @@ -431,6 +483,18 @@ } qCDebug(KSCREEN_KDED) << "Moving" << otherOutput->name() << "from" << otherOutput->pos() << "to" << otherPos; otherOutput->setPos(otherPos); + + if (otherOutput->isPrimary()) { + primaryFound = true; + } + + if (!firstOther) { + firstOther = otherOutput; + } + } + + if (!primaryFound && disabledWasPrimary && firstOther) { + firstOther->setPrimary(true); } // Disable the output diff --git a/kded/generator.h b/kded/generator.h --- a/kded/generator.h +++ b/kded/generator.h @@ -61,6 +61,11 @@ void ready(); private: + enum LeftOrRight { + Left = 0, + Right + }; + explicit Generator(); ~Generator() override; @@ -70,6 +75,8 @@ void laptop(KScreen::OutputList &connectedOutputs); void singleOutput(KScreen::OutputList &connectedOutputs); void extendToRight(KScreen::OutputList &connectedOutputs); + void extendToLeft(KScreen::OutputList &connectedOutputs); + void extendToLeftOrRight(KScreen::OutputList &connectedOutputs, LeftOrRight leftOrRight); KScreen::ModePtr bestModeForSize(const KScreen::ModeList& modes, const QSize &size); KScreen::ModePtr bestModeForOutput(const KScreen::OutputPtr &output); diff --git a/kded/generator.cpp b/kded/generator.cpp --- a/kded/generator.cpp +++ b/kded/generator.cpp @@ -177,18 +177,31 @@ return config; } - // We cannot try all possible combinations with two and more outputs - if (connectedOutputs.count() > 2) { + KScreen::OutputPtr embedded, external; + embedded = embeddedOutput(connectedOutputs); + + // If the lid is closed, the embedded panel is not available + if (embedded && isLaptop() && isLidClosed()) { + connectedOutputs.remove(embedded->id()); + embedded = KScreen::OutputPtr(); + } + + // Use extendToRight / extendToLeft functions for more than two outputs + if (action == Generator::ExtendToRight && connectedOutputs.count() > 2) { extendToRight(connectedOutputs); return config; } + else if (action == Generator::ExtendToLeft && connectedOutputs.count() > 2) { + extendToLeft(connectedOutputs); + return config; + } - KScreen::OutputPtr embedded, external; - embedded = embeddedOutput(connectedOutputs); + bool embeddedIsExternal = false; // If we don't have an embedded output (desktop with two external screens // for instance), then pretend one of them is embedded if (!embedded) { embedded = connectedOutputs.value(connectedOutputs.keys().first()); + embeddedIsExternal = true; } // Just to be sure if (embedded->modes().isEmpty()) { @@ -204,6 +217,7 @@ connectedOutputs.remove(embedded->id()); external = connectedOutputs.value(connectedOutputs.keys().first()); + // Just to be sure if (external->modes().isEmpty()) { return config; @@ -231,18 +245,39 @@ } case Generator::TurnOffEmbedded: { qCDebug(KSCREEN_KDED) << "Turn off embedded (laptop)"; + if (embeddedIsExternal) { + qCDebug(KSCREEN_KDED) << "embeddedIsExternal: don't turn off embedded (laptop)"; + return config; + } embedded->setEnabled(false); embedded->setPrimary(false); external->setEnabled(true); external->setPrimary(true); const KScreen::ModePtr extMode = bestModeForOutput(external); Q_ASSERT(extMode); external->setCurrentModeId(extMode->id()); + + if (connectedOutputs.count() > 1) { + connectedOutputs.remove(external->id()); + QMapIterator i(connectedOutputs); + while (i.hasNext()) { + i.next(); + i.value()->setEnabled(true); + const KScreen::ModePtr extMode = bestModeForOutput(i.value()); + Q_ASSERT(extMode); + i.value()->setCurrentModeId(extMode->id()); + } + } + return config; } case Generator::TurnOffExternal: { qCDebug(KSCREEN_KDED) << "Turn off external screen"; + if (embeddedIsExternal) { + qCDebug(KSCREEN_KDED) << "embeddedIsExternal: don't turn off external screen"; + return config; + } embedded->setPos(QPoint(0,0)); embedded->setEnabled(true); embedded->setPrimary(true); @@ -252,6 +287,17 @@ external->setEnabled(false); external->setPrimary(false); + + if (connectedOutputs.count() > 1) { + connectedOutputs.remove(external->id()); + QMapIterator i(connectedOutputs); + while (i.hasNext()) { + i.next(); + i.value()->setEnabled(false); + external->setPrimary(false); + } + } + return config; } case Generator::ExtendToRight: { @@ -263,7 +309,6 @@ Q_ASSERT(embeddedMode); embedded->setCurrentModeId(embeddedMode->id()); - Q_ASSERT(embedded->currentMode()); // we must have a mode now const QSize size = embedded->geometry().size(); external->setPos(QPoint(size.width(), 0)); @@ -370,6 +415,7 @@ Q_ASSERT(bestMode); output->setCurrentModeId(bestMode->id()); output->setEnabled(true); + output->setPrimary(true); output->setPos(QPoint(0,0)); } @@ -470,38 +516,61 @@ } } -void Generator::extendToRight(KScreen::OutputList &connectedOutputs) +void Generator::extendToLeftOrRight(KScreen::OutputList &connectedOutputs, LeftOrRight leftOrRight) { ASSERT_OUTPUTS(connectedOutputs); if (connectedOutputs.isEmpty()) { return; } - qCDebug(KSCREEN_KDED) << "Extending to the right"; + if (leftOrRight == Left) + qCDebug(KSCREEN_KDED) << "Extending to the left"; + else + qCDebug(KSCREEN_KDED) << "Extending to the right"; + KScreen::OutputPtr biggest = biggestOutput(connectedOutputs); Q_ASSERT(biggest); connectedOutputs.remove(biggest->id()); biggest->setEnabled(true); biggest->setPrimary(true); - biggest->setPos(QPoint(0,0)); - const KScreen::ModePtr mode = bestModeForOutput(biggest); - Q_ASSERT(mode); - biggest->setCurrentModeId(mode->id()); - int globalWidth = biggest->geometry().width(); + if (leftOrRight == Right) + biggest->setPos(QPoint(0,0)); + + const KScreen::ModePtr biggestMode = bestModeForOutput(biggest); + Q_ASSERT(biggestMode); + biggest->setCurrentModeId(biggestMode->id()); + + int globalWidth = 0; + + if (leftOrRight == Right) + globalWidth = biggest->geometry().width(); Q_FOREACH(KScreen::OutputPtr output, connectedOutputs) { output->setEnabled(true); output->setPrimary(false); output->setPos(QPoint(globalWidth, 0)); - const KScreen::ModePtr mode = bestModeForOutput(output); - Q_ASSERT(mode); - output->setCurrentModeId(mode->id()); + const KScreen::ModePtr otherMode = bestModeForOutput(output); + Q_ASSERT(otherMode); + output->setCurrentModeId(otherMode->id()); globalWidth += output->geometry().width(); } + + if (leftOrRight == Left) + biggest->setPos(QPoint(globalWidth,0)); +} + +void Generator::extendToRight(KScreen::OutputList &connectedOutputs) +{ + extendToLeftOrRight(connectedOutputs, Right); +} + +void Generator::extendToLeft(KScreen::OutputList &connectedOutputs) +{ + extendToLeftOrRight(connectedOutputs, Left); } KScreen::ModePtr Generator::biggestMode(const KScreen::ModeList &modes) diff --git a/kded/osdmanager.h b/kded/osdmanager.h --- a/kded/osdmanager.h +++ b/kded/osdmanager.h @@ -45,7 +45,7 @@ void showOutputIdentifiers(); void showOsd(const QString &icon, const QString &text); void hideOsd(); - OsdAction *showActionSelector(); + OsdAction *showActionSelector(bool lidIsOpen); private: void slotIdentifyOutputs(KScreen::ConfigOperation *op); diff --git a/kded/osdmanager.cpp b/kded/osdmanager.cpp --- a/kded/osdmanager.cpp +++ b/kded/osdmanager.cpp @@ -27,6 +27,7 @@ #include #include +#include namespace KScreen { @@ -133,7 +134,7 @@ ); } -OsdAction *OsdManager::showActionSelector() +OsdAction *OsdManager::showActionSelector(bool lidIsOpen) { hideOsd(); @@ -145,7 +146,7 @@ } }); connect(new KScreen::GetConfigOperation(), &KScreen::GetConfigOperation::finished, - this, [this, action](const KScreen::ConfigOperation *op) { + this, [this, action, lidIsOpen](const KScreen::ConfigOperation *op) { if (op->hasError()) { qCWarning(KSCREEN_KDED) << op->errorString(); return; @@ -160,18 +161,33 @@ } // Prefer laptop screen - if (output->type() == KScreen::Output::Panel) { + if ((output->type() == KScreen::Output::Panel) && lidIsOpen) { osdOutput = output; break; } // Fallback to primary - if (output->isPrimary()) { + if (output->isPrimary() && lidIsOpen) { osdOutput = output; break; } } - // no laptop or primary screen, just take the first usable one + + // no laptop (with open lid) or primary screen (with open lid), just take the first usable one (with open lid) + if (!osdOutput) { + for (const auto &output : outputs) { + if ( (output->isConnected() && output->isEnabled() && output->currentMode()) + && ( ( (output->type() == KScreen::Output::Panel) && lidIsOpen) + || (output->type() != KScreen::Output::Panel) + ) + ) { + osdOutput = output; + break; + } + } + } + + // nothing up to now, just take the first usable one, even with closed lid if (!osdOutput) { for (const auto &output : outputs) { if (output->isConnected() && output->isEnabled() && output->currentMode()) { diff --git a/tests/kded/configtest.cpp b/tests/kded/configtest.cpp --- a/tests/kded/configtest.cpp +++ b/tests/kded/configtest.cpp @@ -456,8 +456,8 @@ configWrapper = std::move(configWrapper->readOpenLidFile()); QVERIFY(configWrapper); - // Check actual files, src should be gone, dest must exist - QVERIFY(!openCfg.exists()); + // Check actual files, src and dest must exist + QVERIFY(openCfg.exists()); QVERIFY(closedCfg.exists()); // Make sure the laptop panel is enabled and primary again diff --git a/tests/osd/osdtest.cpp b/tests/osd/osdtest.cpp --- a/tests/osd/osdtest.cpp +++ b/tests/osd/osdtest.cpp @@ -86,7 +86,7 @@ void OsdTest::showActionSelector() { if (!m_useDBus) { - auto action = getOsdManager()->showActionSelector(); + auto action = getOsdManager()->showActionSelector(true); connect(action, &KScreen::OsdAction::selected, [](KScreen::OsdAction::Action action) { qCDebug(KSCREEN_KDED) << "Selected action:" << action;