diff --git a/common/globals.cpp b/common/globals.cpp index f67f852..cb987ef 100644 --- a/common/globals.cpp +++ b/common/globals.cpp @@ -1,35 +1,33 @@ /******************************************************************** 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, see . *********************************************************************/ #include "globals.h" -#include #include -#include QString Globals::s_dirPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) % QStringLiteral("/kscreen/"); QString Globals::dirPath() { return s_dirPath; } void Globals::setDirPath(const QString &path) { s_dirPath = path; if (!s_dirPath.endsWith(QLatin1Char('/'))) { s_dirPath += QLatin1Char('/'); } } diff --git a/console/console.cpp b/console/console.cpp index 9d9fa4c..c2fcaf0 100644 --- a/console/console.cpp +++ b/console/console.cpp @@ -1,225 +1,223 @@ /************************************************************************************* * 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 "console.h" #include -#include #include #include #include #include #include #include #include #include #include #include #include #include static QTextStream cout(stdout); namespace KScreen { namespace ConfigSerializer { // Exported private symbol in configserializer_p.h in KScreen extern QJsonObject serializeConfig(const KScreen::ConfigPtr &config); } } using namespace KScreen; Console::Console(const ConfigPtr &config) : QObject() , m_config(config) { } Console::~Console() { } -#include void Console::printConfig() { if (!m_config) { qDebug() << "Config is invalid, probably backend couldn't load"; return; } if (!m_config->screen()) { qDebug() << "No screen in the configuration, broken backend"; return; } connect(m_config.data(), &Config::primaryOutputChanged, [&](const OutputPtr &output) { if (output) { qDebug() << "New primary output: " << output->id() << output->name(); } else { qDebug() << "No primary output."; } }); qDebug() << "Screen:"; qDebug() << "\tmaxSize:" << m_config->screen()->maxSize(); qDebug() << "\tminSize:" << m_config->screen()->minSize(); qDebug() << "\tcurrentSize:" << m_config->screen()->currentSize(); OutputList outputs = m_config->outputs(); Q_FOREACH(const OutputPtr &output, outputs) { qDebug() << "\n-----------------------------------------------------\n"; qDebug() << "Id: " << output->id(); qDebug() << "Name: " << output->name(); qDebug() << "Type: " << typetoString(output->type()); qDebug() << "Connected: " << output->isConnected(); if (!output->isConnected()) { continue; } qDebug() << "Enabled: " << output->isEnabled(); qDebug() << "Primary: " << output->isPrimary(); qDebug() << "Rotation: " << output->rotation(); qDebug() << "Pos: " << output->pos(); qDebug() << "MMSize: " << output->sizeMm(); qDebug() << "FollowPreferredMode: " << output->followPreferredMode(); if (output->currentMode()) { qDebug() << "Size: " << output->size(); } qDebug() << "Scale: " << output->scale(); if (output->clones().isEmpty()) { qDebug() << "Clones: " << "None"; } else { qDebug() << "Clones: " << output->clones().count(); } qDebug() << "Mode: " << output->currentModeId(); qDebug() << "Preferred Mode: " << output->preferredModeId(); qDebug() << "Preferred modes: " << output->preferredModes(); qDebug() << "Modes: "; ModeList modes = output->modes(); Q_FOREACH(const ModePtr &mode, modes) { qDebug() << "\t" << mode->id() << " " << mode->name() << " " << mode->size() << " " << mode->refreshRate(); } Edid* edid = output->edid(); qDebug() << "EDID Info: "; if (edid && edid->isValid()) { qDebug() << "\tDevice ID: " << edid->deviceId(); qDebug() << "\tName: " << edid->name(); qDebug() << "\tVendor: " << edid->vendor(); qDebug() << "\tSerial: " << edid->serial(); qDebug() << "\tEISA ID: " << edid->eisaId(); qDebug() << "\tHash: " << edid->hash(); qDebug() << "\tWidth: " << edid->width(); qDebug() << "\tHeight: " << edid->height(); qDebug() << "\tGamma: " << edid->gamma(); qDebug() << "\tRed: " << edid->red(); qDebug() << "\tGreen: " << edid->green(); qDebug() << "\tBlue: " << edid->blue(); qDebug() << "\tWhite: " << edid->white(); } else { qDebug() << "\tUnavailable"; } } } QString Console::typetoString(const Output::Type& type) const { switch (type) { case Output::Unknown: return QStringLiteral("Unknown"); case Output::Panel: return QStringLiteral("Panel (Laptop)"); case Output::VGA: return QStringLiteral("VGA"); case Output::DVI: return QStringLiteral("DVI"); case Output::DVII: return QStringLiteral("DVI-I"); case Output::DVIA: return QStringLiteral("DVI-A"); case Output::DVID: return QStringLiteral("DVI-D"); case Output::HDMI: return QStringLiteral("HDMI"); case Output::TV: return QStringLiteral("TV"); case Output::TVComposite: return QStringLiteral("TV-Composite"); case Output::TVSVideo: return QStringLiteral("TV-SVideo"); case Output::TVComponent: return QStringLiteral("TV-Component"); case Output::TVSCART: return QStringLiteral("TV-SCART"); case Output::TVC4: return QStringLiteral("TV-C4"); case Output::DisplayPort: return QStringLiteral("DisplayPort"); }; return QStringLiteral("Invalid Type") + QString::number(type); } void Console::printJSONConfig() { QJsonDocument doc(KScreen::ConfigSerializer::serializeConfig(m_config)); cout << doc.toJson(QJsonDocument::Indented); } void Console::printSerializations() { QString path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kscreen/"); qDebug() << "Configs in: " << path; QDir dir(path); QStringList files = dir.entryList(QDir::Files); qDebug() << "Number of files: " << files.count() << endl; QJsonDocument parser; Q_FOREACH(const QString fileName, files) { QJsonParseError error; qDebug() << fileName; QFile file(path + QLatin1Char('/') + fileName); file.open(QFile::ReadOnly); QVariant data = parser.fromJson(file.readAll(), &error); if (error.error != QJsonParseError::NoError) { qDebug() << " " << "can't parse file"; qDebug() << " " << error.errorString(); continue; } qDebug() << parser.toJson(QJsonDocument::Indented) << endl; } } void Console::monitor() { ConfigMonitor::instance()->addConfig(m_config); } void Console::monitorAndPrint() { monitor(); connect(ConfigMonitor::instance(), &ConfigMonitor::configurationChanged, this, &Console::printConfig); } diff --git a/kcm/src/declarative/qmloutput.cpp b/kcm/src/declarative/qmloutput.cpp index aab2922..0d99a5c 100644 --- a/kcm/src/declarative/qmloutput.cpp +++ b/kcm/src/declarative/qmloutput.cpp @@ -1,589 +1,588 @@ /* Copyright (C) 2012 Dan Vratil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "qmloutput.h" #include "qmlscreen.h" #include #include -#include #include #include const static int sMargin = 0; const static int sSnapArea = 20; const static int sSnapAlignArea = 6; Q_DECLARE_METATYPE(KScreen::ModePtr) bool operator>(const QSize &sizeA, const QSize &sizeB) { return ((sizeA.width() > sizeB.width()) && (sizeA.height() > sizeB.height())); } QMLOutput::QMLOutput(QQuickItem *parent): QQuickItem(parent), m_screen(nullptr), m_cloneOf(nullptr), m_leftDock(nullptr), m_topDock(nullptr), m_rightDock(nullptr), m_bottomDock(nullptr), m_isCloneMode(false) { connect(this, &QMLOutput::xChanged, this, static_cast(&QMLOutput::moved)); connect(this, &QMLOutput::yChanged, this, static_cast(&QMLOutput::moved)); } KScreen::Output* QMLOutput::output() const { return m_output.data(); } KScreen::OutputPtr QMLOutput::outputPtr() const { return m_output; } void QMLOutput::setOutputPtr(const KScreen::OutputPtr &output) { Q_ASSERT(m_output.isNull()); m_output = output; Q_EMIT outputChanged(); connect(m_output.data(), &KScreen::Output::rotationChanged, this, &QMLOutput::updateRootProperties); connect(m_output.data(), &KScreen::Output::currentModeIdChanged, this, &QMLOutput::currentModeIdChanged); connect(m_output.data(), &KScreen::Output::scaleChanged, this, &QMLOutput::currentModeIdChanged); } QMLScreen *QMLOutput::screen() const { return m_screen; } void QMLOutput::setScreen(QMLScreen *screen) { Q_ASSERT(m_screen == nullptr); m_screen = screen; Q_EMIT screenChanged(); } void QMLOutput::setLeftDockedTo(QMLOutput *output) { if (m_leftDock == output) { return; } m_leftDock = output; Q_EMIT leftDockedToChanged(); } QMLOutput *QMLOutput::leftDockedTo() const { return m_leftDock; } void QMLOutput::undockLeft() { setLeftDockedTo(nullptr); } void QMLOutput::setTopDockedTo(QMLOutput *output) { if (m_topDock == output) { return; } m_topDock = output; Q_EMIT topDockedToChanged(); } QMLOutput *QMLOutput::topDockedTo() const { return m_topDock; } void QMLOutput::undockTop() { setTopDockedTo(nullptr); } void QMLOutput::setRightDockedTo(QMLOutput *output) { if (m_rightDock == output) { return; } m_rightDock = output; Q_EMIT rightDockedToChanged(); } QMLOutput *QMLOutput::rightDockedTo() const { return m_rightDock; } void QMLOutput::undockRight() { setRightDockedTo(nullptr); } void QMLOutput::setBottomDockedTo(QMLOutput *output) { if (m_bottomDock == output) { return; } m_bottomDock = output; Q_EMIT bottomDockedToChanged(); } QMLOutput *QMLOutput::bottomDockedTo() const { return m_bottomDock; } void QMLOutput::undockBottom() { setBottomDockedTo(nullptr); } void QMLOutput::setCloneOf(QMLOutput* other) { if (m_cloneOf == other) { return; } m_cloneOf = other; Q_EMIT cloneOfChanged(); } QMLOutput* QMLOutput::cloneOf() const { return m_cloneOf; } int QMLOutput::currentOutputHeight() const { if (!m_output) { return 0; } KScreen::ModePtr mode = m_output->currentMode(); if (!mode) { if (m_output->isConnected()) { mode = bestMode(); if (!mode) { return 1000; } m_output->setCurrentModeId(mode->id()); } else { return 1000; } } return mode->size().height() / m_output->scale(); } int QMLOutput::currentOutputWidth() const { if (!m_output) { return 0; } KScreen::ModePtr mode = m_output->currentMode(); if (!mode) { if (m_output->isConnected()) { mode = bestMode(); if (!mode) { return 1000; } m_output->setCurrentModeId(mode->id()); } else { return 1000; } } return mode->size().width() / m_output->scale(); } void QMLOutput::currentModeIdChanged() { if (!m_output) { return; } if (isCloneMode()) { const float newWidth = currentOutputWidth() * m_screen->outputScale(); setX((m_screen->width() - newWidth) / 2); const float newHeight = currentOutputHeight() * m_screen->outputScale(); setY((m_screen->height() - newHeight) / 2); } else { if (m_rightDock) { QMLOutput *rightDock = m_rightDock; float newWidth = currentOutputWidth() * m_screen->outputScale(); setX(rightDock->x() - newWidth); setRightDockedTo(rightDock); } if (m_bottomDock) { QMLOutput *bottomDock = m_bottomDock; float newHeight = currentOutputHeight() * m_screen->outputScale(); setY(bottomDock->y() - newHeight); setBottomDockedTo(bottomDock); } } Q_EMIT currentOutputSizeChanged(); } int QMLOutput::outputX() const { return m_output->pos().x(); } void QMLOutput::setOutputX(int x) { if (m_output->pos().rx() == x) { return; } QPoint pos = m_output->pos(); pos.setX(x); m_output->setPos(pos); Q_EMIT outputXChanged(); } int QMLOutput::outputY() const { return m_output->pos().y(); } void QMLOutput::setOutputY(int y) { if (m_output->pos().ry() == y) { return; } QPoint pos = m_output->pos(); pos.setY(y); m_output->setPos(pos); Q_EMIT outputYChanged(); } bool QMLOutput::isCloneMode() const { return m_isCloneMode; } void QMLOutput::setIsCloneMode(bool isCloneMode) { if (m_isCloneMode == isCloneMode) { return; } m_isCloneMode = isCloneMode; Q_EMIT isCloneModeChanged(); } void QMLOutput::dockToNeighbours() { Q_FOREACH (QMLOutput *otherQmlOutput, m_screen->outputs()) { if (otherQmlOutput == this) { continue; } if (!otherQmlOutput->output()->isConnected() || !otherQmlOutput->output()->isEnabled()) { continue; } const QRect geom = m_output->geometry(); const QRect otherGeom = otherQmlOutput->output()->geometry(); if (geom.left() - 1 == otherGeom.right()) { setLeftDockedTo(otherQmlOutput); continue; } if (geom.right() + 1 == otherGeom.left()) { setRightDockedTo(otherQmlOutput); continue; } if (geom.top() - 1 == otherGeom.bottom()) { setTopDockedTo(otherQmlOutput); continue; } if (geom.bottom() + 1 == otherGeom.top()) { setBottomDockedTo(otherQmlOutput); continue; } } } KScreen::ModePtr QMLOutput::bestMode() const { if (!m_output) { return KScreen::ModePtr(); } KScreen::ModeList modes = m_output->modes(); KScreen::ModePtr bestMode; Q_FOREACH (const KScreen::ModePtr &mode, modes) { if (!bestMode || (mode->size() > bestMode->size())) { bestMode = mode; } } return bestMode; } bool QMLOutput::collidesWithOutput(QObject *other) { QQuickItem* otherItem = qobject_cast(other); return boundingRect().intersects(otherItem->boundingRect()); } bool QMLOutput::maybeSnapTo(QMLOutput *other) { qreal centerX = x() + (width() / 2.0); qreal centerY = y() + (height() / 2.0); const qreal x2 = other->x(); const qreal y2 = other->y(); const qreal height2 = other->height(); const qreal width2 = other->width(); const qreal centerX2 = x2 + (width2 / 2.0); const qreal centerY2 = y2 + (height2 / 2.0); /* left of other */ if ((x() + width() > x2 - sSnapArea) && (x() + width() < x2 + sSnapArea) && (y() + height() > y2) && (y() < y2 + height2)) { setX(x2 - width() + sMargin); centerX = x() + (width() / 2.0); setRightDockedTo(other); other->setLeftDockedTo(this); //output.cloneOf = null; /* output is snapped to other on left and their * upper sides are aligned */ if ((y() < y2 + sSnapAlignArea) && (y() > y2 - sSnapAlignArea)) { setY(y2); return true; } /* output is snapped to other on left and they * are centered */ if ((centerY < centerY2 + sSnapAlignArea) && (centerY > centerY2 - sSnapAlignArea)) { setY(centerY2 - (height() / 2.0)); return true; } /* output is snapped to other on left and their * bottom sides are aligned */ if ((y() + height() < y2 + height2 + sSnapAlignArea) && (y() + height() > y2 + height2 - sSnapAlignArea)) { setY(y2 + height2 - height()); return true; } return true; } /* output is right of other */ if ((x() > x2 + width2 - sSnapArea) && (x() < x2 + width2 + sSnapArea) && (y() + height() > y2) && (y() < y2 + height2)) { setX(x2 + width2 - sMargin); centerX = x() + (width() / 2.0); setLeftDockedTo(other); other->setRightDockedTo(this); //output.cloneOf = null; /* output is snapped to other on right and their * upper sides are aligned */ if ((y() < y2 + sSnapAlignArea) && (y() > y2 - sSnapAlignArea)) { setY(y2); return true; } /* output is snapped to other on right and they * are centered */ if ((centerY < centerY2 + sSnapAlignArea) && (centerY > centerY2 - sSnapAlignArea)) { setY(centerY2 - (height() / 2.0)); return true; } /* output is snapped to other on right and their * bottom sides are aligned */ if ((y() + height() < y2 + height2 + sSnapAlignArea) && (y() + height() > y2 + height2 - sSnapAlignArea)) { setY(y2 + height2 - height()); return true; } return true; } /* output is above other */ if ((y() + height() > y2 - sSnapArea) && (y() + height() < y2 + sSnapArea) && (x() + width() > x2) && (x() < x2 + width2)) { setY(y2 - height() + sMargin); centerY = y() + (height() / 2.0); setBottomDockedTo(other); other->setTopDockedTo(this); //output.cloneOf = null; /* output is snapped to other on top and their * left sides are aligned */ if ((x() < x2 + sSnapAlignArea) && (x() > x2 - sSnapAlignArea)) { setX(x2); return true; } /* output is snapped to other on top and they * are centered */ if ((centerX < centerX2 + sSnapAlignArea) && (centerX > centerX2 - sSnapAlignArea)) { setX(centerX2 - (width() / 2.0)); return true; } /* output is snapped to other on top and their * right sides are aligned */ if ((x() + width() < x2 + width2 + sSnapAlignArea) && (x() + width() > x2 + width2 - sSnapAlignArea)) { setX(x2 + width2 - width()); return true; } return true; } /* output is below other */ if ((y() > y2 + height2 - sSnapArea) && (y() < y2 + height2 + sSnapArea) && (x() + width() > x2) && (x() < x2 + width2)) { setY(y2 + height2 - sMargin); centerY = y() + (height() / 2.0); setTopDockedTo(other); other->setBottomDockedTo(this); //output.cloneOf = null; /* output is snapped to other on bottom and their * left sides are aligned */ if ((x() < x2 + sSnapAlignArea) && (x() > x2 - sSnapAlignArea)) { setX(x2); return true; } /* output is snapped to other on bottom and they * are centered */ if ((centerX < centerX2 + sSnapAlignArea) && (centerX > centerX2 - sSnapAlignArea)) { setX(centerX2 - (width() / 2.0)); return true; } /* output is snapped to other on bottom and their * right sides are aligned */ if ((x() + width() < x2 + width2 + sSnapAlignArea) && (x() + width() > x2 + width2 - sSnapAlignArea)) { setX(x2 + width2 - width()); return true; } return true; } return false; } void QMLOutput::moved() { const QList siblings = screen()->childItems(); // First, if we have moved, then unset the "cloneOf" flag setCloneOf(nullptr); disconnect(this, &QMLOutput::xChanged, this, static_cast(&QMLOutput::moved)); disconnect(this, &QMLOutput::yChanged, this, static_cast(&QMLOutput::moved)); Q_FOREACH (QQuickItem *sibling, siblings) { QMLOutput *otherOutput = qobject_cast(sibling); if (!otherOutput || otherOutput == this) { continue; } if (!maybeSnapTo(otherOutput)) { if (m_leftDock == otherOutput) { m_leftDock->undockRight(); undockLeft(); } if (m_topDock == otherOutput) { m_topDock->undockBottom(); undockTop(); } if (m_rightDock == otherOutput) { m_rightDock->undockLeft(); undockRight(); } if (m_bottomDock == otherOutput) { m_bottomDock->undockTop(); undockBottom(); } } } connect(this, &QMLOutput::xChanged, this, static_cast(&QMLOutput::moved)); connect(this, &QMLOutput::yChanged, this, static_cast(&QMLOutput::moved)); Q_EMIT moved(m_output->name()); } /* Transformation of an item (rotation of the MouseArea) is only visual. * The coordinates and dimensions are still the same (when you rotated * 100x500 rectangle by 90 deg, it will still be 100x500, although * visually it will be 500x100). * * This method calculates the real-visual coordinates and dimensions of * the MouseArea and updates root item to match them. This makes snapping * work correctly regardless off visual rotation of the output */ void QMLOutput::updateRootProperties() { const float transformedWidth = (m_output->isHorizontal() ? currentOutputWidth() : currentOutputHeight()) * m_screen->outputScale(); const float transformedHeight = (m_output->isHorizontal() ? currentOutputHeight() : currentOutputWidth()) * m_screen->outputScale(); const float transformedX = x() + (width() / 2.0) - (transformedWidth / 2.0); const float transformedY = y() + (height() / 2.0) - (transformedHeight / 2.0); setPosition(QPointF(transformedX, transformedY)); setSize(QSizeF(transformedWidth, transformedHeight)); } diff --git a/kcm/src/declarative/qmloutputcomponent.cpp b/kcm/src/declarative/qmloutputcomponent.cpp index 4dc6838..35f9b11 100644 --- a/kcm/src/declarative/qmloutputcomponent.cpp +++ b/kcm/src/declarative/qmloutputcomponent.cpp @@ -1,62 +1,61 @@ /* * Copyright (C) 2013 Daniel Vrátil * * 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 "qmloutputcomponent.h" #include "qmloutput.h" #include "qmlscreen.h" #include -#include #include #include Q_DECLARE_METATYPE(KScreen::OutputPtr) Q_DECLARE_METATYPE(QMLScreen*) QMLOutputComponent::QMLOutputComponent(QQmlEngine *engine, QMLScreen *parent): QQmlComponent(engine, parent), m_engine(engine) { const QString qmlPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kcm_kscreen/qml/Output.qml")); loadUrl(QUrl::fromLocalFile(qmlPath)); } QMLOutputComponent::~QMLOutputComponent() { } QMLOutput* QMLOutputComponent::createForOutput(const KScreen::OutputPtr &output) { QObject *instance = beginCreate(m_engine->rootContext()); if (!instance) { qWarning() << errorString(); return nullptr; } bool success = instance->setProperty("outputPtr", QVariant::fromValue(output)); Q_ASSERT(success); success = instance->setProperty("screen", QVariant::fromValue(qobject_cast(parent()))); Q_ASSERT(success); Q_UNUSED(success); completeCreate(); return qobject_cast(instance); } diff --git a/kcm/src/kcm_kscreen.cpp b/kcm/src/kcm_kscreen.cpp index 4e8766e..41af66e 100644 --- a/kcm/src/kcm_kscreen.cpp +++ b/kcm/src/kcm_kscreen.cpp @@ -1,189 +1,188 @@ /* Copyright (C) 2012 Dan Vratil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "kcm_kscreen.h" #include "kcm_screen_debug.h" #include "widget.h" #include #include #include #include #include #include #include #include #include -#include #include #include #include #include #include K_PLUGIN_FACTORY(KCMDisplayConfigurationFactory, registerPlugin();) using namespace KScreen; Q_DECLARE_METATYPE(KScreen::OutputPtr) Q_DECLARE_METATYPE(KScreen::ScreenPtr) KCMKScreen::KCMKScreen(QWidget* parent, const QVariantList& args) : KCModule(parent, args) { Log::instance(); setButtons(Apply | Default); KAboutData* about = new KAboutData(QStringLiteral("kcm_kscreen"), i18n("Display Configuration"), QStringLiteral(KSCREEN_VERSION), i18n("Configuration for displays"), KAboutLicense::GPL_V2, i18n("(c), 2012-2013 Daniel Vrátil")); about->addAuthor(i18n("Daniel Vrátil"), i18n("Maintainer") , QStringLiteral("dvratil@redhat.com")); setAboutData(about); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); } void KCMKScreen::configReady(ConfigOperation* op) { delete mMainLayout; mMainLayout = new QHBoxLayout(this); mMainLayout->setContentsMargins(0, 0, 0, 0); if (op->hasError()) { mKScreenWidget = nullptr; delete mKScreenWidget; QLabel *errorLabel = new QLabel(this); mMainLayout->addWidget(errorLabel); errorLabel->setText(i18n("No kscreen backend found. Please check your kscreen installation.")); return; } if (!mKScreenWidget) { mKScreenWidget = new Widget(this); mMainLayout->addWidget(mKScreenWidget); QObject::connect(mKScreenWidget, &Widget::changed, this, &KCMKScreen::changed); } else { mMainLayout->addWidget(mKScreenWidget); } mKScreenWidget->setConfig(qobject_cast(op)->config()); } KCMKScreen::~KCMKScreen() { } QSize KCMKScreen::sizeHint() const { return QSize(0, 700); } void KCMKScreen::changed() { if (!m_blockChanges) { KCModule::changed(); } } void KCMKScreen::save() { qCDebug(KSCREEN_KCM) << "Saving."; if (!mKScreenWidget) { return; } mKScreenWidget->saveControls(); const KScreen::ConfigPtr &config = mKScreenWidget->currentConfig(); bool atLeastOneEnabledOutput = false; Q_FOREACH(const KScreen::OutputPtr &output, config->outputs()) { KScreen::ModePtr mode = output->currentMode(); if (output->isEnabled()) { atLeastOneEnabledOutput = true; } qCDebug(KSCREEN_KCM) << output->name() << output->id() << output.data() << "\n" << " Connected:" << output->isConnected() << "\n" << " Enabled:" << output->isEnabled() << "\n" << " Primary:" << output->isPrimary() << "\n" << " Rotation:" << output->rotation() << "\n" << " Mode:" << (mode ? mode->name() : QStringLiteral("unknown")) << "@" << (mode ? mode->refreshRate() : 0.0) << "Hz" << "\n" << " Position:" << output->pos().x() << "x" << output->pos().y(); } if (!atLeastOneEnabledOutput) { if (KMessageBox::warningYesNo(this, i18n("Are you sure you want to disable all outputs?"), i18nc("@title:window", "Disable All Outputs"), KGuiItem(i18n("&Disable All Outputs"), QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))), KGuiItem(i18n("&Reconfigure"), QIcon::fromTheme(QStringLiteral("dialog-cancel"))), QString(), KMessageBox::Dangerous) == KMessageBox::No) { return; } } if (!Config::canBeApplied(config)) { KMessageBox::information(this, i18n("Sorry, your configuration could not be applied.\n\n" "Common reasons are that the overall screen size is too big, or you enabled more displays than supported by your GPU."), i18nc("@title:window", "Unsupported Configuration")); return; } m_blockChanges = true; /* Store the current config, apply settings */ auto *op = new SetConfigOperation(config); /* Block until the operation is completed, otherwise KCMShell will terminate * before we get to execute the Operation */ op->exec(); // The 1000ms is a bit "random" here, it's what works on the systems I've tested, but ultimately, this is a hack // due to the fact that we just can't be sure when xrandr is done changing things, 1000 doesn't seem to get in the way QTimer::singleShot(1000, this, [this] () { m_blockChanges = false; } ); } void KCMKScreen::defaults() { qCDebug(KSCREEN_KCM) << "APPLY DEFAULT"; load(); } void KCMKScreen::load() { qCDebug(KSCREEN_KCM) << "LOAD"; connect(new GetConfigOperation(), &GetConfigOperation::finished, this, &KCMKScreen::configReady); } #include "kcm_kscreen.moc" diff --git a/kcm/src/outputconfig.cpp b/kcm/src/outputconfig.cpp index 6264310..c30e47c 100644 --- a/kcm/src/outputconfig.cpp +++ b/kcm/src/outputconfig.cpp @@ -1,286 +1,284 @@ /* * Copyright 2013 Daniel Vrátil * * 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 . * */ #include "outputconfig.h" #include "resolutionslider.h" #include "utils.h" #include "kcm_screen_debug.h" -#include #include #include #include -#include #include #include #include #include #include #include #include OutputConfig::OutputConfig(QWidget *parent) : QWidget(parent) , mOutput(nullptr) { } OutputConfig::OutputConfig(const KScreen::OutputPtr &output, QWidget *parent) : QWidget(parent) { setOutput(output); } OutputConfig::~OutputConfig() { } void OutputConfig::setTitle(const QString& title) { mTitle->setText(title); } void OutputConfig::initUi() { connect(mOutput.data(), &KScreen::Output::isConnectedChanged, this, [=]() { if (!mOutput->isConnected()) { setVisible(false); } }); connect(mOutput.data(), &KScreen::Output::isEnabledChanged, this, [=]() { mEnabled->setChecked(mOutput->isEnabled()); }); connect(mOutput.data(), &KScreen::Output::rotationChanged, this, [=]() { const int index = mRotation->findData(mOutput->rotation()); mRotation->setCurrentIndex(index); }); connect(mOutput.data(), &KScreen::Output::scaleChanged, this, [=]() { const int index = mScale->findData(mOutput->scale()); mScale->setCurrentIndex(index); }); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); QVBoxLayout *vbox = new QVBoxLayout(this); mTitle = new QLabel(this); mTitle->setAlignment(Qt::AlignHCenter); vbox->addWidget(mTitle); setTitle(Utils::outputName(mOutput)); QFormLayout *formLayout = new QFormLayout(); vbox->addLayout(formLayout); mEnabled = new QCheckBox(i18n("Enabled"), this); mEnabled->setChecked(mOutput->isEnabled()); connect(mEnabled, &QCheckBox::clicked, this, [=](bool checked) { mOutput->setEnabled(checked); qCDebug(KSCREEN_KCM) << mOutput.data() << mOutput->name() << mOutput->isEnabled(); mChanged = true; Q_EMIT changed(); }); formLayout->addRow(i18n("Display:"), mEnabled); mResolution = new ResolutionSlider(mOutput, this); connect(mResolution, &ResolutionSlider::resolutionChanged, this, &OutputConfig::slotResolutionChanged); formLayout->addRow(i18n("Resolution:"), mResolution); mRotation = new QComboBox(this); QIcon previewIcon = QIcon::fromTheme(QStringLiteral("view-preview")); QPixmap previewPixmap = previewIcon.pixmap(mRotation->iconSize()); QIcon previewRotatedCounterClockwise = QIcon(previewPixmap.transformed(QMatrix(0.0, -1.0, 1.0, 0.0, 0.0, 0.0))); QIcon previewRotatedClockwise = QIcon(previewPixmap.transformed(QMatrix(0.0, 1.0, -1.0, 0.0, 0.0, 0.0))); QIcon previewRotatedUpSideDown = QIcon(previewPixmap.transformed(QMatrix(-1.0, 0.0, 0.0, -1.0, 0.0, 0.0))); mRotation->addItem(previewIcon, i18n("No Rotation"), KScreen::Output::None); mRotation->addItem(previewRotatedClockwise, i18n("90° Clockwise"), KScreen::Output::Right); mRotation->addItem(previewRotatedUpSideDown, i18n("Upside Down"), KScreen::Output::Inverted); mRotation->addItem(previewRotatedCounterClockwise, i18n("90° Counterclockwise"), KScreen::Output::Left); connect(mRotation, static_cast(&QComboBox::activated), this, &OutputConfig::slotRotationChanged); mRotation->setCurrentIndex(mRotation->findData(mOutput->rotation())); formLayout->addRow(i18n("Orientation:"), mRotation); if (mShowScaleOption) { mScale = new QComboBox(this); mScale->addItem(i18nc("Scale multiplier, show everything at 1 times normal scale", "1x"), 1); mScale->addItem(i18nc("Scale multiplier, show everything at 2 times normal scale", "2x"), 2); connect(mScale, static_cast(&QComboBox::activated), this, &OutputConfig::slotScaleChanged); mScale->setCurrentIndex(mScale->findData(mOutput->scale())); formLayout->addRow(i18n("Scale:"), mScale); formLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum)); } mRefreshRate = new QComboBox(this); mRefreshRate->addItem(i18n("Auto"), -1); formLayout->addRow(i18n("Refresh rate:"), mRefreshRate); slotResolutionChanged(mResolution->currentResolution()); connect(mRefreshRate, static_cast(&QComboBox::activated), this, &OutputConfig::slotRefreshRateChanged); mRetentionGroupBox = new QGroupBox(i18n("Retention of values"), this); mGlobalRetentionButton = new QRadioButton(i18n("Save as new global values for this display."), this); mIndividualRetentionButton = new QRadioButton(i18n("Save values only for display in this specific configuration."), this); mIndividualRetentionButton->setChecked(mRetention == Control::OutputRetention::Individual); mGlobalRetentionButton->setChecked(!mIndividualRetentionButton->isChecked()); QVBoxLayout *vbox2 = new QVBoxLayout(mRetentionGroupBox); vbox2->addWidget(mGlobalRetentionButton); vbox2->addWidget(mIndividualRetentionButton); mRetentionGroupBox->setLayout(vbox2); vbox->addWidget(mRetentionGroupBox); } void OutputConfig::setOutput(const KScreen::OutputPtr &output, Control::OutputRetention retention) { mOutput = output; mRetention = retention; initUi(); } KScreen::OutputPtr OutputConfig::output() const { return mOutput; } void OutputConfig::slotResolutionChanged(const QSize &size) { // Ignore disconnected outputs if (!size.isValid()) { return; } KScreen::ModePtr selectedMode; QList modes; Q_FOREACH (const KScreen::ModePtr &mode, mOutput->modes()) { if (mode->size() == size) { modes << mode; if (!selectedMode || selectedMode->refreshRate() < mode->refreshRate()) { selectedMode = mode; } } } Q_ASSERT(selectedMode); mOutput->setCurrentModeId(selectedMode->id()); // Don't remove the first "Auto" item - prevents ugly flicker of the combobox // when changing resolution for (int i = 1; i < mRefreshRate->count(); ++i) { mRefreshRate->removeItem(i); } for (int i = 0, total = modes.count(); i < total; ++i) { const KScreen::ModePtr mode = modes.at(i); mRefreshRate->addItem(i18n("%1 Hz", QLocale().toString(mode->refreshRate(), 'f', 2)), mode->id()); // If selected refresh rate is other then what we consider the "Auto" value // - that is it's not the highest resolution - then select it, otherwise // we stick with "Auto" if (mode == selectedMode && i > 1) { // i + 1 since 0 is auto mRefreshRate->setCurrentIndex(i + 1); } } mChanged = true; Q_EMIT changed(); } void OutputConfig::slotRotationChanged(int index) { KScreen::Output::Rotation rotation = static_cast(mRotation->itemData(index).toInt()); mOutput->setRotation(rotation); mChanged = true; Q_EMIT changed(); } void OutputConfig::slotRefreshRateChanged(int index) { QString modeId; if (index == 0) { // Item 0 is "Auto" - "Auto" is equal to highest refresh rate (at least // that's how I understand it, and since the combobox is sorted in descending // order, we just pick the second item from top modeId = mRefreshRate->itemData(1).toString(); } else { modeId = mRefreshRate->itemData(index).toString(); } mOutput->setCurrentModeId(modeId); mChanged = true; Q_EMIT changed(); } void OutputConfig::slotScaleChanged(int index) { auto scale = mScale->itemData(index).toInt(); mOutput->setScale(scale); mChanged = true; Q_EMIT changed(); } void OutputConfig::setShowScaleOption(bool showScaleOption) { mShowScaleOption = showScaleOption; if (mOutput) { initUi(); } } bool OutputConfig::showScaleOption() const { return mShowScaleOption; } Control::OutputRetention OutputConfig::applyRetention() { if (mIndividualRetentionButton->isChecked()) { mRetention = Control::OutputRetention::Individual; } else { mRetention = Control::OutputRetention::Global; } return mRetention; } bool OutputConfig::hasChange() const { return mChanged; } diff --git a/kcm/src/previewwidget.cpp b/kcm/src/previewwidget.cpp index 8f1a9fd..6683b0a 100644 --- a/kcm/src/previewwidget.cpp +++ b/kcm/src/previewwidget.cpp @@ -1,87 +1,86 @@ /* * Copyright (C) 2015 David Edmundson * * 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 . * */ #include "previewwidget.h" -#include #include #include "ui_stylepreview.h" PreviewWidget::PreviewWidget(QWidget *parent): QLabel(parent), m_scale(1.0), m_internalPreview(new QWidget) // deliberately no parent, we don't want it to have a screen { Ui::StylePreview ui; ui.setupUi(m_internalPreview); } PreviewWidget::~PreviewWidget() { delete m_internalPreview; } void PreviewWidget::setScale(qreal scale) { m_scale = scale; QFont font; //take the user's configured point size, and convert it to a pixel size for preview font.setPixelSize(pointSizeToPixelSize(font.pointSize())); m_internalPreview->setFont(font); //as we are a hidden widget, we need to force a repaint to update the size hint properly updatePixmapCache(); m_internalPreview->resize(sizeHint()); m_internalPreview->adjustSize(); QPixmap preview = updatePixmapCache(); setPixmap(preview); } qreal PreviewWidget::pointSizeToPixelSize(qreal pointSize) const { //point size is in how many 72ths of an inch it should be, default DPI is 96 qreal pixelSize = pointSize * 96.0 / 72.0; //scale by our new factor pixelSize *= m_scale; return pixelSize / m_scale; //as we are now dealing with pixels it will be scaled up in the paint(), so it needs dividing here } QPixmap PreviewWidget::updatePixmapCache() { QPixmap pixmap(m_internalPreview ->sizeHint() * m_scale); pixmap.setDevicePixelRatio(m_scale); QPainter p(&pixmap); m_internalPreview ->render(&p); //render back at whatever the native DPR of the KCM is pixmap.setDevicePixelRatio(devicePixelRatioF()); return pixmap; } diff --git a/kcm/src/resolutionslider.h b/kcm/src/resolutionslider.h index cc45376..146263e 100644 --- a/kcm/src/resolutionslider.h +++ b/kcm/src/resolutionslider.h @@ -1,65 +1,64 @@ /* * Copyright 2013 Daniel Vrátil * * 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 . * */ #ifndef RESOLUTIONSLIDER_H #define RESOLUTIONSLIDER_H #include -#include #include class QSlider; class QLabel; class QComboBox; class ResolutionSlider : public QWidget { Q_OBJECT public: explicit ResolutionSlider(const KScreen::OutputPtr &output, QWidget *parent = nullptr); ~ResolutionSlider() override; QSize currentResolution() const; Q_SIGNALS: void resolutionChanged(const QSize &size); private Q_SLOTS: void slotValueChanged(int); void slotOutputModeChanged(); private: void init(); KScreen::OutputPtr mOutput; QList mModes; QLabel *mSmallestLabel = nullptr; QLabel *mBiggestLabel = nullptr; QLabel *mCurrentLabel = nullptr; QSlider *mSlider = nullptr; QComboBox *mComboBox = nullptr; }; #endif // RESOLUTIONSLIDER_H diff --git a/kcm/src/scalingconfig.cpp b/kcm/src/scalingconfig.cpp index 53408f0..380b18d 100644 --- a/kcm/src/scalingconfig.cpp +++ b/kcm/src/scalingconfig.cpp @@ -1,131 +1,129 @@ /* * Copyright (C) 2015 David Edmundson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "scalingconfig.h" #include -#include #include #include - #include //we want a scale between 1 and 3.0 in intervals of 0.1 //slider can only handle ints so goes 10-30 #define SLIDER_RATIO 10.0 ScalingConfig::ScalingConfig(const KScreen::OutputList &outputList, QWidget* parent): QDialog(parent), m_outputList(outputList) { ui.setupUi(this); ui.warningWidget->setText(i18n("Scaling changes will come into effect after restart")); ui.warningWidget->show(); connect(ui.scaleSlider, &QSlider::valueChanged, ui.previewWidget, [this](qreal value) { ui.previewWidget->setScale(value / SLIDER_RATIO); }); connect(ui.scaleSlider, &QSlider::valueChanged, ui.scaleLabel, [this](qreal value) { ui.scaleLabel->setText(QString::number(value / SLIDER_RATIO)); }); ui.previewWidget->setScale(1); ui.scaleLabel->setText(QString::number(1)); load(); } ScalingConfig::~ScalingConfig() { } void ScalingConfig::load() { //we load UI from a config, as rdb value might not be updated yet auto config = KSharedConfig::openConfig(QStringLiteral("kdeglobals")); const qreal dpi = config->group("KScreen").readEntry("ScaleFactor", 1.0); m_initialScalingFactor = dpi; ui.scaleSlider->setValue(dpi * SLIDER_RATIO); } void ScalingConfig::accept() { if (qFuzzyCompare(scaleFactor(), m_initialScalingFactor)) { QDialog::accept(); return; } const qreal scalingFactor = scaleFactor(); //save to config auto config = KSharedConfig::openConfig(QStringLiteral("kdeglobals")); config->group("KScreen").writeEntry("ScaleFactor", scalingFactor); //write env var to be used by startkde.sh to populate the QT_SCREEN_SCALE_FACTORS env var //we use QT_SCREEN_SCALE_FACTORS as opposed to QT_SCALE_FACTOR as we need to use one that will *NOT* scale fonts according to the scale //scaling the fonts makes sense if you don't also set a font DPI, but we *need* to set a font DPI for both PlasmaShell which does it's own thing, and for KDE4/GTK2 applications QString screenFactors; foreach (const KScreen::OutputPtr &output, m_outputList) { screenFactors.append(output->name() + QLatin1Char('=') + QString::number(scalingFactor) + QLatin1Char(';')); } config->group("KScreen").writeEntry("ScreenScaleFactors", screenFactors); KConfig fontConfig(QStringLiteral("kcmfonts")); auto fontConfigGroup = fontConfig.group("General"); if (qFuzzyCompare(scalingFactor, 1.0)) { //if dpi is the default (96) remove the entry rather than setting it QProcess proc; proc.start(QStringLiteral("xrdb -quiet -remove -nocpp")); if (proc.waitForStarted()) { proc.write(QByteArray("Xft.dpi\n")); proc.closeWriteChannel(); proc.waitForFinished(); } fontConfigGroup.writeEntry("forceFontDPI", 0); } else { QProcess proc; proc.start(QStringLiteral("xrdb -quiet -merge -nocpp")); if (proc.waitForStarted()) { proc.write(QByteArray("Xft.dpi: " + QString::number(scaleDPI()).toLatin1())); proc.closeWriteChannel(); proc.waitForFinished(); } fontConfigGroup.writeEntry("forceFontDPI", scaleDPI()); } QDialog::accept(); } int ScalingConfig::scaleDPI() const { return qRound(scaleFactor() * 96.0); } qreal ScalingConfig::scaleFactor() const { return ui.scaleSlider->value() / SLIDER_RATIO; } diff --git a/kcm/src/unifiedoutputconfig.cpp b/kcm/src/unifiedoutputconfig.cpp index db02b5d..b36f3db 100644 --- a/kcm/src/unifiedoutputconfig.cpp +++ b/kcm/src/unifiedoutputconfig.cpp @@ -1,199 +1,198 @@ /* * Copyright 2013 Daniel Vrátil * * 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 . * */ #include "unifiedoutputconfig.h" #include "resolutionslider.h" #include "utils.h" #include "kcm_screen_debug.h" #include #include #include #include #include #include #include -#include #include #include #include bool operator<(const QSize &s1, const QSize &s2) { return s1.width() * s1.height() < s2.width() * s2.height(); } template<> bool qMapLessThanKey(const QSize &s1, const QSize &s2) { return s1 < s2; } UnifiedOutputConfig::UnifiedOutputConfig(const KScreen::ConfigPtr &config, QWidget *parent) : OutputConfig(parent) , mConfig(config) { } UnifiedOutputConfig::~UnifiedOutputConfig() { } void UnifiedOutputConfig::setOutput(const KScreen::OutputPtr &output, Control::OutputRetention retention) { Q_UNUSED(retention); // TODO: use? mOutput = output; mClones.clear(); mClones.reserve(mOutput->clones().count()); Q_FOREACH (int id, mOutput->clones()) { mClones << mConfig->output(id); } mClones << mOutput; OutputConfig::setOutput(output); } void UnifiedOutputConfig::initUi() { QVBoxLayout *vbox = new QVBoxLayout(this); mTitle = new QLabel(this); mTitle->setAlignment(Qt::AlignHCenter); vbox->addWidget(mTitle); setTitle(i18n("Unified Outputs")); QGridLayout *formLayout = new QGridLayout(); vbox->addLayout(formLayout); vbox->addStretch(2); KScreen::OutputPtr fakeOutput = createFakeOutput(); mResolution = new ResolutionSlider(fakeOutput, this); connect(mResolution, &ResolutionSlider::resolutionChanged, this, &UnifiedOutputConfig::slotResolutionChanged); formLayout->addWidget(new QLabel(i18n("Resolution:"), this), 1, 0); formLayout->addWidget(mResolution, 1, 1); slotResolutionChanged(mResolution->currentResolution()); mRotation = new QComboBox(this); connect(mRotation, static_cast(&QComboBox::currentIndexChanged), this, &UnifiedOutputConfig::slotRotationChanged); mRotation->addItem(QIcon::fromTheme(QStringLiteral("arrow-up")), i18n("Normal"), KScreen::Output::None); mRotation->addItem(QIcon::fromTheme(QStringLiteral("arrow-right")), i18n("90° Clockwise"), KScreen::Output::Right); mRotation->addItem(QIcon::fromTheme(QStringLiteral("arrow-down")), i18n("Upside Down"), KScreen::Output::Inverted); mRotation->addItem(QIcon::fromTheme(QStringLiteral("arrow-left")), i18n("90° Counterclockwise"), KScreen::Output::Left); formLayout->addWidget(new QLabel(i18n("Orientation:"), this), 2, 0); formLayout->addWidget(mRotation, 2, 1); formLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum), 0, 2, 3, 1); } KScreen::OutputPtr UnifiedOutputConfig::createFakeOutput() { // Find set of common resolutions QMap commonSizes; Q_FOREACH (const KScreen::OutputPtr &clone, mClones) { QList processedSizes; Q_FOREACH (const KScreen::ModePtr &mode, clone->modes()) { // Make sure we don't count some modes multiple times because of different // refresh rates if (processedSizes.contains(mode->size())) { continue; } processedSizes << mode->size(); if (commonSizes.contains(mode->size())) { commonSizes[mode->size()]++; } else { commonSizes.insert(mode->size(), 1); } } } KScreen::OutputPtr fakeOutput(new KScreen::Output); // This will give us list of resolution that are shared by all outputs QList commonResults = commonSizes.keys(mClones.count()); // If there are no common resolution, fallback to smallest preferred mode if (commonResults.isEmpty()) { QSize smallestMode; Q_FOREACH (const KScreen::OutputPtr &clone, mClones) { qCDebug(KSCREEN_KCM) << smallestMode << clone->preferredMode()->size(); if (!smallestMode.isValid() || clone->preferredMode()->size() < smallestMode) { smallestMode = clone->preferredMode()->size(); } } commonResults << smallestMode; } std::sort(commonResults.begin(), commonResults.end()); KScreen::ModeList modes; Q_FOREACH (const QSize &size, commonResults) { KScreen::ModePtr mode(new KScreen::Mode); mode->setSize(size); mode->setId(Utils::sizeToString(size)); mode->setName(mode->id()); modes.insert(mode->id(), mode); } fakeOutput->setModes(modes); fakeOutput->setCurrentModeId(Utils::sizeToString(commonResults.last())); return fakeOutput; } void UnifiedOutputConfig::slotResolutionChanged(const QSize &size) { // Ignore disconnected outputs if (!size.isValid()) { return; } Q_FOREACH (const KScreen::OutputPtr &clone, mClones) { const QString &id = findBestMode(clone, size); if (id.isEmpty()) { // FIXME: Error? return; } clone->setCurrentModeId(id); } Q_EMIT changed(); } QString UnifiedOutputConfig::findBestMode(const KScreen::OutputPtr &output, const QSize &size) { float refreshRate = 0; QString id; Q_FOREACH (const KScreen::ModePtr &mode, output->modes()) { if (mode->size() == size && mode->refreshRate() > refreshRate) { refreshRate = mode->refreshRate(); id = mode->id(); } } return id; } diff --git a/kcm/src/widget.cpp b/kcm/src/widget.cpp index dd733f9..94e65d3 100644 --- a/kcm/src/widget.cpp +++ b/kcm/src/widget.cpp @@ -1,487 +1,483 @@ /* * Copyright (C) 2013 Daniel Vr??til * * 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 "widget.h" #include "controlpanel.h" -#include -#include -#include #include -#include #include "declarative/qmloutput.h" #include "declarative/qmlscreen.h" #include "utils.h" #include "scalingconfig.h" #include "../../common/control.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_kscreen_widget.h" #define QML_PATH "kcm_kscreen/qml/" Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::KScreenWidget()) { qRegisterMetaType(); ui->setupUi(this); ui->quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); connect(ui->primaryCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &Widget::primaryOutputSelected); mControlPanel = new ControlPanel(this); connect(mControlPanel, &ControlPanel::changed, this, &Widget::changed); ui->controlPanelLayout->addWidget(mControlPanel); connect(ui->unifyButton, &QPushButton::released, [this]{ slotUnifyOutputs(); }); connect(ui->scaleAllOutputsButton, &QPushButton::released, [this] { QPointer dialog = new ScalingConfig(mConfig->outputs(), this); dialog->exec(); delete dialog; }); connect(this, &Widget::saveControls, mControlPanel, &ControlPanel::save); mOutputTimer = new QTimer(this); connect(mOutputTimer, &QTimer::timeout, this, &Widget::clearOutputIdentifiers); loadQml(); } Widget::~Widget() { clearOutputIdentifiers(); delete ui; } bool Widget::eventFilter(QObject* object, QEvent* event) { if (event->type() == QEvent::Resize) { if (mOutputIdentifiers.contains(qobject_cast(object))) { QResizeEvent *e = static_cast(event); const QRect screenSize = object->property("screenSize").toRect(); QRect geometry(QPoint(0, 0), e->size()); geometry.moveCenter(screenSize.center()); static_cast(object)->setGeometry(geometry); // Pass the event further } } return QObject::eventFilter(object, event); } void Widget::setConfig(const KScreen::ConfigPtr &config) { if (mConfig) { KScreen::ConfigMonitor::instance()->removeConfig(mConfig); for (const KScreen::OutputPtr &output : mConfig->outputs()) { output->disconnect(this); } mConfig->disconnect(this); } mConfig = config; KScreen::ConfigMonitor::instance()->addConfig(mConfig); resetPrimaryCombo(); connect(mConfig.data(), &KScreen::Config::outputAdded, this, &Widget::outputAdded); connect(mConfig.data(), &KScreen::Config::outputRemoved, this, &Widget::outputRemoved); connect(mConfig.data(), &KScreen::Config::primaryOutputChanged, this, &Widget::primaryOutputChanged); mScreen->setConfig(mConfig); mControlPanel->setConfig(mConfig); ui->unifyButton->setEnabled(mConfig->outputs().count() > 1); ui->scaleAllOutputsButton->setVisible(!mConfig->supportedFeatures().testFlag(KScreen::Config::Feature::PerOutputScaling)); for (const KScreen::OutputPtr &output : mConfig->outputs()) { outputAdded(output); } // Select the primary (or only) output by default QMLOutput *qmlOutput = mScreen->primaryOutput(); if (qmlOutput) { mScreen->setActiveOutput(qmlOutput); } else { if (!mScreen->outputs().isEmpty()) { mScreen->setActiveOutput(mScreen->outputs().at(0)); } } slotOutputEnabledChanged(); } KScreen::ConfigPtr Widget::currentConfig() const { return mConfig; } void Widget::loadQml() { qmlRegisterType("org.kde.kscreen", 1, 0, "QMLOutput"); qmlRegisterType("org.kde.kscreen", 1, 0, "QMLScreen"); qmlRegisterType("org.kde.kscreen", 1, 0, "KScreenOutput"); qmlRegisterType("org.kde.kscreen", 1, 0, "KScreenEdid"); qmlRegisterType("org.kde.kscreen", 1, 0, "KScreenMode"); //const QString file = QDir::currentPath() + "/main.qml"; const QString file = QStandardPaths::locate(QStandardPaths::QStandardPaths::GenericDataLocation, QStringLiteral("kcm_kscreen/qml/main.qml")); ui->quickWidget->setSource(QUrl::fromLocalFile(file)); QQuickItem* rootObject = ui->quickWidget->rootObject(); mScreen = rootObject->findChild(QStringLiteral("outputView")); if (!mScreen) { return; } connect(mScreen, &QMLScreen::focusedOutputChanged, this, &Widget::slotFocusedOutputChanged); connect(rootObject->findChild(QStringLiteral("identifyButton")), SIGNAL(clicked()), this, SLOT(slotIdentifyButtonClicked())); } void Widget::resetPrimaryCombo() { bool isPrimaryDisplaySupported = mConfig->supportedFeatures().testFlag(KScreen::Config::Feature::PrimaryDisplay); ui->primaryLabel->setVisible(isPrimaryDisplaySupported); ui->primaryCombo->setVisible(isPrimaryDisplaySupported); // Don't emit currentIndexChanged when resetting bool blocked = ui->primaryCombo->blockSignals(true); ui->primaryCombo->clear(); ui->primaryCombo->addItem(i18n("No Primary Output")); ui->primaryCombo->blockSignals(blocked); if (!mConfig) { return; } for (auto &output: mConfig->outputs()) { addOutputToPrimaryCombo(output); } } void Widget::addOutputToPrimaryCombo(const KScreen::OutputPtr &output) { if (!output->isConnected() || !output->isEnabled()) { return; } ui->primaryCombo->addItem(Utils::outputName(output), output->id()); if (output->isPrimary()) { Q_ASSERT(mConfig); int lastIndex = ui->primaryCombo->count() - 1; ui->primaryCombo->setCurrentIndex(lastIndex); } } void Widget::slotFocusedOutputChanged(QMLOutput *output) { mControlPanel->activateOutput(output->outputPtr()); } void Widget::slotOutputEnabledChanged() { resetPrimaryCombo(); int enabledOutputsCount = 0; Q_FOREACH (const KScreen::OutputPtr &output, mConfig->outputs()) { if (output->isEnabled()) { ++enabledOutputsCount; } if (enabledOutputsCount > 1) { break; } } ui->unifyButton->setEnabled(enabledOutputsCount > 1); } void Widget::slotOutputConnectedChanged() { resetPrimaryCombo(); } void Widget::slotUnifyOutputs() { QMLOutput *base = mScreen->primaryOutput(); QList clones; if (!base) { for (QMLOutput *output: mScreen->outputs()) { if (output->output()->isConnected() && output->output()->isEnabled()) { base = output; break; } } if (!base) { // WTF? return; } } if (base->isCloneMode()) { setConfig(mPrevConfig); mPrevConfig.clear(); ui->primaryCombo->setEnabled(true); ui->unifyButton->setText(i18n("Unify Outputs")); } else { // Clone the current config, so that we can restore it in case user // breaks the cloning mPrevConfig = mConfig->clone(); for (QMLOutput *output: mScreen->outputs()) { if (!output->output()->isConnected()) { continue; } if (!output->output()->isEnabled()) { output->setVisible(false); continue; } if (!base) { base = output; } output->setOutputX(0); output->setOutputY(0); output->output()->setPos(QPoint(0, 0)); output->output()->setClones(QList()); if (base != output) { clones << output->output()->id(); output->setCloneOf(base); output->setVisible(false); } } base->output()->setClones(clones); base->setIsCloneMode(true); mScreen->updateOutputsPlacement(); ui->primaryCombo->setEnabled(false); mControlPanel->setUnifiedOutput(base->outputPtr()); ui->unifyButton->setText(i18n("Break Unified Outputs")); } Q_EMIT changed(); } // FIXME: Copy-pasted from KDED's Serializer::findOutput() KScreen::OutputPtr Widget::findOutput(const KScreen::ConfigPtr &config, const QVariantMap &info) { KScreen::OutputList outputs = config->outputs(); Q_FOREACH(const KScreen::OutputPtr &output, outputs) { if (!output->isConnected()) { continue; } const QString outputId = (output->edid() && output->edid()->isValid()) ? output->edid()->hash() : output->name(); if (outputId != info[QStringLiteral("id")].toString()) { continue; } 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()); output->setRotation(static_cast(info[QStringLiteral("rotation")].toInt())); QVariantMap modeInfo = info[QStringLiteral("mode")].toMap(); QVariantMap modeSize = modeInfo[QStringLiteral("size")].toMap(); QSize size(modeSize[QStringLiteral("width")].toInt(), modeSize[QStringLiteral("height")].toInt()); const KScreen::ModeList modes = output->modes(); Q_FOREACH(const KScreen::ModePtr &mode, modes) { if (mode->size() != size) { continue; } if (QString::number(mode->refreshRate()) != modeInfo[QStringLiteral("refresh")].toString()) { continue; } output->setCurrentModeId(mode->id()); break; } return output; } return KScreen::OutputPtr(); } void Widget::clearOutputIdentifiers() { mOutputTimer->stop(); qDeleteAll(mOutputIdentifiers); mOutputIdentifiers.clear(); } void Widget::outputAdded(const KScreen::OutputPtr &output) { connect(output.data(), &KScreen::Output::isConnectedChanged, this, &Widget::slotOutputConnectedChanged); connect(output.data(), &KScreen::Output::isEnabledChanged, this, &Widget::slotOutputEnabledChanged); connect(output.data(), &KScreen::Output::posChanged, this, &Widget::changed); addOutputToPrimaryCombo(output); } void Widget::outputRemoved(int outputId) { KScreen::OutputPtr output = mConfig->output(outputId); if (!output.isNull()) { output->disconnect(this); } const int index = ui->primaryCombo->findData(outputId); if (index == -1) { return; } if (index == ui->primaryCombo->currentIndex()) { // We'll get the actual primary update signal eventually // Don't emit currentIndexChanged const bool blocked = ui->primaryCombo->blockSignals(true); ui->primaryCombo->setCurrentIndex(0); ui->primaryCombo->blockSignals(blocked); } ui->primaryCombo->removeItem(index); } void Widget::primaryOutputSelected(int index) { if (!mConfig) { return; } const KScreen::OutputPtr newPrimary = index == 0 ? KScreen::OutputPtr() : mConfig->output(ui->primaryCombo->itemData(index).toInt()); if (newPrimary == mConfig->primaryOutput()) { return; } mConfig->setPrimaryOutput(newPrimary); Q_EMIT changed(); } void Widget::primaryOutputChanged(const KScreen::OutputPtr &output) { Q_ASSERT(mConfig); int index = output.isNull() ? 0 : ui->primaryCombo->findData(output->id()); if (index == -1 || index == ui->primaryCombo->currentIndex()) { return; } ui->primaryCombo->setCurrentIndex(index); } void Widget::slotIdentifyButtonClicked(bool checked) { Q_UNUSED(checked); connect(new KScreen::GetConfigOperation(), &KScreen::GetConfigOperation::finished, this, &Widget::slotIdentifyOutputs); } void Widget::slotIdentifyOutputs(KScreen::ConfigOperation *op) { if (op->hasError()) { return; } const KScreen::ConfigPtr config = qobject_cast(op)->config(); const QString qmlPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral(QML_PATH "OutputIdentifier.qml")); mOutputTimer->stop(); clearOutputIdentifiers(); /* Obtain the current active configuration from KScreen */ Q_FOREACH (const KScreen::OutputPtr &output, config->outputs()) { if (!output->isConnected() || !output->currentMode()) { continue; } const KScreen::ModePtr mode = output->currentMode(); QQuickView *view = new QQuickView(); view->setFlags(Qt::X11BypassWindowManagerHint | Qt::FramelessWindowHint); view->setResizeMode(QQuickView::SizeViewToRootObject); view->setSource(QUrl::fromLocalFile(qmlPath)); view->installEventFilter(this); QQuickItem *rootObj = view->rootObject(); if (!rootObj) { qWarning() << "Failed to obtain root item"; continue; } QSize deviceSize, logicalSize; if (output->isHorizontal()) { deviceSize = mode->size(); } else { deviceSize = QSize(mode->size().height(), mode->size().width()); } if (config->supportedFeatures() & KScreen::Config::Feature::PerOutputScaling) { // no scale adjustment needed on Wayland logicalSize = deviceSize; } else { logicalSize = deviceSize / devicePixelRatioF(); } rootObj->setProperty("outputName", Utils::outputName(output)); rootObj->setProperty("modeName", Utils::sizeToString(deviceSize)); view->setProperty("screenSize", QRect(output->pos(), logicalSize)); mOutputIdentifiers << view; } for (QQuickView *view: mOutputIdentifiers) { view->show(); } mOutputTimer->start(2500); } diff --git a/kded/config.cpp b/kded/config.cpp index 7a86f40..a9b7c23 100644 --- a/kded/config.cpp +++ b/kded/config.cpp @@ -1,224 +1,223 @@ /******************************************************************** Copyright 2012 Alejandro Fiestas Olivares 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 "config.h" #include "output.h" #include "../common/globals.h" #include "../common/control.h" #include "kscreen_daemon_debug.h" #include "device.h" #include #include #include #include #include -#include #include #include QString Config::s_fixedConfigFileName = QStringLiteral("fixed-config"); QString Config::s_configsDirName = QStringLiteral("" /*"configs/"*/); // TODO: KDE6 - move these files into the subfolder QString Config::configsDirPath() { return Globals::dirPath() % s_configsDirName; } Config::Config(KScreen::ConfigPtr config) : m_data(config) { } QString Config::filePath() { if (!QDir().mkpath(configsDirPath())) { return QString(); } return configsDirPath() % id(); } QString Config::id() const { if (!m_data) { return QString(); } return m_data->connectedOutputsHash(); } bool Config::fileExists() const { return (QFile::exists(configsDirPath() % id()) || QFile::exists(configsDirPath() % s_fixedConfigFileName)); } std::unique_ptr Config::readFile() { 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 readFile(id()); } std::unique_ptr Config::readOpenLidFile() { const QString openLidFilePath = filePath() % QStringLiteral("_lidOpened"); auto config = readFile(openLidFilePath); QFile::remove(openLidFilePath); return config; } std::unique_ptr Config::readFile(const QString &fileName) { if (!m_data) { return nullptr; } auto config = std::unique_ptr(new Config(m_data->clone())); config->setValidityFlags(m_validityFlags); QFile file; if (QFile::exists(configsDirPath() % s_fixedConfigFileName)) { file.setFileName(configsDirPath() % s_fixedConfigFileName); qCDebug(KSCREEN_KDED) << "found a fixed config, will use " << file.fileName(); } else { file.setFileName(configsDirPath() % fileName); } if (!file.open(QIODevice::ReadOnly)) { qCDebug(KSCREEN_KDED) << "failed to open file" << file.fileName(); return nullptr; } QJsonDocument parser; QVariantList outputs = parser.fromJson(file.readAll()).toVariant().toList(); Output::readInOutputs(config->data(), outputs); QSize screenSize; for (const auto &output : config->data()->outputs()) { if (!output->isConnected() || !output->isEnabled()) { continue; } const QRect geom = output->geometry(); if (geom.x() + geom.width() > screenSize.width()) { screenSize.setWidth(geom.x() + geom.width()); } if (geom.y() + geom.height() > screenSize.height()) { screenSize.setHeight(geom.y() + geom.height()); } } config->data()->screen()->setCurrentSize(screenSize); if (!canBeApplied(config->data())) { return nullptr; } return config; } bool Config::canBeApplied() const { return canBeApplied(m_data); } bool Config::canBeApplied(KScreen::ConfigPtr config) const { #ifdef KDED_UNIT_TEST Q_UNUSED(config); return true; #else return KScreen::Config::canBeApplied(config, m_validityFlags); #endif } bool Config::writeFile() { return writeFile(filePath()); } bool Config::writeOpenLidFile() { return writeFile(filePath() % QStringLiteral("_lidOpened")); } bool Config::writeFile(const QString &filePath) { if (id().isEmpty()) { return false; } const KScreen::OutputList outputs = m_data->outputs(); const auto control = ControlConfig(m_data); QVariantList outputList; Q_FOREACH(const KScreen::OutputPtr &output, outputs) { QVariantMap info; if (!output->isConnected()) { continue; } if (!Output::writeGlobalPart(output, info)) { continue; } info[QStringLiteral("primary")] = output->isPrimary(); info[QStringLiteral("enabled")] = output->isEnabled(); QVariantMap pos; pos[QStringLiteral("x")] = output->pos().x(); pos[QStringLiteral("y")] = output->pos().y(); info[QStringLiteral("pos")] = pos; if (control.getOutputRetention(output->hash(), output->name()) != Control::OutputRetention::Individual) { // try to update global output data Output::writeGlobal(output); } outputList.append(info); } QFile file(filePath); if (!file.open(QIODevice::WriteOnly)) { qCWarning(KSCREEN_KDED) << "Failed to open config file for writing! " << file.errorString(); return false; } file.write(QJsonDocument::fromVariant(outputList).toJson()); qCDebug(KSCREEN_KDED) << "Config saved on: " << file.fileName(); return true; } void Config::log() { if (!m_data) { return; } const auto outputs = m_data->outputs(); for (const auto o : outputs) { if (o->isConnected()) { qCDebug(KSCREEN_KDED) << o; } } } diff --git a/kded/config.h b/kded/config.h index d227567..227698b 100644 --- a/kded/config.h +++ b/kded/config.h @@ -1,71 +1,69 @@ /******************************************************************** 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 . *********************************************************************/ #ifndef KDED_CONFIG_H #define KDED_CONFIG_H -#include -#include #include #include class Config { public: explicit Config(KScreen::ConfigPtr config); ~Config() = default; QString id() const; bool fileExists() const; std::unique_ptr readFile(); std::unique_ptr readOpenLidFile(); bool writeFile(); bool writeOpenLidFile(); KScreen::ConfigPtr data() const { return m_data; } void log(); void setValidityFlags(KScreen::Config::ValidityFlags flags) { m_validityFlags = flags; } bool canBeApplied() const; private: friend class TestConfig; QString filePath(); std::unique_ptr readFile(const QString &fileName); bool writeFile(const QString &filePath); bool canBeApplied(KScreen::ConfigPtr config) const; KScreen::ConfigPtr m_data; KScreen::Config::ValidityFlags m_validityFlags; static QString s_configsDirName; static QString s_fixedConfigFileName; static QString configsDirPath(); }; #endif diff --git a/kded/device.cpp b/kded/device.cpp index 1a975cc..7c54015 100644 --- a/kded/device.cpp +++ b/kded/device.cpp @@ -1,178 +1,177 @@ /************************************************************************************* * Copyright (C) 2012 by Alejandro Fiestas Olivares * * Copyright (C) 2015 by Daniel Vrátil * * * * 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 "device.h" #include "kscreen_daemon_debug.h" #include "kded/freedesktop_interface.h" -#include Device* Device::m_instance = nullptr; Device* Device::self() { if (!Device::m_instance) { m_instance = new Device(); } return m_instance; } void Device::destroy() { delete m_instance; m_instance = nullptr; } Device::Device(QObject* parent) : QObject(parent) , m_isReady(false) , m_isLaptop(false) , m_isLidClosed(false) , m_isDocked(false) { m_freedesktop = new OrgFreedesktopDBusPropertiesInterface(QStringLiteral("org.freedesktop.UPower"), QStringLiteral("/org/freedesktop/UPower"), QDBusConnection::systemBus(), this); if (!m_freedesktop->isValid()) { qCWarning(KSCREEN_KDED) << "UPower not available, lid detection won't work"; qCDebug(KSCREEN_KDED) << m_freedesktop->lastError().message(); } else { QDBusConnection::systemBus().connect(QStringLiteral("org.freedesktop.UPower"), QStringLiteral("/org/freedesktop/UPower"), QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("PropertiesChanged"), this, SLOT(changed())); fetchIsLaptop(); } m_suspendSession = new QDBusInterface(QStringLiteral("org.kde.Solid.PowerManagement"), QStringLiteral("/org/kde/Solid/PowerManagement/Actions/SuspendSession"), QStringLiteral("org.kde.Solid.PowerManagement.Actions.SuspendSession"), QDBusConnection::sessionBus(), this); if (m_suspendSession->isValid()) { connect(m_suspendSession, SIGNAL(resumingFromSuspend()), this, SIGNAL(resumingFromSuspend())); connect(m_suspendSession, SIGNAL(aboutToSuspend()), this, SIGNAL(aboutToSuspend())); } else { qCWarning(KSCREEN_KDED) << "PowerDevil SuspendSession action not available!"; qCDebug(KSCREEN_KDED) << m_suspendSession->lastError().message(); } fetchIsLaptop(); } Device::~Device() { } void Device::changed() { fetchLidIsClosed(); } void Device::setReady() { if (m_isReady) { return; } m_isReady = true; Q_EMIT ready(); } bool Device::isReady() const { return m_isReady; } bool Device::isLaptop() const { return m_isLaptop; } bool Device::isLidClosed() const { return m_isLidClosed; } bool Device::isDocked() const { return m_isDocked; } void Device::fetchIsLaptop() { QDBusPendingReply res = m_freedesktop->Get(QStringLiteral("org.freedesktop.UPower"), QStringLiteral("LidIsPresent")); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(res); connect(watcher, &QDBusPendingCallWatcher::finished, this, &Device::isLaptopFetched); } void Device::isLaptopFetched(QDBusPendingCallWatcher* watcher) { const QDBusPendingReply reply = *watcher; if (reply.isError()) { qCDebug(KSCREEN_KDED) << "Couldn't get if the device is a laptop: " << reply.error().message(); return; } m_isLaptop = reply.value().toBool(); watcher->deleteLater(); if (!m_isLaptop) { setReady(); return; } fetchLidIsClosed(); } void Device::fetchLidIsClosed() { QDBusPendingReply res = m_freedesktop->Get(QStringLiteral("org.freedesktop.UPower"), QStringLiteral("LidIsClosed")); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(res); connect(watcher, &QDBusPendingCallWatcher::finished, this, &Device::isLidClosedFetched); } void Device::isLidClosedFetched(QDBusPendingCallWatcher* watcher) { const QDBusPendingReply reply = *watcher; if (reply.isError()) { qCDebug(KSCREEN_KDED) << "Couldn't get if the laptop has the lid closed: " << reply.error().message(); return; } if (reply.argumentAt<0>() != m_isLidClosed) { m_isLidClosed = reply.value().toBool(); if (m_isReady) { Q_EMIT lidClosedChanged(m_isLidClosed);; } } watcher->deleteLater(); fetchIsDocked(); } void Device::fetchIsDocked() { setReady(); } diff --git a/kded/generator.cpp b/kded/generator.cpp index 9767f9d..07618a4 100644 --- a/kded/generator.cpp +++ b/kded/generator.cpp @@ -1,679 +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 -#include -#include #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 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/generator.h b/kded/generator.h index 0c48bca..0a4e253 100644 --- a/kded/generator.h +++ b/kded/generator.h @@ -1,96 +1,95 @@ /************************************************************************************* * 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 * *************************************************************************************/ #ifndef KDED_GENERATOR_H #define KDED_GENERATOR_H #include -#include #include #include namespace KScreen { class Config; } class Generator : public QObject { Q_OBJECT public: enum DisplaySwitchAction { None = 0, Clone = 1, ExtendToLeft = 2, TurnOffEmbedded = 3, TurnOffExternal = 4, ExtendToRight = 5, }; static Generator* self(); static void destroy(); void setCurrentConfig(const KScreen::ConfigPtr ¤tConfig); KScreen::ConfigPtr idealConfig(const KScreen::ConfigPtr ¤tConfig); KScreen::ConfigPtr displaySwitch(DisplaySwitchAction iteration); void setForceLaptop(bool force); void setForceLidClosed(bool force); void setForceDocked(bool force); void setForceNotLaptop(bool force); static KScreen::ModePtr biggestMode(const KScreen::ModeList &modes); Q_SIGNALS: void ready(); private: explicit Generator(); ~Generator() override; KScreen::ConfigPtr fallbackIfNeeded(const KScreen::ConfigPtr &config); void cloneScreens(KScreen::OutputList &connectedOutputs); void laptop(KScreen::OutputList &connectedOutputs); void singleOutput(KScreen::OutputList &connectedOutputs); void extendToRight(KScreen::OutputList &connectedOutputs); KScreen::ModePtr bestModeForSize(const KScreen::ModeList& modes, const QSize &size); KScreen::ModePtr bestModeForOutput(const KScreen::OutputPtr &output); qreal bestScaleForOutput(const KScreen::OutputPtr &output); KScreen::OutputPtr biggestOutput(const KScreen::OutputList &connectedOutputs); KScreen::OutputPtr embeddedOutput(const KScreen::OutputList &connectedOutputs); void disableAllDisconnectedOutputs(const KScreen::OutputList &connectedOutputs); bool isLaptop() const; bool isLidClosed() const; bool isDocked() const; bool m_forceLaptop; bool m_forceLidClosed; bool m_forceNotLaptop; bool m_forceDocked; KScreen::ConfigPtr m_currentConfig; static Generator* instance; }; #endif //KDED_GENERATOR_H diff --git a/kded/osd.cpp b/kded/osd.cpp index 9d738ae..e620f8e 100644 --- a/kded/osd.cpp +++ b/kded/osd.cpp @@ -1,218 +1,217 @@ /* * Copyright 2014 Martin Klapetek * 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 "osd.h" #include "utils.h" #include "kscreen_daemon_debug.h" #include -#include #include #include #include using namespace KScreen; Osd::Osd(const KScreen::OutputPtr &output, QObject *parent) : QObject(parent) , m_output(output) { connect(output.data(), &KScreen::Output::isConnectedChanged, this, &Osd::onOutputAvailabilityChanged); connect(output.data(), &KScreen::Output::isEnabledChanged, this, &Osd::onOutputAvailabilityChanged); connect(output.data(), &KScreen::Output::currentModeIdChanged, this, &Osd::updatePosition); connect(output.data(), &KScreen::Output::destroyed, this, &Osd::hideOsd); } Osd::~Osd() { } bool Osd::initOsd() { if (m_osdObject) { return true; } const QString osdPath = QStandardPaths::locate(QStandardPaths::QStandardPaths::GenericDataLocation, QStringLiteral("kded_kscreen/qml/Osd.qml")); if (osdPath.isEmpty()) { qCWarning(KSCREEN_KDED) << "Failed to find OSD QML file" << osdPath; return false; } m_osdObject = new KDeclarative::QmlObjectSharedEngine(this); m_osdObject->setSource(QUrl::fromLocalFile(osdPath)); if (m_osdObject->status() != QQmlComponent::Ready) { qCWarning(KSCREEN_KDED) << "Failed to load OSD QML file" << osdPath; delete m_osdObject; m_osdObject = nullptr; return false; } m_timeout = m_osdObject->rootObject()->property("timeout").toInt(); m_osdTimer = new QTimer(this); m_osdTimer->setSingleShot(true); connect(m_osdTimer, &QTimer::timeout, this, &Osd::hideOsd); return true; } void Osd::showGenericOsd(const QString &icon, const QString &text) { if (!initOsd()) { return; } m_outputGeometry = m_output->geometry(); auto *rootObject = m_osdObject->rootObject(); rootObject->setProperty("itemSource", QStringLiteral("OsdItem.qml")); rootObject->setProperty("infoText", text); rootObject->setProperty("icon", icon); showOsd(); } void Osd::showOutputIdentifier(const KScreen::OutputPtr &output) { if (!initOsd()) { return; } m_outputGeometry = output->geometry(); auto *rootObject = m_osdObject->rootObject(); auto mode = output->currentMode(); QSize realSize = mode->size(); if (!output->isHorizontal()) { realSize.transpose(); } rootObject->setProperty("itemSource", QStringLiteral("OutputIdentifier.qml")); rootObject->setProperty("modeName", Utils::sizeToString(realSize)); rootObject->setProperty("outputName", Utils::outputName(output)); showOsd(); } void Osd::showActionSelector() { if (!m_osdActionSelector) { const QString osdPath = QStandardPaths::locate(QStandardPaths::QStandardPaths::GenericDataLocation, QStringLiteral("kded_kscreen/qml/OsdSelector.qml")); if (osdPath.isEmpty()) { qCWarning(KSCREEN_KDED) << "Failed to find action selector OSD QML file" << osdPath; return; } m_osdActionSelector = new KDeclarative::QmlObjectSharedEngine(this); m_osdActionSelector->setSource(QUrl::fromLocalFile(osdPath)); if (m_osdActionSelector->status() != QQmlComponent::Ready) { qCWarning(KSCREEN_KDED) << "Failed to load OSD QML file" << osdPath; delete m_osdActionSelector; m_osdActionSelector = nullptr; return; } auto *rootObject = m_osdActionSelector->rootObject(); connect(rootObject, SIGNAL(clicked(int)), this, SLOT(onOsdActionSelected(int))); } if (auto *rootObject = m_osdActionSelector->rootObject()) { rootObject->setProperty("visible", true); } else { qCWarning(KSCREEN_KDED) << "Could not get root object for action selector."; } } void Osd::onOsdActionSelected(int action) { Q_EMIT osdActionSelected(static_cast(action)); hideOsd(); } void Osd::onOutputAvailabilityChanged() { if (!m_output || !m_output->isConnected() || !m_output->isEnabled() || !m_output->currentMode()) { hideOsd(); } } void Osd::updatePosition() { if (!initOsd()) { return; } const auto geometry = m_output->geometry(); if (!geometry.isValid()) { hideOsd(); } auto *rootObject = m_osdObject->rootObject(); const int dialogWidth = rootObject->property("width").toInt(); const int dialogHeight = rootObject->property("height").toInt(); const int relx = geometry.x(); const int rely = geometry.y(); const int pos_x = relx + (geometry.width() - dialogWidth) / 2; const int pos_y = rely + (geometry.height() - dialogHeight) / 2; rootObject->setProperty("x", pos_x); rootObject->setProperty("y", pos_y); } void Osd::showOsd() { m_osdTimer->stop(); auto *rootObject = m_osdObject->rootObject(); // only animate on X11, wayland plugin doesn't support this and // pukes loads of warnings into our logs if (qGuiApp->platformName() == QLatin1String("xcb")) { if (rootObject->property("timeout").toInt() > 0) { rootObject->setProperty("animateOpacity", false); rootObject->setProperty("opacity", 1); rootObject->setProperty("animateOpacity", true); rootObject->setProperty("opacity", 0); } } rootObject->setProperty("visible", true); QTimer::singleShot(0, this, &Osd::updatePosition); if (m_timeout > 0) { m_osdTimer->start(m_timeout); } } void Osd::hideOsd() { if (m_osdActionSelector) { if (auto *rootObject = m_osdActionSelector->rootObject()) { rootObject->setProperty("visible", false); } } if (m_osdObject) { if (auto *rootObject = m_osdObject->rootObject()) { rootObject->setProperty("visible", false); } } } diff --git a/kded/output.cpp b/kded/output.cpp index 7ff32be..b393e08 100644 --- a/kded/output.cpp +++ b/kded/output.cpp @@ -1,383 +1,382 @@ /******************************************************************** 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 #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())); output->setScale(info.value(QStringLiteral("scale"), 1).toInt()); 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; } 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(); if (posInfo.isEmpty() || modeSize.isEmpty() || !scaleInfo.canConvert()) { return false; } const int scale = scaleInfo.toInt(); if (scale <= 0) { return false; } const QPoint pos = QPoint(posInfo[QStringLiteral("x")].toInt(), posInfo[QStringLiteral("y")].toInt()); const QSize size = QSize(modeSize[QStringLiteral("width")].toInt() / scale, modeSize[QStringLiteral("height")].toInt() / scale); 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-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)); break; } if (!infoFound) { // 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) { if (!output->isEnabled()) { return false; } const KScreen::ModePtr mode = output->currentMode(); if (!mode) { qWarning() << "CurrentMode is null" << output->name(); return false; } info[QStringLiteral("id")] = output->hash(); info[QStringLiteral("rotation")] = output->rotation(); info[QStringLiteral("scale")] = output->scale(); info[QStringLiteral("metadata")] = metadata(output); QVariantMap modeInfo; modeInfo[QStringLiteral("refresh")] = mode->refreshRate(); QVariantMap modeSize; modeSize[QStringLiteral("width")] = mode->size().width(); modeSize[QStringLiteral("height")] = mode->size().height(); modeInfo[QStringLiteral("size")] = modeSize; 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)) { 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; }