diff --git a/kded/control.cpp b/common/control.cpp similarity index 90% rename from kded/control.cpp rename to common/control.cpp index 7edb5e8..26ee559 100644 --- a/kded/control.cpp +++ b/common/control.cpp @@ -1,107 +1,99 @@ /******************************************************************** 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 "control.h" -#include "config.h" +#include "globals.h" -#include "kscreen_daemon_debug.h" -#include "generator.h" - -#include #include -#include #include #include -#include - -#include -#include QString Control::s_dirName = QStringLiteral("control/"); QString Control::dirPath() { - return Config::dirPath() % s_dirName; + return Globals::dirPath() % s_dirName; } QString Control::outputFilePath(const QString &hash) { const QString dir = dirPath() % QStringLiteral("outputs/"); if (!QDir().mkpath(dir)) { return QString(); } return dir % hash; } QString Control::configFilePath(const QString &hash) { const QString dir = dirPath() % QStringLiteral("configs/"); if (!QDir().mkpath(dir)) { return QString(); } return dir % hash; } Control::OutputRetention Control::convertVariantToOutputRetention(QVariant variant) { if (variant.canConvert()) { const auto retention = variant.toInt(); if (retention == (int)OutputRetention::Global) { return OutputRetention::Global; } if (retention == (int)OutputRetention::Individual) { return OutputRetention::Individual; } } return OutputRetention::Undefined; } QMap Control::readInOutputRetentionValues(const QString &configId) { // qDebug() << "Looking for control file:" << configId; QFile file(configFilePath(configId)); if (!file.open(QIODevice::ReadOnly)) { - qCDebug(KSCREEN_KDED) << "Failed to open file" << file.fileName(); + // TODO: have a logging category +// qCDebug(KSCREEN_COMMON) << "Failed to open file" << file.fileName(); return QMap(); } QJsonDocument parser; const QVariantMap controlInfo = parser.fromJson(file.readAll()).toVariant().toMap(); const QVariantList outputsInfo = controlInfo[QStringLiteral("outputs")].toList(); QMap retentions; for (const auto variantInfo : outputsInfo) { const QVariantMap info = variantInfo.toMap(); // TODO: this does not yet consider the output name (i.e. connector). Necessary? const QString outputHash = info[QStringLiteral("id")].toString(); if (outputHash.isEmpty()) { continue; } retentions[outputHash] = convertVariantToOutputRetention(info[QStringLiteral("retention")]); } return retentions; } Control::OutputRetention Control::getOutputRetention(const QString &outputId, const QMap &retentions) { if (retentions.contains(outputId)) { return retentions[outputId]; } // info for output not found return OutputRetention::Undefined; } diff --git a/kded/control.h b/common/control.h similarity index 96% copy from kded/control.h copy to common/control.h index 243ddcf..e77a069 100644 --- a/kded/control.h +++ b/common/control.h @@ -1,46 +1,46 @@ /******************************************************************** 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_CONTROL_H -#define KDED_CONTROL_H +#ifndef COMMON_CONTROL_H +#define COMMON_CONTROL_H #include #include class Control { public: enum class OutputRetention { Undefined = -1, Global = 0, Individual = 1, }; static QMap readInOutputRetentionValues(const QString &configId); static OutputRetention getOutputRetention(const QString &outputId, const QMap &retentions); static QString configFilePath(const QString &hash); static QString outputFilePath(const QString &hash); private: static QString dirPath(); static OutputRetention convertVariantToOutputRetention(QVariant variant); static QString s_dirName; }; #endif diff --git a/common/globals.cpp b/common/globals.cpp new file mode 100644 index 0000000..f67f852 --- /dev/null +++ b/common/globals.cpp @@ -0,0 +1,35 @@ +/******************************************************************** +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/kded/control.h b/common/globals.h similarity index 53% rename from kded/control.h rename to common/globals.h index 243ddcf..863f51d 100644 --- a/kded/control.h +++ b/common/globals.h @@ -1,46 +1,32 @@ /******************************************************************** -Copyright 2019 Roman Gilg +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 . *********************************************************************/ -#ifndef KDED_CONTROL_H -#define KDED_CONTROL_H +#ifndef COMMON_GLOBALS_H +#define COMMON_GLOBALS_H -#include +#include -#include - -class Control +class Globals { public: - enum class OutputRetention { - Undefined = -1, - Global = 0, - Individual = 1, - }; - - static QMap readInOutputRetentionValues(const QString &configId); - static OutputRetention getOutputRetention(const QString &outputId, const QMap &retentions); - - static QString configFilePath(const QString &hash); - static QString outputFilePath(const QString &hash); - -private: + static void setDirPath(const QString &path); static QString dirPath(); - static OutputRetention convertVariantToOutputRetention(QVariant variant); - static QString s_dirName; +private: + static QString s_dirPath; }; #endif diff --git a/kded/CMakeLists.txt b/kded/CMakeLists.txt index 74a581c..e009917 100644 --- a/kded/CMakeLists.txt +++ b/kded/CMakeLists.txt @@ -1,56 +1,57 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kscreen\") include_directories(${CMAKE_CURRENT_BINARY_DIR}/../ ${CMAKE_SOURCE_DIR}/kcm/src) set(kscreen_daemon_SRCS daemon.cpp config.cpp output.cpp - control.cpp generator.cpp device.cpp osd.cpp osdmanager.cpp osdaction.cpp + ${CMAKE_SOURCE_DIR}/common/globals.cpp + ${CMAKE_SOURCE_DIR}/common/control.cpp ${CMAKE_SOURCE_DIR}/kcm/src/utils.cpp ) ecm_qt_declare_logging_category(kscreen_daemon_SRCS HEADER kscreen_daemon_debug.h IDENTIFIER KSCREEN_KDED CATEGORY_NAME kscreen.kded) qt5_add_dbus_interface(kscreen_daemon_SRCS org.freedesktop.DBus.Properties.xml freedesktop_interface) qt5_add_dbus_adaptor(kscreen_daemon_SRCS org.kde.KScreen.xml daemon.h KScreenDaemon ) add_library(kscreen MODULE ${kscreen_daemon_SRCS}) target_link_libraries(kscreen Qt5::Widgets Qt5::DBus Qt5::Quick KF5::Declarative KF5::Screen KF5::DBusAddons KF5::I18n KF5::XmlGui KF5::GlobalAccel) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/kscreen.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/kscreen.desktop @ONLY) kcoreaddons_desktop_to_json(kscreen ${CMAKE_CURRENT_BINARY_DIR}/kscreen.desktop) install(TARGETS kscreen DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kded) set(QML_FILES qml/Osd.qml qml/OsdItem.qml qml/OsdSelector.qml qml/OutputIdentifier.qml ) install(FILES ${QML_FILES} DESTINATION ${KDE_INSTALL_DATADIR}/kded_kscreen/qml) diff --git a/kded/config.cpp b/kded/config.cpp index cea320d..91cfb1c 100644 --- a/kded/config.cpp +++ b/kded/config.cpp @@ -1,232 +1,224 @@ /******************************************************************** 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 "control.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_dirPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) % QStringLiteral("/kscreen/"); QString Config::s_configsDirName = QStringLiteral("" /*"configs/"*/); // TODO: KDE6 - move these files into the subfolder QString Config::configsDirPath() { - return s_dirPath % s_configsDirName; + return Globals::dirPath() % s_configsDirName; } Config::Config(KScreen::ConfigPtr config) : m_data(config) { } -void Config::setDirPath(const QString &path) -{ - s_dirPath = path; - if (!s_dirPath.endsWith(QLatin1Char('/'))) { - s_dirPath += QLatin1Char('/'); - } -} - 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(), outputs, Control::readInOutputRetentionValues(config->id())); 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 retentions = Control::readInOutputRetentionValues(id()); 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(), retentions) != 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 725fc79..d227567 100644 --- a/kded/config.h +++ b/kded/config.h @@ -1,77 +1,71 @@ /******************************************************************** 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; - static void setDirPath(const QString &path); - static QString dirPath() { - return s_dirPath; - } - 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_dirPath; static QString s_configsDirName; static QString s_fixedConfigFileName; static QString configsDirPath(); }; #endif diff --git a/kded/output.cpp b/kded/output.cpp index d52b1e0..c54bbdc 100644 --- a/kded/output.cpp +++ b/kded/output.cpp @@ -1,248 +1,249 @@ /******************************************************************** Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "output.h" #include "config.h" +#include "../common/globals.h" #include "kscreen_daemon_debug.h" #include "generator.h" #include #include #include #include #include #include #include #include QString Output::s_dirName = QStringLiteral("outputs/"); QString Output::dirPath() { - return Config::dirPath() % s_dirName; + 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::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::OutputList outputs, const QVariantList &outputsInfo, const QMap &retentions) { // 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(); const auto retention = Control::getOutputRetention(outputId, retentions); 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, retention); 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()); } } } } 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; } diff --git a/kded/output.h b/kded/output.h index d553727..2a8a68e 100644 --- a/kded/output.h +++ b/kded/output.h @@ -1,47 +1,47 @@ /******************************************************************** 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_OUTPUT_H #define KDED_OUTPUT_H -#include "control.h" +#include "../common/control.h" #include #include class Output { public: static void readInOutputs(KScreen::OutputList outputs, const QVariantList &outputsInfo, const QMap &retentions); static void writeGlobal(const KScreen::OutputPtr &output); static bool writeGlobalPart(const KScreen::OutputPtr &output, QVariantMap &info); static QString dirPath(); private: static QString globalFileName(const QString &hash); static QVariantMap getGlobalData(KScreen::OutputPtr output); static void readIn(KScreen::OutputPtr output, const QVariantMap &info, Control::OutputRetention retention); static bool readInGlobal(KScreen::OutputPtr output); static void readInGlobalPartFromInfo(KScreen::OutputPtr output, const QVariantMap &info); static QString s_dirName; }; #endif diff --git a/tests/kded/CMakeLists.txt b/tests/kded/CMakeLists.txt index f8d7f6e..d26f4bf 100644 --- a/tests/kded/CMakeLists.txt +++ b/tests/kded/CMakeLists.txt @@ -1,32 +1,33 @@ include_directories(${CMAKE_BINARY_DIR}) add_definitions(-DKDED_UNIT_TEST) macro(ADD_KDED_TEST testname) set(test_SRCS ${testname}.cpp ${CMAKE_SOURCE_DIR}/kded/generator.cpp ${CMAKE_SOURCE_DIR}/kded/device.cpp ${CMAKE_SOURCE_DIR}/kded/config.cpp ${CMAKE_SOURCE_DIR}/kded/output.cpp - ${CMAKE_SOURCE_DIR}/kded/control.cpp + ${CMAKE_SOURCE_DIR}/common/globals.cpp + ${CMAKE_SOURCE_DIR}/common/control.cpp #${CMAKE_SOURCE_DIR}/kded/daemon.cpp ) ecm_qt_declare_logging_category(test_SRCS HEADER kscreen_daemon_debug.h IDENTIFIER KSCREEN_KDED CATEGORY_NAME kscreen.kded) qt5_add_dbus_interface(test_SRCS ${CMAKE_SOURCE_DIR}/kded/org.freedesktop.DBus.Properties.xml freedesktop_interface ) add_executable(${testname} ${test_SRCS}) add_dependencies(${testname} kscreen) # make sure the dbus interfaces are generated target_compile_definitions(${testname} PRIVATE "-DTEST_DATA=\"${CMAKE_CURRENT_SOURCE_DIR}/\"") target_link_libraries(${testname} Qt5::Test Qt5::DBus Qt5::Gui KF5::Screen) add_test(NAME kscreen-kded-${testname} COMMAND ${testname}) ecm_mark_as_test(${testname}) endmacro() add_kded_test(testgenerator) add_kded_test(configtest) #add_kded_test(testdaemon) diff --git a/tests/kded/configtest.cpp b/tests/kded/configtest.cpp index 6accad9..7cf9d57 100644 --- a/tests/kded/configtest.cpp +++ b/tests/kded/configtest.cpp @@ -1,515 +1,515 @@ /******************************************************************** Copyright 2015 Daniel Vrátil 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 "../../kded/config.h" +#include "../../common/globals.h" #include #include #include #include #include #include #include #include class TestConfig : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testSimpleConfig(); void testTwoScreenConfig(); void testRotatedScreenConfig(); void testDisabledScreenConfig(); void testConfig404(); void testCorruptConfig(); void testCorruptEmptyConfig(); void testCorruptUselessConfig(); void testNullConfig(); void testIdenticalOutputs(); void testMoveConfig(); void testFixedConfig(); private: std::unique_ptr createConfig(bool output1Connected, bool output2Conected); }; std::unique_ptr TestConfig::createConfig(bool output1Connected, bool output2Connected) { KScreen::ScreenPtr screen = KScreen::ScreenPtr::create(); screen->setCurrentSize(QSize(1920, 1080)); screen->setMaxSize(QSize(32768, 32768)); screen->setMinSize(QSize(8, 8)); QList sizes({ QSize(320, 240), QSize(640, 480), QSize(1024, 768), QSize(1280, 1024), QSize(1920, 1280) }); KScreen::ModeList modes; for (int i = 0; i < sizes.count(); ++i) { const QSize &size = sizes[i]; KScreen::ModePtr mode = KScreen::ModePtr::create(); mode->setId(QStringLiteral("MODE-%1").arg(i)); mode->setName(QStringLiteral("%1x%2").arg(size.width()).arg(size.height())); mode->setSize(size); mode->setRefreshRate(60.0); modes.insert(mode->id(), mode); } KScreen::OutputPtr output1 = KScreen::OutputPtr::create(); output1->setId(1); output1->setName(QStringLiteral("OUTPUT-1")); output1->setPos(QPoint(0, 0)); output1->setConnected(output1Connected); output1->setEnabled(output1Connected); if (output1Connected) { output1->setModes(modes); } KScreen::OutputPtr output2 = KScreen::OutputPtr::create(); output2->setId(2); output2->setName(QStringLiteral("OUTPUT-2")); output2->setPos(QPoint(0, 0)); output2->setConnected(output2Connected); if (output2Connected) { output2->setModes(modes); } KScreen::ConfigPtr config = KScreen::ConfigPtr::create(); config->setScreen(screen); config->addOutput(output1); config->addOutput(output2); auto configWrapper = std::unique_ptr(new Config(config)); return configWrapper; } void TestConfig::initTestCase() { qputenv("KSCREEN_LOGGING", "false"); - Config::setDirPath(QStringLiteral(TEST_DATA "serializerdata/")); + Globals::setDirPath(QStringLiteral(TEST_DATA "serializerdata/")); } void TestConfig::testSimpleConfig() { auto configWrapper = createConfig(true, false); configWrapper = std::move(configWrapper->readFile(QStringLiteral("simpleConfig.json"))); auto config = configWrapper->data(); QVERIFY(config); QCOMPARE(config->connectedOutputs().count(), 1); auto output = config->connectedOutputs().first(); QCOMPARE(output->name(), QLatin1String("OUTPUT-1")); QCOMPARE(output->currentModeId(), QLatin1String("MODE-4")); QCOMPARE(output->currentMode()->size(), QSize(1920, 1280)); QCOMPARE(output->isEnabled(), true); QCOMPARE(output->rotation(), KScreen::Output::None); QCOMPARE(output->pos(), QPoint(0, 0)); QCOMPARE(output->isPrimary(), true); auto screen = config->screen(); QCOMPARE(screen->currentSize(), QSize(1920, 1280)); } void TestConfig::testTwoScreenConfig() { auto configWrapper = createConfig(true, true); configWrapper = std::move(configWrapper->readFile(QStringLiteral("twoScreenConfig.json"))); auto config = configWrapper->data(); QVERIFY(config); QCOMPARE(config->connectedOutputs().count(), 2); auto output = config->connectedOutputs().first(); QCOMPARE(output->name(), QLatin1String("OUTPUT-1")); QCOMPARE(output->currentModeId(), QLatin1String("MODE-4")); QCOMPARE(output->currentMode()->size(), QSize(1920, 1280)); QCOMPARE(output->isEnabled(), true); QCOMPARE(output->rotation(), KScreen::Output::None); QCOMPARE(output->pos(), QPoint(0, 0)); QCOMPARE(output->isPrimary(), true); output = config->connectedOutputs().last(); QCOMPARE(output->name(), QLatin1String("OUTPUT-2")); QCOMPARE(output->currentModeId(), QLatin1String("MODE-3")); QCOMPARE(output->currentMode()->size(), QSize(1280, 1024)); QCOMPARE(output->isEnabled(), true); QCOMPARE(output->rotation(), KScreen::Output::None); QCOMPARE(output->pos(), QPoint(1920, 0)); QCOMPARE(output->isPrimary(), false); auto screen = config->screen(); QCOMPARE(screen->currentSize(), QSize(3200, 1280)); } void TestConfig::testRotatedScreenConfig() { auto configWrapper = createConfig(true, true); configWrapper = std::move(configWrapper->readFile(QStringLiteral("rotatedScreenConfig.json"))); auto config = configWrapper->data(); QVERIFY(config); QCOMPARE(config->connectedOutputs().count(), 2); auto output = config->connectedOutputs().first(); QCOMPARE(output->name(), QLatin1String("OUTPUT-1")); QCOMPARE(output->currentModeId(), QLatin1String("MODE-4")); QCOMPARE(output->currentMode()->size(), QSize(1920, 1280)); QCOMPARE(output->isEnabled(), true); QCOMPARE(output->rotation(), KScreen::Output::None); QCOMPARE(output->pos(), QPoint(0, 0)); QCOMPARE(output->isPrimary(), true); output = config->connectedOutputs().last(); QCOMPARE(output->name(), QLatin1String("OUTPUT-2")); QCOMPARE(output->currentModeId(), QLatin1String("MODE-3")); QCOMPARE(output->currentMode()->size(), QSize(1280, 1024)); QCOMPARE(output->isEnabled(), true); QCOMPARE(output->rotation(), KScreen::Output::Left); QCOMPARE(output->pos(), QPoint(1920, 0)); QCOMPARE(output->isPrimary(), false); auto screen = config->screen(); QCOMPARE(screen->currentSize(), QSize(2944, 1280)); } void TestConfig::testDisabledScreenConfig() { auto configWrapper = createConfig(true, true); configWrapper = std::move(configWrapper->readFile(QStringLiteral("disabledScreenConfig.json"))); auto config = configWrapper->data(); QVERIFY(config); QCOMPARE(config->connectedOutputs().count(), 2); auto output = config->connectedOutputs().first(); QCOMPARE(output->name(), QLatin1String("OUTPUT-1")); QCOMPARE(output->currentModeId(), QLatin1String("MODE-4")); QCOMPARE(output->currentMode()->size(), QSize(1920, 1280)); QCOMPARE(output->isEnabled(), true); QCOMPARE(output->rotation(), KScreen::Output::None); QCOMPARE(output->pos(), QPoint(0, 0)); QCOMPARE(output->isPrimary(), true); output = config->connectedOutputs().last(); QCOMPARE(output->name(), QLatin1String("OUTPUT-2")); QCOMPARE(output->isEnabled(), false); auto screen = config->screen(); QCOMPARE(screen->currentSize(), QSize(1920, 1280)); } void TestConfig::testConfig404() { auto configWrapper = createConfig(true, true); configWrapper = std::move(configWrapper->readFile(QStringLiteral("filenotfoundConfig.json"))); QVERIFY(!configWrapper); } void TestConfig::testCorruptConfig() { auto configWrapper = createConfig(true, true); configWrapper = std::move(configWrapper->readFile(QStringLiteral("corruptConfig.json"))); auto config = configWrapper->data(); QVERIFY(config); QCOMPARE(config->outputs().count(), 2); QVERIFY(config->isValid()); } void TestConfig::testCorruptEmptyConfig() { auto configWrapper = createConfig(true, true); configWrapper = std::move(configWrapper->readFile(QStringLiteral("corruptEmptyConfig.json"))); auto config = configWrapper->data(); QVERIFY(config); QCOMPARE(config->outputs().count(), 2); QVERIFY(config->isValid()); } void TestConfig::testCorruptUselessConfig() { auto configWrapper = createConfig(true, true); configWrapper = std::move(configWrapper->readFile(QStringLiteral("corruptUselessConfig.json"))); auto config = configWrapper->data(); QVERIFY(config); QCOMPARE(config->outputs().count(), 2); QVERIFY(config->isValid()); } void TestConfig::testNullConfig() { Config nullConfig(nullptr); QVERIFY(!nullConfig.data()); // Null configs have empty configIds QVERIFY(nullConfig.id().isEmpty()); // Load config from a file not found results in a nullptr auto config = createConfig(true, true); QVERIFY(!config->readFile(QString())); // Wrong config file name should fail to save QVERIFY(!config->writeFile(QString())); } void TestConfig::testIdenticalOutputs() { // Test configuration of a video wall with 6 identical outputs connected // this is the autotest for https://bugs.kde.org/show_bug.cgi?id=325277 KScreen::ScreenPtr screen = KScreen::ScreenPtr::create(); screen->setCurrentSize(QSize(1920, 1080)); screen->setMaxSize(QSize(32768, 32768)); screen->setMinSize(QSize(8, 8)); QList sizes({ QSize(640, 480), QSize(1024, 768), QSize(1920, 1080), QSize(1280, 1024), QSize(1920, 1280) }); KScreen::ModeList modes; for (int i = 0; i < sizes.count(); ++i) { const QSize &size = sizes[i]; KScreen::ModePtr mode = KScreen::ModePtr::create(); mode->setId(QStringLiteral("MODE-%1").arg(i)); mode->setName(QStringLiteral("%1x%2").arg(size.width()).arg(size.height())); mode->setSize(size); mode->setRefreshRate(60.0); modes.insert(mode->id(), mode); } // This one is important, the output id in the config file is a hash of it QByteArray data = QByteArray::fromBase64("AP///////wAQrBbwTExLQQ4WAQOANCB46h7Frk80sSYOUFSlSwCBgKlA0QBxTwEBAQEBAQEBKDyAoHCwI0AwIDYABkQhAAAaAAAA/wBGNTI1TTI0NUFLTEwKAAAA/ABERUxMIFUyNDEwCiAgAAAA/QA4TB5REQAKICAgICAgAToCAynxUJAFBAMCBxYBHxITFCAVEQYjCQcHZwMMABAAOC2DAQAA4wUDAQI6gBhxOC1AWCxFAAZEIQAAHgEdgBhxHBYgWCwlAAZEIQAAngEdAHJR0B4gbihVAAZEIQAAHowK0Iog4C0QED6WAAZEIQAAGAAAAAAAAAAAAAAAAAAAPg=="); // When setting up the outputs, make sure they're not added in alphabetical order // or in the same order of the config file, as that makes the tests accidentally pass KScreen::OutputPtr output1 = KScreen::OutputPtr::create(); output1->setId(1); output1->setEdid(data); output1->setName(QStringLiteral("DisplayPort-0")); output1->setPos(QPoint(0, 0)); output1->setConnected(true); output1->setEnabled(false); output1->setModes(modes); KScreen::OutputPtr output2 = KScreen::OutputPtr::create(); output2->setId(2); output2->setEdid(data); output2->setName(QStringLiteral("DisplayPort-1")); output2->setPos(QPoint(0, 0)); output2->setConnected(true); output2->setEnabled(false); output2->setModes(modes); KScreen::OutputPtr output3 = KScreen::OutputPtr::create(); output3->setId(3); output3->setEdid(data); output3->setName(QStringLiteral("DisplayPort-2")); output3->setPos(QPoint(0, 0)); output3->setConnected(true); output3->setEnabled(false); output3->setModes(modes); KScreen::OutputPtr output6 = KScreen::OutputPtr::create(); output6->setId(6); output6->setEdid(data); output6->setName(QStringLiteral("DVI-0")); output6->setPos(QPoint(0, 0)); output6->setConnected(true); output6->setEnabled(false); output6->setModes(modes); KScreen::OutputPtr output4 = KScreen::OutputPtr::create(); output4->setId(4); output4->setEdid(data); output4->setName(QStringLiteral("DisplayPort-3")); output4->setPos(QPoint(0, 0)); output4->setConnected(true); output4->setEnabled(false); output4->setModes(modes); KScreen::OutputPtr output5 = KScreen::OutputPtr::create(); output5->setId(5); output5->setEdid(data); output5->setName(QStringLiteral("DVI-1")); output5->setPos(QPoint(0, 0)); output5->setConnected(true); output5->setEnabled(false); output5->setModes(modes); KScreen::ConfigPtr config = KScreen::ConfigPtr::create(); config->setScreen(screen); config->addOutput(output6); config->addOutput(output2); config->addOutput(output5); config->addOutput(output4); config->addOutput(output3); config->addOutput(output1); Config configWrapper(config); - configWrapper.setDirPath(QStringLiteral(TEST_DATA "serializerdata/")); QHash positions; positions[QStringLiteral("DisplayPort-0")] = QPoint(0, 1080); positions[QStringLiteral("DisplayPort-1")] = QPoint(2100, 30); positions[QStringLiteral("DisplayPort-2")] = QPoint(2100, 1080); positions[QStringLiteral("DisplayPort-3")] = QPoint(4020, 0); positions[QStringLiteral("DVI-0")] = QPoint(4020, 1080); positions[QStringLiteral("DVI-1")] = QPoint(0, 0); auto configWrapper2 = std::move(configWrapper.readFile(QStringLiteral("outputgrid_2x3.json"))); KScreen::ConfigPtr config2 = configWrapper2->data(); QVERIFY(config2); QVERIFY(config != config2); QCOMPARE(config2->connectedOutputs().count(), 6); Q_FOREACH (auto output, config2->connectedOutputs()) { QVERIFY(positions.keys().contains(output->name())); QVERIFY(output->name() != output->hash()); QCOMPARE(positions[output->name()], output->pos()); QCOMPARE(output->currentMode()->size(), QSize(1920, 1080)); QCOMPARE(output->currentMode()->refreshRate(), 60.0); QVERIFY(output->isEnabled()); } QCOMPARE(config2->screen()->currentSize(), QSize(5940, 2160)); } void TestConfig::testMoveConfig() { // Test if restoring a config using Serializer::moveConfig(src, dest) works // https://bugs.kde.org/show_bug.cgi?id=353029 // Load a dualhead config auto configWrapper = createConfig(true, true); configWrapper = std::move(configWrapper->readFile(QStringLiteral("twoScreenConfig.json"))); auto config = configWrapper->data(); QVERIFY(config); // Make sure we don't write into TEST_DATA QStandardPaths::setTestModeEnabled(true); - configWrapper->setDirPath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) % QStringLiteral("/kscreen/")); + Globals::setDirPath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) % QStringLiteral("/kscreen/")); // TODO: this needs setup of the control directory // Basic assumptions for the remainder of our tests, this is the situation where the lid is opened QCOMPARE(config->connectedOutputs().count(), 2); auto output = config->connectedOutputs().first(); QCOMPARE(output->name(), QLatin1String("OUTPUT-1")); QCOMPARE(output->isEnabled(), true); QCOMPARE(output->isPrimary(), true); auto output2 = config->connectedOutputs().last(); QCOMPARE(output2->name(), QLatin1String("OUTPUT-2")); QCOMPARE(output2->isEnabled(), true); QCOMPARE(output2->isPrimary(), false); // we fake the lid being closed, first save our current config to _lidOpened configWrapper->writeOpenLidFile(); // ... then switch off the panel, set primary to the other output output->setEnabled(false); output->setPrimary(false); output2->setPrimary(true); // save config as the current one, this is the config we don't want restored, and which we'll overwrite configWrapper->writeFile(); QCOMPARE(output->isEnabled(), false); QCOMPARE(output->isPrimary(), false); QCOMPARE(output2->isPrimary(), true); // Check if both files exist const QString closedPath = Config::configsDirPath() % configWrapper->id(); const QString openedPath = closedPath % QStringLiteral("_lidOpened"); QFile openCfg(openedPath); QFile closedCfg(closedPath); QVERIFY(openCfg.exists()); QVERIFY(closedCfg.exists()); // Switcheroolooloo... configWrapper = std::move(configWrapper->readOpenLidFile()); QVERIFY(configWrapper); // Check actual files, src should be gone, dest must exist QVERIFY(!openCfg.exists()); QVERIFY(closedCfg.exists()); // Make sure the laptop panel is enabled and primary again config = configWrapper->data(); output = config->connectedOutputs().first(); QCOMPARE(output->name(), QLatin1String("OUTPUT-1")); QCOMPARE(output->isEnabled(), true); QCOMPARE(output->isPrimary(), true); output2 = config->connectedOutputs().last(); QCOMPARE(output2->name(), QLatin1String("OUTPUT-2")); QCOMPARE(output2->isEnabled(), true); QCOMPARE(output2->isPrimary(), false); // Make sure we don't screw up when there's no _lidOpened config configWrapper = std::move(configWrapper->readOpenLidFile()); config = configWrapper->data(); output = config->connectedOutputs().first(); QCOMPARE(output->name(), QLatin1String("OUTPUT-1")); QCOMPARE(output->isEnabled(), true); QCOMPARE(output->isPrimary(), true); output2 = config->connectedOutputs().last(); QCOMPARE(output2->name(), QLatin1String("OUTPUT-2")); QCOMPARE(output2->isEnabled(), true); QCOMPARE(output2->isPrimary(), false); - Config::setDirPath(QStringLiteral(TEST_DATA "/serializerdata/")); + Globals::setDirPath(QStringLiteral(TEST_DATA "/serializerdata/")); } void TestConfig::testFixedConfig() { // Load a dualhead config auto configWrapper = createConfig(true, true); configWrapper = std::move(configWrapper->readFile(QStringLiteral("twoScreenConfig.json"))); auto config = configWrapper->data(); QVERIFY(config); // Make sure we don't write into TEST_DATA QStandardPaths::setTestModeEnabled(true); - Config::setDirPath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) % QStringLiteral("/kscreen/")); + Globals::setDirPath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) % QStringLiteral("/kscreen/")); // TODO: this needs setup of the control directory const QString fixedCfgPath = Config::configsDirPath() % Config::s_fixedConfigFileName; // save config as the current one, this is the config we don't want restored, and which we'll overwrite configWrapper->writeFile(fixedCfgPath); // Check if both files exist QFile fixedCfg(fixedCfgPath); QVERIFY(fixedCfg.exists()); - Config::setDirPath(QStringLiteral(TEST_DATA "/serializerdata/")); + Globals::setDirPath(QStringLiteral(TEST_DATA "/serializerdata/")); } QTEST_MAIN(TestConfig) #include "configtest.moc"