diff --git a/kcm/package/contents/ui/main.qml b/kcm/package/contents/ui/main.qml index 955e3a2..157d68a 100644 --- a/kcm/package/contents/ui/main.qml +++ b/kcm/package/contents/ui/main.qml @@ -1,129 +1,129 @@ /******************************************************************** Copyright © 2019 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, see . *********************************************************************/ import QtQuick 2.9 import QtQuick.Layouts 1.1 import QtQuick.Controls 2.3 as Controls import org.kde.kirigami 2.4 as Kirigami import org.kde.kcm 1.2 as KCM KCM.SimpleKCM { id: root implicitWidth: units.gridUnit * 32 implicitHeight: units.gridUnit * 38 property int selectedOutput: 0 ColumnLayout { Kirigami.InlineMessage { // Note1: There is an implicit height binding loop error on - // first invocation. Seems to be an issue in Kirigami. + // first invokation. Seems to be an issue in Kirigami. // Note2: This should maybe go in header component of the KCM, // but there seems to be another issue in Kirigami then // being always hidden. Compare Night Color KCM with // the same issue. id: dangerousSaveMsg Layout.fillWidth: true type: Kirigami.MessageType.Warning text: i18n("Are you sure you want to disable all outputs? This might render the device unusable.") showCloseButton: true actions: [ Kirigami.Action { iconName: "dialog-ok" text: i18n("Disable All Outputs") onTriggered: { dangerousSaveMsg.visible = false; kcm.forceSave(); } } ] } Kirigami.InlineMessage { id: errBackendMsg Layout.fillWidth: true type: Kirigami.MessageType.Error text: i18n("No KScreen backend found. Please check your KScreen installation.") visible: false showCloseButton: false } Kirigami.InlineMessage { id: errSaveMsg Layout.fillWidth: true type: Kirigami.MessageType.Error text: i18n("Outputs could not be saved due to error.") visible: false showCloseButton: true } Kirigami.InlineMessage { id: scaleMsg Layout.fillWidth: true type: Kirigami.MessageType.Positive text: i18n("New global scale applied. Change will come into effect after restart.") visible: false showCloseButton: true } Kirigami.InlineMessage { id: connectMsg Layout.fillWidth: true type: Kirigami.MessageType.Information visible: false showCloseButton: true } Connections { target: kcm onDangerousSave: dangerousSaveMsg.visible = true; onErrorOnSave: errSaveMsg.visible = true; onGlobalScaleWritten: scaleMsg.visible = true; onOutputConnect: { if (connected) { connectMsg.text = i18n("A new output has been added. Settings have been reloaded."); } else { connectMsg.text = i18n("An output has been removed. Settings have been reloaded."); } connectMsg.visible = true; } onBackendError: errBackendMsg.visible = true; onChanged: { dangerousSaveMsg.visible = false; errSaveMsg.visible = false; scaleMsg.visible = false; } } Screen { id: screen Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: Math.max(root.width * 0.8, units.gridUnit * 26) Layout.topMargin: Kirigami.Units.smallSpacing Layout.bottomMargin: Kirigami.Units.largeSpacing * 2 enabled: kcm.outputModel && kcm.backendReady outputs: kcm.outputModel } Panel { enabled: kcm.outputModel && kcm.backendReady Layout.fillWidth: true } } } diff --git a/kded/daemon.cpp b/kded/daemon.cpp index 58aaeb3..c618d76 100644 --- a/kded/daemon.cpp +++ b/kded/daemon.cpp @@ -1,446 +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, [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 successful, fall back to ideal config + // 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() { 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( QLatin1String("org.kde.plasmashell"), QLatin1String("/org/kde/osdService"), QLatin1String("org.kde.osdService"), QLatin1String("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/generator.cpp b/kded/generator.cpp index f74d58d..07618a4 100644 --- a/kded/generator.cpp +++ b/kded/generator.cpp @@ -1,674 +1,674 @@ /************************************************************************************* * Copyright (C) 2012 by Alejandro Fiestas Olivares * * * * 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 "generator.h" #include "device.h" #include "kscreen_daemon_debug.h" #include #include #if defined(QT_NO_DEBUG) #define ASSERT_OUTPUTS(outputs) #else #define ASSERT_OUTPUTS(outputs) \ while(true) { \ Q_ASSERT(!outputs.isEmpty()); \ Q_FOREACH (const KScreen::OutputPtr &output, outputs) { \ Q_ASSERT(output); \ Q_ASSERT(output->isConnected()); \ } break; \ } #endif Generator* Generator::instance = nullptr; bool operator<(const QSize &s1, const QSize &s2) { return s1.width() * s1.height() < s2.width() * s2.height(); } Generator* Generator::self() { if (!Generator::instance) { Generator::instance = new Generator(); } return Generator::instance; } Generator::Generator() : QObject() , m_forceLaptop(false) , m_forceLidClosed(false) , m_forceNotLaptop(false) , m_forceDocked(false) { connect(Device::self(), &Device::ready, this, &Generator::ready); } void Generator::destroy() { delete Generator::instance; Generator::instance = nullptr; } Generator::~Generator() { } void Generator::setCurrentConfig(const KScreen::ConfigPtr ¤tConfig) { m_currentConfig = currentConfig; } KScreen::ConfigPtr Generator::idealConfig(const KScreen::ConfigPtr ¤tConfig) { Q_ASSERT(currentConfig); // KDebug::Block idealBlock("Ideal Config"); KScreen::ConfigPtr config = currentConfig->clone(); disableAllDisconnectedOutputs(config->outputs()); KScreen::OutputList connectedOutputs = config->connectedOutputs(); qCDebug(KSCREEN_KDED) << "Connected outputs: " << connectedOutputs.count(); if (connectedOutputs.isEmpty()) { return config; } //the scale will generally be independent no matter where the output is //scale will affect geometry, so do this first if (config->supportedFeatures().testFlag(KScreen::Config::Feature::PerOutputScaling)) { for(auto output: qAsConst(connectedOutputs)) { output->setScale(bestScaleForOutput(output)); } } if (connectedOutputs.count() == 1) { singleOutput(connectedOutputs); return config; } if (isLaptop()) { laptop(connectedOutputs); return fallbackIfNeeded(config); } qCDebug(KSCREEN_KDED) << "Extend to Right"; extendToRight(connectedOutputs); return fallbackIfNeeded(config); } KScreen::ConfigPtr Generator::fallbackIfNeeded(const KScreen::ConfigPtr &config) { qCDebug(KSCREEN_KDED) << "fallbackIfNeeded()"; KScreen::ConfigPtr newConfig; //If the ideal config can't be applied, try clonning if (!KScreen::Config::canBeApplied(config)) { if (isLaptop()) { newConfig = displaySwitch(Generator::Clone); // Try to clone at our best } else { newConfig = config; KScreen::OutputList connectedOutputs = config->connectedOutputs(); if (connectedOutputs.isEmpty()) { return config; } connectedOutputs.value(connectedOutputs.keys().first())->setPrimary(true); cloneScreens(connectedOutputs); } } else { newConfig = config; } //If after trying to clone at our best, we fail... return current if (!KScreen::Config::canBeApplied(newConfig)) { qCDebug(KSCREEN_KDED) << "Config cannot be applied"; newConfig = config; } return config; } KScreen::ConfigPtr Generator::displaySwitch(DisplaySwitchAction action) { // KDebug::Block switchBlock("Display Switch"); KScreen::ConfigPtr config = m_currentConfig; Q_ASSERT(config); KScreen::OutputList connectedOutputs = config->connectedOutputs(); //the scale will generally be independent no matter where the output is //scale will affect geometry, so do this first if (config->supportedFeatures().testFlag(KScreen::Config::Feature::PerOutputScaling)) { for(auto output: qAsConst(connectedOutputs)) { output->setScale(bestScaleForOutput(output)); } } // There's not much else we can do with only one output if (connectedOutputs.count() < 2) { singleOutput(connectedOutputs); return config; } // We cannot try all possible combinations with two and more outputs if (connectedOutputs.count() > 2) { extendToRight(connectedOutputs); return config; } KScreen::OutputPtr embedded, external; embedded = embeddedOutput(connectedOutputs); // 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()); } // Just to be sure if (embedded->modes().isEmpty()) { return config; } if (action == Generator::Clone) { qCDebug(KSCREEN_KDED) << "Cloning"; embedded->setPrimary(true); cloneScreens(connectedOutputs); return config; } connectedOutputs.remove(embedded->id()); external = connectedOutputs.value(connectedOutputs.keys().first()); // Just to be sure if (external->modes().isEmpty()) { return config; } switch (action) { case Generator::ExtendToLeft: { qCDebug(KSCREEN_KDED) << "Extend to left"; external->setPos(QPoint(0,0)); external->setEnabled(true); const KScreen::ModePtr extMode = bestModeForOutput(external); Q_ASSERT(extMode); external->setCurrentModeId(extMode->id()); Q_ASSERT(external->currentMode()); // we must have a mode now const QSize size = external->geometry().size(); embedded->setPos(QPoint(size.width(), 0)); embedded->setEnabled(true); embedded->setPrimary(true); const KScreen::ModePtr embeddedMode = bestModeForOutput(embedded); Q_ASSERT(embeddedMode); embedded->setCurrentModeId(embeddedMode->id()); return config; } case Generator::TurnOffEmbedded: { qCDebug(KSCREEN_KDED) << "Turn off embedded (laptop)"; 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()); return config; } case Generator::TurnOffExternal: { qCDebug(KSCREEN_KDED) << "Turn off external screen"; embedded->setPos(QPoint(0,0)); embedded->setEnabled(true); embedded->setPrimary(true); const KScreen::ModePtr embeddedMode = bestModeForOutput(embedded); Q_ASSERT(embeddedMode); embedded->setCurrentModeId(embeddedMode->id()); external->setEnabled(false); external->setPrimary(false); return config; } case Generator::ExtendToRight: { qCDebug(KSCREEN_KDED) << "Extend to the right"; embedded->setPos(QPoint(0,0)); embedded->setEnabled(true); embedded->setPrimary(true); const KScreen::ModePtr embeddedMode = bestModeForOutput(embedded); 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)); external->setEnabled(true); external->setPrimary(false); const KScreen::ModePtr extMode = bestModeForOutput(external); Q_ASSERT(extMode); external->setCurrentModeId(extMode->id()); return config; } case Generator::None: // just return config case Generator::Clone: // handled above break; } // switch return config; } uint qHash(const QSize &size) { return size.width() * size.height(); } void Generator::cloneScreens(KScreen::OutputList &connectedOutputs) { ASSERT_OUTPUTS(connectedOutputs); if (connectedOutputs.isEmpty()) { return; } QSet commonSizes; const QSize maxScreenSize = m_currentConfig->screen()->maxSize(); Q_FOREACH(const KScreen::OutputPtr &output, connectedOutputs) { QSet modeSizes; Q_FOREACH(const KScreen::ModePtr &mode, output->modes()) { const QSize size = mode->size(); if (size.width() > maxScreenSize.width() || size.height() > maxScreenSize.height()) { continue; } modeSizes.insert(mode->size()); } //If we have nothing to compare against if (commonSizes.isEmpty()) { commonSizes = modeSizes; continue; } commonSizes.intersect(modeSizes); } qCDebug(KSCREEN_KDED) << "Common sizes: " << commonSizes; //fallback to biggestMode if no commonSizes have been found if (commonSizes.isEmpty()) { Q_FOREACH(KScreen::OutputPtr output, connectedOutputs) { if (output->modes().isEmpty()) { continue; } output->setEnabled(true); output->setPos(QPoint(0, 0)); const KScreen::ModePtr mode = biggestMode(output->modes()); Q_ASSERT(mode); output->setCurrentModeId(mode->id()); } return; } //At this point, we know we have common sizes, let's get the biggest on QList commonSizeList = commonSizes.toList(); std::sort(commonSizeList.begin(), commonSizeList.end()); const QSize biggestSize = commonSizeList.last(); //Finally, look for the mode with biggestSize and biggest refreshRate and set it qCDebug(KSCREEN_KDED) << "Biggest Size: " << biggestSize; KScreen::ModePtr bestMode; Q_FOREACH(KScreen::OutputPtr output, connectedOutputs) { if (output->modes().isEmpty()) { continue; } bestMode = bestModeForSize(output->modes(), biggestSize); Q_ASSERT(bestMode); // we resolved this mode previously, so it better works output->setEnabled(true); output->setPos(QPoint(0, 0)); output->setCurrentModeId(bestMode->id()); } } void Generator::singleOutput(KScreen::OutputList &connectedOutputs) { ASSERT_OUTPUTS(connectedOutputs); if (connectedOutputs.isEmpty()) { return; } KScreen::OutputPtr output = connectedOutputs.take(connectedOutputs.keys().first()); if (output->modes().isEmpty()) { return; } const KScreen::ModePtr bestMode = bestModeForOutput(output); Q_ASSERT(bestMode); output->setCurrentModeId(bestMode->id()); output->setEnabled(true); output->setPrimary(true); output->setPos(QPoint(0,0)); } void Generator::laptop(KScreen::OutputList &connectedOutputs) { ASSERT_OUTPUTS(connectedOutputs) if (connectedOutputs.isEmpty()) { return; } // KDebug::Block laptopBlock("Laptop config"); KScreen::OutputPtr embedded = embeddedOutput(connectedOutputs); /* Apparently older laptops use "VGA-*" as embedded output ID, so embeddedOutput() * will fail, because it looks only for modern "LVDS", "EDP", etc. If we * fail to detect which output is embedded, just use the one with the lowest * ID. It's a wild guess, but I think it's highly probable that it will work. * See bug #318907 for further reference. -- dvratil */ if (!embedded) { QList keys = connectedOutputs.keys(); std::sort(keys.begin(), keys.end()); embedded = connectedOutputs.value(keys.first()); } connectedOutputs.remove(embedded->id()); if (connectedOutputs.isEmpty() || embedded->modes().isEmpty()) { qCWarning(KSCREEN_KDED) << "No external outputs found, going for singleOutput()"; connectedOutputs.insert(embedded->id(), embedded); return singleOutput(connectedOutputs); } if (isLidClosed() && connectedOutputs.count() == 1) { qCDebug(KSCREEN_KDED) << "With lid closed"; embedded->setEnabled(false); embedded->setPrimary(false); KScreen::OutputPtr external = connectedOutputs.value(connectedOutputs.keys().first()); if (external->modes().isEmpty()) { return; } external->setEnabled(true); external->setPrimary(true); const KScreen::ModePtr bestMode = bestModeForOutput(external); Q_ASSERT(bestMode); external->setCurrentModeId(bestMode->id()); external->setPos(QPoint(0, 0)); return; } if (isLidClosed() && connectedOutputs.count() > 1) { qCDebug(KSCREEN_KDED) << "Lid is closed, and more than one output"; embedded->setEnabled(false); embedded->setPrimary(false); extendToRight(connectedOutputs); return; } qCDebug(KSCREEN_KDED) << "Lid is open"; - //If lid is open, laptop screen should be primary + //If lid is open, laptop screen shuold be primary embedded->setPos(QPoint(0,0)); embedded->setPrimary(true); embedded->setEnabled(true); const KScreen::ModePtr embeddedMode = bestModeForOutput(embedded); Q_ASSERT(embeddedMode); embedded->setCurrentModeId(embeddedMode->id()); int globalWidth = embedded->geometry().width(); KScreen::OutputPtr biggest = biggestOutput(connectedOutputs); Q_ASSERT(biggest); connectedOutputs.remove(biggest->id()); biggest->setPos(QPoint(globalWidth, 0)); biggest->setEnabled(true); biggest->setPrimary(false); const KScreen::ModePtr mode = bestModeForOutput(biggest); biggest->setCurrentModeId(mode->id()); 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()); globalWidth += output->geometry().width(); } if (isDocked()) { qCDebug(KSCREEN_KDED) << "Docked"; embedded->setPrimary(false); biggest->setPrimary(true); } } void Generator::extendToRight(KScreen::OutputList &connectedOutputs) { ASSERT_OUTPUTS(connectedOutputs); if (connectedOutputs.isEmpty()) { return; } 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(); 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()); globalWidth += output->geometry().width(); } } KScreen::ModePtr Generator::biggestMode(const KScreen::ModeList &modes) { Q_ASSERT(!modes.isEmpty()); int modeArea, biggestArea = 0; KScreen::ModePtr biggestMode; Q_FOREACH(const KScreen::ModePtr &mode, modes) { modeArea = mode->size().width() * mode->size().height(); if (modeArea < biggestArea) { continue; } if (modeArea == biggestArea && mode->refreshRate() < biggestMode->refreshRate()) { continue; } if (modeArea == biggestArea && mode->refreshRate() > biggestMode->refreshRate()) { biggestMode = mode; continue; } biggestArea = modeArea; biggestMode = mode; } return biggestMode; } KScreen::ModePtr Generator::bestModeForSize(const KScreen::ModeList &modes, const QSize &size) { KScreen::ModePtr bestMode; Q_FOREACH(const KScreen::ModePtr &mode, modes) { if (mode->size() != size) { continue; } if (!bestMode) { bestMode = mode; continue; } if (mode->refreshRate() > bestMode->refreshRate()) { bestMode = mode; } } return bestMode; } qreal Generator::bestScaleForOutput(const KScreen::OutputPtr &output) { //if we have no physical size, we can't determine the DPI properly. Fallback to scale 1 if (output->sizeMm().height() <= 0) { return 1.0; } const auto mode = bestModeForOutput(output); const qreal dpi = mode->size().height() / (output->sizeMm().height() / 25.4); //if reported DPI is closer to two times normal DPI, followed by a sanity check of having the sort of vertical resolution //you'd find in a high res screen if (dpi > 96 * 1.5 && mode->size().height() >= 1440) { return 2.0; } return 1.0; } KScreen::ModePtr Generator::bestModeForOutput(const KScreen::OutputPtr &output) { if (KScreen::ModePtr outputMode = output->preferredMode()) { return outputMode; } return biggestMode(output->modes()); } KScreen::OutputPtr Generator::biggestOutput(const KScreen::OutputList &outputs) { ASSERT_OUTPUTS(outputs) int area, total = 0; KScreen::OutputPtr biggest; Q_FOREACH(const KScreen::OutputPtr &output, outputs) { const KScreen::ModePtr mode = bestModeForOutput(output); if (!mode) { continue; } area = mode->size().width() * mode->size().height(); if (area <= total) { continue; } total = area; biggest = output; } return biggest; } void Generator::disableAllDisconnectedOutputs(const KScreen::OutputList &outputs) { // KDebug::Block disableBlock("Disabling disconnected screens"); Q_FOREACH(KScreen::OutputPtr output, outputs) { if (!output->isConnected()) { qCDebug(KSCREEN_KDED) << output->name() << " Disabled"; output->setEnabled(false); output->setPrimary(false); } } } KScreen::OutputPtr Generator::embeddedOutput(const KScreen::OutputList &outputs) { Q_FOREACH(const KScreen::OutputPtr &output, outputs) { if (output->type() != KScreen::Output::Panel) { continue; } return output; } return KScreen::OutputPtr(); } bool Generator::isLaptop() const { if (m_forceLaptop) { return true; } if (m_forceNotLaptop) { return false; } return Device::self()->isLaptop(); } bool Generator::isLidClosed() const { if (m_forceLidClosed) { return true; } if (m_forceNotLaptop) { return false; } return Device::self()->isLidClosed(); } bool Generator::isDocked() const { if (m_forceDocked) { return true; } return Device::self()->isDocked(); } void Generator::setForceLaptop(bool force) { m_forceLaptop = force; } void Generator::setForceLidClosed(bool force) { m_forceLidClosed = force; } void Generator::setForceDocked(bool force) { m_forceDocked = force; } void Generator::setForceNotLaptop(bool force) { m_forceNotLaptop = force; } diff --git a/kded/osdmanager.cpp b/kded/osdmanager.cpp index 36eaff6..9c6a116 100644 --- a/kded/osdmanager.cpp +++ b/kded/osdmanager.cpp @@ -1,208 +1,208 @@ /* * Copyright 2016 Sebastian Kügler * * 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 "osdmanager.h" #include "osd.h" #include "kscreen_daemon_debug.h" #include #include #include #include #include namespace KScreen { class OsdActionImpl : public OsdAction { Q_OBJECT public: OsdActionImpl(QObject *parent = nullptr) : OsdAction(parent) {} void setOsd(Osd *osd) { connect(osd, &Osd::osdActionSelected, this, [this](Action action) { Q_EMIT selected(action); deleteLater(); }); } }; OsdManager::OsdManager(QObject *parent) : QObject(parent) , m_cleanupTimer(new QTimer(this)) { qmlRegisterSingletonType("org.kde.KScreen", 1, 0, "OsdAction", [](QQmlEngine *, QJSEngine *) -> QObject* { return new KScreen::OsdAction(); }); // free up memory when the osd hasn't been used for more than 1 minute m_cleanupTimer->setInterval(60000); m_cleanupTimer->setSingleShot(true); connect(m_cleanupTimer, &QTimer::timeout, this, [this]() { hideOsd(); }); QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.kscreen.osdService")); if (!QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kde/kscreen/osdService"), this, QDBusConnection::ExportAllSlots)) { qCWarning(KSCREEN_KDED) << "Failed to registerObject"; } } void OsdManager::hideOsd() { qDeleteAll(m_osds); m_osds.clear(); } OsdManager::~OsdManager() { } void OsdManager::showOutputIdentifiers() { connect(new KScreen::GetConfigOperation(), &KScreen::GetConfigOperation::finished, this, &OsdManager::slotIdentifyOutputs); } void OsdManager::slotIdentifyOutputs(KScreen::ConfigOperation *op) { if (op->hasError()) { return; } const KScreen::ConfigPtr config = qobject_cast(op)->config(); Q_FOREACH (const KScreen::OutputPtr &output, config->outputs()) { if (!output->isConnected() || !output->isEnabled() || !output->currentMode()) { continue; } auto osd = m_osds.value(output->name()); if (!osd) { osd = new KScreen::Osd(output, this); m_osds.insert(output->name(), osd); } osd->showOutputIdentifier(output); } m_cleanupTimer->start(); } void OsdManager::showOsd(const QString& icon, const QString& text) { hideOsd(); connect(new KScreen::GetConfigOperation(), &KScreen::GetConfigOperation::finished, this, [this, icon, text] (KScreen::ConfigOperation *op) { if (op->hasError()) { return; } const KScreen::ConfigPtr config = qobject_cast(op)->config(); Q_FOREACH (const KScreen::OutputPtr &output, config->outputs()) { if (!output->isConnected() || !output->isEnabled() || !output->currentMode()) { continue; } auto osd = m_osds.value(output->name()); if (!osd) { osd = new KScreen::Osd(output, this); m_osds.insert(output->name(), osd); } osd->showGenericOsd(icon, text); } m_cleanupTimer->start(); } ); } OsdAction *OsdManager::showActionSelector() { hideOsd(); OsdActionImpl *action = new OsdActionImpl(this); connect(action, &OsdActionImpl::selected, this, [this]() { for (auto osd : qAsConst(m_osds)) { osd->hideOsd(); } }); connect(new KScreen::GetConfigOperation(), &KScreen::GetConfigOperation::finished, this, [this, action](const KScreen::ConfigOperation *op) { if (op->hasError()) { qCWarning(KSCREEN_KDED) << op->errorString(); return; } - // Show selector on all enabled screens + // Show selector on alll enabled screens const auto outputs = op->config()->outputs(); KScreen::OutputPtr osdOutput; for (const auto &output : outputs) { if (!output->isConnected() || !output->isEnabled() || !output->currentMode()) { continue; } // Prefer laptop screen if (output->type() == KScreen::Output::Panel) { osdOutput = output; break; } // Fallback to primary if (output->isPrimary()) { osdOutput = output; break; } } // no laptop or primary screen, just take the first usable one if (!osdOutput) { for (const auto &output : outputs) { if (output->isConnected() && output->isEnabled() && output->currentMode()) { osdOutput = output; break; } } } if (!osdOutput) { // huh!? return; } KScreen::Osd* osd = nullptr; if (m_osds.contains(osdOutput->name())) { osd = m_osds.value(osdOutput->name()); } else { osd = new KScreen::Osd(osdOutput, this); m_osds.insert(osdOutput->name(), osd); } action->setOsd(osd); osd->showActionSelector(); m_cleanupTimer->start(); } ); return action; } } #include "osdmanager.moc" diff --git a/kded/output.cpp b/kded/output.cpp index fa37fb7..df52cde 100644 --- a/kded/output.cpp +++ b/kded/output.cpp @@ -1,425 +1,425 @@ /******************************************************************** Copyright 2019 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, see . *********************************************************************/ #include "output.h" #include "config.h" #include "../common/globals.h" #include "kscreen_daemon_debug.h" #include "generator.h" #include #include #include #include #include #include #include #include QString Output::s_dirName = QStringLiteral("outputs/"); QString Output::dirPath() { return Globals::dirPath() % s_dirName; } QString Output::globalFileName(const QString &hash) { const auto dir = dirPath(); if (!QDir().mkpath(dir)) { return QString(); } return dir % hash; } void Output::readInGlobalPartFromInfo(KScreen::OutputPtr output, const QVariantMap &info) { output->setRotation(static_cast(info.value(QStringLiteral("rotation"), 1).toInt())); bool scaleOk; const qreal scale = info.value(QStringLiteral("scale"), 1.).toDouble(&scaleOk); if (scaleOk) { output->setScale(scale); } const QVariantMap modeInfo = info[QStringLiteral("mode")].toMap(); const QVariantMap modeSize = modeInfo[QStringLiteral("size")].toMap(); const QSize size = QSize(modeSize[QStringLiteral("width")].toInt(), modeSize[QStringLiteral("height")].toInt()); qCDebug(KSCREEN_KDED) << "Finding a mode for" << size << "@" << modeInfo[QStringLiteral("refresh")].toFloat(); KScreen::ModeList modes = output->modes(); KScreen::ModePtr matchingMode; for(const KScreen::ModePtr &mode : modes) { if (mode->size() != size) { continue; } if (!qFuzzyCompare(mode->refreshRate(), modeInfo[QStringLiteral("refresh")].toFloat())) { continue; } qCDebug(KSCREEN_KDED) << "\tFound: " << mode->id() << " " << mode->size() << "@" << mode->refreshRate(); matchingMode = mode; break; } if (!matchingMode) { qCWarning(KSCREEN_KDED) << "\tFailed to find a matching mode - this means that our config is corrupted" "or a different device with the same serial number has been connected (very unlikely)." "Falling back to preferred modes."; matchingMode = output->preferredMode(); } if (!matchingMode) { qCWarning(KSCREEN_KDED) << "\tFailed to get a preferred mode, falling back to biggest mode."; matchingMode = Generator::biggestMode(modes); } if (!matchingMode) { qCWarning(KSCREEN_KDED) << "\tFailed to get biggest mode. Which means there are no modes. Turning off the screen."; output->setEnabled(false); return; } output->setCurrentModeId(matchingMode->id()); } QVariantMap Output::getGlobalData(KScreen::OutputPtr output) { QFile file(globalFileName(output->hashMd5())); if (!file.open(QIODevice::ReadOnly)) { qCDebug(KSCREEN_KDED) << "Failed to open file" << file.fileName(); return QVariantMap(); } QJsonDocument parser; return parser.fromJson(file.readAll()).toVariant().toMap(); } bool Output::readInGlobal(KScreen::OutputPtr output) { const QVariantMap info = getGlobalData(output); if (info.empty()) { // if info is empty, the global file does not exists, or is in an unreadable state return false; } readInGlobalPartFromInfo(output, info); return true; } void Output::adjustPositions(KScreen::ConfigPtr config, const QVariantList &outputsInfo) { typedef QPair Out; KScreen::OutputList outputs = config->outputs(); QVector sortedOutputs; // for (const KScreen::OutputPtr output : outputs) { sortedOutputs.append(Out(output->id(), output->pos())); } // go from left to right, top to bottom std::sort(sortedOutputs.begin(), sortedOutputs.end(), [](const Out &o1, const Out &o2) { const int x1 = o1.second.x(); const int x2 = o2.second.x(); return x1 < x2 || (x1 == x2 && o1.second.y() < o2.second.y()); }); for (int cnt = 1; cnt < sortedOutputs.length(); cnt++) { auto getOutputInfoProperties = [outputsInfo](KScreen::OutputPtr output, QRect &geo) -> bool { if (!output) { return false; } const auto hash = output->hash(); auto it = std::find_if(outputsInfo.begin(), outputsInfo.end(), [hash](QVariant v) { const QVariantMap info = v.toMap(); return info[QStringLiteral("id")].toString() == hash; } ); if (it == outputsInfo.end()) { return false; } auto isPortrait = [](const QVariant &info) { bool ok; const int rot = info.toInt(&ok); if (!ok) { return false; } return rot & KScreen::Output::Rotation::Left || rot & KScreen::Output::Rotation::Right; }; const QVariantMap outputInfo = it->toMap(); const QVariantMap posInfo = outputInfo[QStringLiteral("pos")].toMap(); const QVariant scaleInfo = outputInfo[QStringLiteral("scale")]; const QVariantMap modeInfo = outputInfo[QStringLiteral("mode")].toMap(); const QVariantMap modeSize = modeInfo[QStringLiteral("size")].toMap(); const bool portrait = isPortrait(outputInfo[QStringLiteral("rotation")]); if (posInfo.isEmpty() || modeSize.isEmpty() || !scaleInfo.canConvert()) { return false; } const qreal scale = scaleInfo.toDouble(); if (scale <= 0) { return false; } const QPoint pos = QPoint(posInfo[QStringLiteral("x")].toInt(), posInfo[QStringLiteral("y")].toInt()); QSize size = QSize(modeSize[QStringLiteral("width")].toInt() / scale, modeSize[QStringLiteral("height")].toInt() / scale); if (portrait) { size.transpose(); } geo = QRect(pos, size); return true; }; // it's guaranteed that we find the following values in the QMap KScreen::OutputPtr prevPtr = outputs.find(sortedOutputs[cnt - 1].first).value(); KScreen::OutputPtr curPtr = outputs.find(sortedOutputs[cnt].first).value(); QRect prevInfoGeo, curInfoGeo; if (!getOutputInfoProperties(prevPtr, prevInfoGeo) || !getOutputInfoProperties(curPtr, curInfoGeo)) { // no info found, nothing can be adjusted for the next output continue; } const QRect prevGeo = prevPtr->geometry(); const QRect curGeo = curPtr->geometry(); // the old difference between previous and current output read from the config file const int xInfoDiff = curInfoGeo.x() - (prevInfoGeo.x() + prevInfoGeo.width()); // the proposed new difference const int prevRight = prevGeo.x() + prevGeo.width(); const int xCorrected = prevRight + prevGeo.width() * xInfoDiff / (double)prevInfoGeo.width(); const int xDiff = curGeo.x() - prevRight; - // In the following calculate the y-correction. This is more involved since we + // In the following calculate the y-coorection. This is more involved since we // differentiate between overlapping and non-overlapping pairs and align either // top to top/bottom or bottom to top/bottom const bool yOverlap = prevInfoGeo.y() + prevInfoGeo.height() > curInfoGeo.y() && prevInfoGeo.y() < curInfoGeo.y() + curInfoGeo.height(); // these values determine which horizontal edge of previous output we align with const int topToTopDiffAbs = qAbs(prevInfoGeo.y() - curInfoGeo.y()); const int topToBottomDiffAbs = qAbs(prevInfoGeo.y() - curInfoGeo.y() - curInfoGeo.height()); const int bottomToBottomDiffAbs = qAbs(prevInfoGeo.y() + prevInfoGeo.height() - curInfoGeo.y() - curInfoGeo.height()); const int bottomToTopDiffAbs = qAbs(prevInfoGeo.y() + prevInfoGeo.height() - curInfoGeo.y()); const bool yTopAligned = topToTopDiffAbs < bottomToBottomDiffAbs && topToTopDiffAbs <= bottomToTopDiffAbs || topToBottomDiffAbs < bottomToBottomDiffAbs; int yInfoDiff = curInfoGeo.y() - prevInfoGeo.y(); int yDiff = curGeo.y() - prevGeo.y(); int yCorrected; if (yTopAligned) { // align to previous top if (!yOverlap) { // align previous top with current bottom yInfoDiff += curInfoGeo.height(); yDiff += curGeo.height(); } // When we align with previous top we are interested in the changes to the // current geometry and not in the ones of the previous one. const double yInfoRel = yInfoDiff / (double)curInfoGeo.height(); yCorrected = prevGeo.y() + yInfoRel * curGeo.height(); } else { // align previous bottom... yInfoDiff -= prevInfoGeo.height(); yDiff -= prevGeo.height(); yCorrected = prevGeo.y() + prevGeo.height(); if (yOverlap) { // ... with current bottom yInfoDiff += curInfoGeo.height(); yDiff += curGeo.height(); yCorrected -= curGeo.height(); } // ... else with current top // When we align with previous bottom we are interested in changes to the // previous geometry. const double yInfoRel = yInfoDiff / (double)prevInfoGeo.height(); yCorrected += yInfoRel * prevGeo.height(); } const int x = xDiff == xInfoDiff ? curGeo.x() : xCorrected; const int y = yDiff == yInfoDiff ? curGeo.y() : yCorrected; curPtr->setPos(QPoint(x, y)); } } void Output::readIn(KScreen::OutputPtr output, const QVariantMap &info, Control::OutputRetention retention) { const QVariantMap posInfo = info[QStringLiteral("pos")].toMap(); QPoint point(posInfo[QStringLiteral("x")].toInt(), posInfo[QStringLiteral("y")].toInt()); output->setPos(point); output->setPrimary(info[QStringLiteral("primary")].toBool()); output->setEnabled(info[QStringLiteral("enabled")].toBool()); if (retention != Control::OutputRetention::Individual && readInGlobal(output)) { // output data read from global output file return; } // output data read directly from info readInGlobalPartFromInfo(output, info); } void Output::readInOutputs(KScreen::ConfigPtr config, const QVariantList &outputsInfo) { KScreen::OutputList outputs = config->outputs(); ControlConfig control(config); // As global outputs are indexed by a hash of their edid, which is not unique, // to be able to tell apart multiple identical outputs, these need special treatment QStringList duplicateIds; { QStringList allIds; allIds.reserve(outputs.count()); for (const KScreen::OutputPtr &output : outputs) { const auto outputId = output->hash(); if (allIds.contains(outputId) && !duplicateIds.contains(outputId)) { duplicateIds << outputId; } allIds << outputId; } allIds.clear(); } for (KScreen::OutputPtr output : outputs) { if (!output->isConnected()) { output->setEnabled(false); continue; } const auto outputId = output->hash(); bool infoFound = false; for (const auto &variantInfo : outputsInfo) { const QVariantMap info = variantInfo.toMap(); if (outputId != info[QStringLiteral("id")].toString()) { continue; } if (!output->name().isEmpty() && duplicateIds.contains(outputId)) { // We may have identical outputs connected, these will have the same id in the config // in order to find the right one, also check the output's name (usually the connector) const auto metadata = info[QStringLiteral("metadata")].toMap(); const auto outputName = metadata[QStringLiteral("name")].toString(); if (output->name() != outputName) { // was a duplicate id, but info not for this output continue; } } infoFound = true; readIn(output, info, control.getOutputRetention(output)); const QString replicationSourceHash = info[QStringLiteral("replicate")].toString(); if (replicationSourceHash.isEmpty()) { output->setReplicationSource(0); } else { for (const KScreen::OutputPtr out : outputs) { if (out != output && out->hashMd5() == replicationSourceHash) { output->setReplicationSource(out->id()); break; } } } break; } if (!infoFound) { - // no info in info for this output, try reading in global output info at least or set some default values + // no info in info for this output, try reading in global output info atleast or set some default values qCWarning(KSCREEN_KDED) << "\tFailed to find a matching output in the current info data - this means that our info is corrupted" "or a different device with the same serial number has been connected (very unlikely)."; if (!readInGlobal(output)) { // set some default values instead readInGlobalPartFromInfo(output, QVariantMap()); } } } // correct positional config regressions on global output data changes adjustPositions(config, outputsInfo); } static QVariantMap metadata(const KScreen::OutputPtr &output) { QVariantMap metadata; metadata[QStringLiteral("name")] = output->name(); if (!output->edid() || !output->edid()->isValid()) { return metadata; } metadata[QStringLiteral("fullname")] = output->edid()->deviceId(); return metadata; } bool Output::writeGlobalPart(const KScreen::OutputPtr &output, QVariantMap &info, const KScreen::OutputPtr &fallback) { info[QStringLiteral("id")] = output->hash(); info[QStringLiteral("metadata")] = metadata(output); info[QStringLiteral("rotation")] = output->rotation(); // Round scale to four digits info[QStringLiteral("scale")] = int(output->scale() * 10000 + 0.5) / 10000.; QVariantMap modeInfo; float refreshRate = -1.; QSize modeSize; if (output->currentMode() && output->isEnabled()) { refreshRate = output->currentMode()->refreshRate(); modeSize = output->currentMode()->size(); } else if (fallback && fallback->currentMode()) { refreshRate = fallback->currentMode()->refreshRate(); modeSize = fallback->currentMode()->size(); } if (refreshRate < 0 || !modeSize.isValid()) { return false; } modeInfo[QStringLiteral("refresh")] = refreshRate; QVariantMap modeSizeMap; modeSizeMap[QStringLiteral("width")] = modeSize.width(); modeSizeMap[QStringLiteral("height")] = modeSize.height(); modeInfo[QStringLiteral("size")] = modeSizeMap; info[QStringLiteral("mode")] = modeInfo; return true; } void Output::writeGlobal(const KScreen::OutputPtr &output) { // get old values and subsequently override QVariantMap info = getGlobalData(output); if (!writeGlobalPart(output, info, nullptr)) { return; } QFile file(globalFileName(output->hashMd5())); if (!file.open(QIODevice::WriteOnly)) { qCWarning(KSCREEN_KDED) << "Failed to open global output file for writing! " << file.errorString(); return; } file.write(QJsonDocument::fromVariant(info).toJson()); return; } diff --git a/plasmoid/kscreenapplet.h b/plasmoid/kscreenapplet.h index 452b6b7..22b6e47 100644 --- a/plasmoid/kscreenapplet.h +++ b/plasmoid/kscreenapplet.h @@ -1,67 +1,67 @@ /* * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #pragma once #include #include class KScreenApplet : public Plasma::Applet { Q_OBJECT /** - * The number of currently connected (not necessarily enabled) outputs + * The number of currently connected (not neccessarily enabled) outputs */ Q_PROPERTY(int connectedOutputCount READ connectedOutputCount NOTIFY connectedOutputCountChanged) public: explicit KScreenApplet(QObject *parent, const QVariantList &data); ~KScreenApplet() override; enum Action { SwitchToExternal, SwitchToInternal, Clone, ExtendLeft, ExtendRight }; Q_ENUM(Action) void init() override; int connectedOutputCount() const; Q_INVOKABLE void applyLayoutPreset(Action action); Q_SIGNALS: void connectedOutputCountChanged(); private: void checkOutputs(); KScreen::ConfigPtr m_screenConfiguration; int m_connectedOutputCount = 0; }; diff --git a/plasmoid/package/metadata.desktop b/plasmoid/package/metadata.desktop index 7c35ad2..0b3e3d0 100644 --- a/plasmoid/package/metadata.desktop +++ b/plasmoid/package/metadata.desktop @@ -1,89 +1,89 @@ [Desktop Entry] Name=Display Configuration Name[ar]=اضبط العرض Name[bs]=Konfiguracija ekrana Name[ca]=Configuració de la pantalla Name[ca@valencia]=Configuració de la pantalla Name[cs]=Nastavení zobrazení Name[da]=Konfiguration af skærme Name[de]=Anzeige-Einrichtung Name[el]=Διαμόρφωση οθόνης Name[en_GB]=Display Configuration Name[es]=Configuración de la pantalla Name[et]=Ekraani seadistamine Name[eu]=Bistaratzaile konfigurazioa Name[fi]=Näyttöasetukset Name[fr]=Configuration de l'affichage Name[gl]=Configuración da pantalla Name[hu]=Kijelzőbeállítás Name[id]=Konfigurasi Displai Name[it]=Configurazione dello schermo Name[ko]=디스플레이 설정 Name[lt]=Ekrano konfigūracija Name[nl]=Instellingen van het scherm Name[nn]=Skjermoppsett Name[pa]=ਡਿਸਪਲੇਅ ਸੰਰਚਨਾ Name[pl]=Ustawienia wyświetlania Name[pt]=Configuração do Ecrã Name[pt_BR]=Configuração da tela Name[ro]=Configurare afișaj Name[ru]=Настройка экранов Name[sk]=Nastavenie obrazovky Name[sl]=Nastavitve zaslona Name[sv]=Inställning av bildskärm Name[tg]=Танзимоти экран Name[tr]=Ekran Yapılandırması Name[uk]=Налаштування дисплея Name[x-test]=xxDisplay Configurationxx Name[zh_CN]=显示配置 Name[zh_TW]=顯示設定 Comment=Quickly switch between screen layouts and presentation mode Comment[ca]=Canvia ràpidament les disposicions de la pantalla i el mode de presentació Comment[ca@valencia]=Canvia ràpidament les disposicions de la pantalla i el mode de presentació Comment[cs]=Rychlé přepínání mezi rozložením obrazovek a režimem prezentace Comment[da]=Skift hurtigt mellem skærmlayouts og præsentationstilstand Comment[de]=Schnell zwischen Bildschirmanordnungen und Präsentationsmodus wechseln Comment[el]=Γρήγορη εναλλαγή μεταξύ διατάξεων οθόνης και λειτουργίας παρουσίασης Comment[en_GB]=Quickly switch between screen layouts and presentation mode Comment[es]=Cambio rápido entre distribuciones de pantallas y modo de presentación Comment[et]=Kiire lülitumine ekraanipaigutuste ja esitlusrežiimi vahel Comment[eu]=Azkar aldatu pantaila antolamenduen eta aurkezpen moduaren artean Comment[fi]=Nopea vaihto eri näyttöasettelujen ja esitystilan välillä Comment[fr]=Basculer rapidement entre les dispositions d'écran et le mode « Présentation » Comment[gl]=Cambia rapidamente entre disposicións de pantalla e modo de presentación Comment[hu]=Gyors váltás a képernyőelrendezések és a prezentációs mód között Comment[id]=Beralih selekasnya antara tataletak layar dan mode presentasi Comment[it]=Passa rapidamente tra le disposizioni dello schermo e la modalità presentazione Comment[ko]=화면 레이아웃과 프레젠테이션 모드 빠르게 전환 Comment[lt]=Greitai perjungti tarp ekrano išdėstymų ir pristatymo veiksenos Comment[nl]=Snel wisselen tussen schermindelingen en presentatiemodus Comment[nn]=Byt raskt mellom skjermutformingar og presentasjonsmodus Comment[pl]=Szybkie przełączanie pomiędzy układami ekranu i trybami prezentacji Comment[pt]=Mudança rápida de formatos do ecrã e de modos de apresentação Comment[pt_BR]=Alterne rapidamente entre layouts de tela e modo de apresentação Comment[ru]=Быстрое переключение между конфигурациями экранов и режимом презентации Comment[sk]=Rýchlo prepnúť medzi rozloženiami obrazovky a prezentačným režimom Comment[sv]=Byt snabbt mellan skärmlayouter och presentationsläge Comment[tg]=Байни тарҳбандии экран ва реҷаи тақдим намоишро зуд иваз намоед Comment[uk]=Швидке перемикання між компонуваннями екрана та режимом презентації Comment[x-test]=xxQuickly switch between screen layouts and presentation modexx Comment[zh_CN]=在屏幕布局和演示模式之间快速切换 Comment[zh_TW]=快速切換螢幕配置和簡報模式 Icon=preferences-desktop-display-randr X-KDE-ServiceTypes=Plasma/Applet Type=Service X-Plasma-API=declarativeappletscript X-Plasma-MainScript=ui/main.qml X-KDE-PluginInfo-Author=Kai Uwe Broulik X-KDE-PluginInfo-Email=kde@broulik.de X-KDE-PluginInfo-Name=org.kde.kscreen X-KDE-PluginInfo-Version=1.0 -X-KDE-PluginInfo-Website=https://kde.org/plasma-desktop +X-KDE-PluginInfo-Website=http://plasma.kde.org/ X-KDE-PluginInfo-Category=Utilities X-KDE-PluginInfo-License=GPL X-Plasma-NotificationArea=true X-Plasma-NotificationAreaCategory=Hardware