diff --git a/CMakeLists.txt b/CMakeLists.txt index 26a3643..b5b726e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,107 +1,107 @@ cmake_minimum_required(VERSION 2.8.12) project(libkscreen) -set(PROJECT_VERSION "5.10.90") +set(PROJECT_VERSION "5.11.90") find_package(ECM 5.14.0 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(KDEInstallDirs) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(ECMSetupVersion) include(ECMPackageConfigHelpers) include(ECMMarkAsTest) include(ECMGenerateHeaders) include(ECMQtDeclareLoggingCategory) include(FeatureSummary) include(CheckCXXCompilerFlag) set(REQUIRED_QT_VERSION 5.4.0) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Core DBus Gui Test X11Extras) # Wayland backend find_package(KF5Wayland CONFIG REQUIRED) add_feature_info("KF5Wayland" KF5Wayland_FOUND "Required for building libkscreen's KWayland backend") # xrandr backend find_package(XCB COMPONENTS XCB RANDR) set_package_properties(XCB PROPERTIES TYPE OPTIONAL PURPOSE "Required for building XRandR backends" ) # library setup set(KF5_VERSION ${PROJECT_VERSION}) #When we are happy with the api, we can sync with frameworks ecm_setup_version(${KF5_VERSION} VARIABLE_PREFIX KSCREEN VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kscreen_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5ScreenConfigVersion.cmake" SOVERSION 7) check_cxx_compiler_flag(-fvisibility=hidden _HAVE_VISIBILITY) if (_HAVE_VISIBILITY AND NOT WIN32) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") check_cxx_compiler_flag(-fvisibility-inlines-hidden _HAVE_VISIBILITY_INLINES) if (_HAVE_VISIBILITY_INLINES) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility-inlines-hidden") endif (_HAVE_VISIBILITY_INLINES) endif (_HAVE_VISIBILITY AND NOT WIN32) add_subdirectory(src) add_subdirectory(backends) add_subdirectory(autotests) add_subdirectory(tests) set(CMAKECONFIG_INSTALL_DIR "${CMAKECONFIG_INSTALL_PREFIX}/KF5Screen") ecm_configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/KF5ScreenConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5ScreenConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5ScreenConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5ScreenConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5ScreenTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5ScreenTargets.cmake NAMESPACE KF5:: COMPONENT Devel ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/kscreen_version.h" DESTINATION "${KF5_INCLUDE_INSTALL_DIR}" COMPONENT Devel ) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) ############################################ apidox ############################################ option(LIBKSCREEN_BUILD_API_DOCS "Build libkscreen API documentation") if(LIBKSCREEN_BUILD_API_DOCS) find_package(Doxygen) if(DOXYGEN_EXECUTABLE) configure_file(${libkscreen_SOURCE_DIR}/.Doxyfile.cmake ${libkscreen_BINARY_DIR}/Doxyfile) if(EXISTS ${QT_DOC_DIR}/html) set(QTDOCS "${QT_DOC_DIR}/html") else(EXISTS ${QT_DOC_DIR}/html) set(QTDOCS "http://doc.qt.nokia.com/latest/") endif(EXISTS ${QT_DOC_DIR}/html) add_custom_target( apidox ALL COMMAND ${DOXYGEN_EXECUTABLE} Doxyfile COMMAND doc/html/installdox -l qt4.tag@${QTDOCS} doc/html/*.html) endif(DOXYGEN_EXECUTABLE) endif(LIBKSCREEN_BUILD_API_DOCS) diff --git a/autotests/testkwaylandconfig.cpp b/autotests/testkwaylandconfig.cpp index aaff77a..b32610f 100644 --- a/autotests/testkwaylandconfig.cpp +++ b/autotests/testkwaylandconfig.cpp @@ -1,230 +1,266 @@ /************************************************************************************* * Copyright 2015 by Sebastian Kügler * * * * 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 St, Fifth Floor, Boston, MA 02110-1301 USA * *************************************************************************************/ #include #include #include #include #include "backendmanager_p.h" #include "getconfigoperation.h" #include "setconfigoperation.h" #include "config.h" #include "configmonitor.h" #include "output.h" #include "mode.h" #include "edid.h" #include "waylandtestserver.h" Q_LOGGING_CATEGORY(KSCREEN_WAYLAND, "kscreen.kwayland") using namespace KScreen; class TestKWaylandConfig : public QObject { Q_OBJECT public: explicit TestKWaylandConfig(QObject *parent = nullptr); private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void changeConfig(); void testPositionChange(); void testRotationChange(); void testRotationChange_data(); + void testScaleChange(); void testModeChange(); private: WaylandTestServer *m_server; }; TestKWaylandConfig::TestKWaylandConfig(QObject *parent) : QObject(parent) , m_server(nullptr) { qputenv("KSCREEN_LOGGING", "false"); } void TestKWaylandConfig::initTestCase() { setenv("KSCREEN_BACKEND", "kwayland", 1); KScreen::BackendManager::instance()->shutdownBackend(); // This is how KWayland will pick up the right socket, // and thus connect to our internal test server. setenv("WAYLAND_DISPLAY", s_socketName.toLocal8Bit(), 1); m_server = new WaylandTestServer(this); m_server->start(); } void TestKWaylandConfig::cleanupTestCase() { qDebug() << "Shutting down"; KScreen::BackendManager::instance()->shutdownBackend(); delete m_server; } void TestKWaylandConfig::changeConfig() { auto op = new GetConfigOperation(); QVERIFY(op->exec()); auto config = op->config(); QVERIFY(config); // Prepare monitor & spy KScreen::ConfigMonitor *monitor = KScreen::ConfigMonitor::instance(); monitor->addConfig(config); QSignalSpy configSpy(monitor, &KScreen::ConfigMonitor::configurationChanged); // The first output is currently disabled, let's try to enable it auto output = config->outputs().first(); QVERIFY(output->isEnabled() == false); output->setEnabled(true); output->setCurrentModeId("76"); auto output2 = config->outputs()[2]; // is this id stable enough? output2->setPos(QPoint(4000, 1080)); output2->setRotation(KScreen::Output::Left); QSignalSpy serverSpy(m_server, &WaylandTestServer::configChanged); auto sop = new SetConfigOperation(config, this); sop->exec(); // fire and forget... QVERIFY(configSpy.wait()); // check if the server changed QCOMPARE(serverSpy.count(), 1); QCOMPARE(configSpy.count(), 1); monitor->removeConfig(config); m_server->showOutputs(); } void TestKWaylandConfig::testPositionChange() { auto op = new GetConfigOperation(); QVERIFY(op->exec()); auto config = op->config(); QVERIFY(config); // Prepare monitor & spy KScreen::ConfigMonitor *monitor = KScreen::ConfigMonitor::instance(); monitor->addConfig(config); QSignalSpy configSpy(monitor, &KScreen::ConfigMonitor::configurationChanged); auto output = config->outputs()[2]; // is this id stable enough? auto new_pos = QPoint(3840, 1080); output->setPos(new_pos); QSignalSpy serverSpy(m_server, &WaylandTestServer::configChanged); auto sop = new SetConfigOperation(config, this); sop->exec(); // fire and forget... QVERIFY(configSpy.wait()); // check if the server changed QCOMPARE(serverSpy.count(), 1); QCOMPARE(configSpy.count(), 1); } void TestKWaylandConfig::testRotationChange_data() { QTest::addColumn("rotation"); QTest::newRow("left") << KScreen::Output::Left; QTest::newRow("inverted") << KScreen::Output::Inverted; QTest::newRow("right") << KScreen::Output::Right; QTest::newRow("none") << KScreen::Output::None; } void TestKWaylandConfig::testRotationChange() { QFETCH(KScreen::Output::Rotation, rotation); auto op = new GetConfigOperation(); QVERIFY(op->exec()); auto config = op->config(); QVERIFY(config); // Prepare monitor & spy KScreen::ConfigMonitor *monitor = KScreen::ConfigMonitor::instance(); monitor->addConfig(config); QSignalSpy configSpy(monitor, &KScreen::ConfigMonitor::configurationChanged); auto output = config->outputs().first(); // is this id stable enough? output->setRotation(rotation); QSignalSpy serverSpy(m_server, &WaylandTestServer::configChanged); auto sop = new SetConfigOperation(config, this); sop->exec(); // fire and forget... QVERIFY(configSpy.wait()); // check if the server changed QCOMPARE(serverSpy.count(), 1); QCOMPARE(configSpy.count(), 1); // Get a new config, then compare the output with the expected new value auto newop = new GetConfigOperation(); QVERIFY(newop->exec()); auto newconfig = newop->config(); QVERIFY(newconfig); auto newoutput = newconfig->outputs().first(); QCOMPARE(newoutput->rotation(), rotation); } +void TestKWaylandConfig::testScaleChange() +{ + auto op = new GetConfigOperation(); + QVERIFY(op->exec()); + auto config = op->config(); + QVERIFY(config); + + auto op2 = new GetConfigOperation(); + QVERIFY(op2->exec()); + auto config2 = op2->config(); + QVERIFY(config2); + + // Prepare monitor & spy + KScreen::ConfigMonitor *monitor = KScreen::ConfigMonitor::instance(); + monitor->addConfig(config); + QSignalSpy configSpy(monitor, &KScreen::ConfigMonitor::configurationChanged); + + auto output2 = config2->outputs()[2]; // is this id stable enough? + QCOMPARE(output2->scale(), 1.0); + + auto output = config->outputs()[2]; // is this id stable enough? + output->setScale(2); + + QSignalSpy serverSpy(m_server, &WaylandTestServer::configChanged); + auto sop = new SetConfigOperation(config, this); + sop->exec(); // fire and forget... + + QVERIFY(configSpy.wait()); + // check if the server changed + QCOMPARE(serverSpy.count(), 1); + + QCOMPARE(configSpy.count(), 1); + QCOMPARE(output2->scale(), 2.0); +} + void TestKWaylandConfig::testModeChange() { auto op = new GetConfigOperation(); QVERIFY(op->exec()); auto config = op->config(); QVERIFY(config); KScreen::ConfigMonitor *monitor = KScreen::ConfigMonitor::instance(); monitor->addConfig(config); QSignalSpy configSpy(monitor, &KScreen::ConfigMonitor::configurationChanged); auto output = config->outputs()[1]; // is this id stable enough? QString new_mode = QStringLiteral("74"); output->setCurrentModeId(new_mode); QSignalSpy serverSpy(m_server, &WaylandTestServer::configChanged); auto sop = new SetConfigOperation(config, this); sop->exec(); QVERIFY(configSpy.wait()); // check if the server changed QCOMPARE(serverSpy.count(), 1); QCOMPARE(configSpy.count(), 1); } QTEST_GUILESS_MAIN(TestKWaylandConfig) #include "testkwaylandconfig.moc" diff --git a/backends/xrandr/xrandrconfig.cpp b/backends/xrandr/xrandrconfig.cpp index 6a54112..fece317 100644 --- a/backends/xrandr/xrandrconfig.cpp +++ b/backends/xrandr/xrandrconfig.cpp @@ -1,586 +1,586 @@ /************************************************************************************* * Copyright (C) 2012 by Alejandro Fiestas Olivares * * Copyright (C) 2012 - 2015 by Daniel Vrátil * * * * 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 St, Fifth Floor, Boston, MA 02110-1301 USA * *************************************************************************************/ #include "xrandrconfig.h" #include "xrandrscreen.h" #include "xrandr.h" #include "xrandrmode.h" #include "xrandroutput.h" #include "xrandrcrtc.h" #include "config.h" #include "output.h" #include "edid.h" #include "../xcbwrapper.h" #include #include #include using namespace KScreen; XRandRConfig::XRandRConfig() : QObject() , m_screen(Q_NULLPTR) { m_screen = new XRandRScreen(this); XCB::ScopedPointer resources(XRandR::screenResources()); xcb_randr_crtc_t *crtcs = xcb_randr_get_screen_resources_crtcs(resources.data()); for (int i = 0, c = xcb_randr_get_screen_resources_crtcs_length(resources.data()); i < c; ++i) { addNewCrtc(crtcs[i]); } xcb_randr_output_t *outputs = xcb_randr_get_screen_resources_outputs(resources.data()); for (int i = 0, c = xcb_randr_get_screen_resources_outputs_length(resources.data()); i < c; ++i) { addNewOutput(outputs[i]); } } XRandRConfig::~XRandRConfig() { qDeleteAll(m_outputs); qDeleteAll(m_crtcs); delete m_screen; } XRandROutput::Map XRandRConfig::outputs() const { return m_outputs; } XRandROutput* XRandRConfig::output(xcb_randr_output_t output) const { return m_outputs[output]; } XRandRCrtc::Map XRandRConfig::crtcs() const { return m_crtcs; } XRandRCrtc* XRandRConfig::crtc(xcb_randr_crtc_t crtc) const { return m_crtcs[crtc]; } XRandRScreen* XRandRConfig::screen() const { return m_screen; } void XRandRConfig::addNewOutput(xcb_randr_output_t id) { XRandROutput *xOutput = new XRandROutput(id, this); m_outputs.insert(id, xOutput); } void XRandRConfig::addNewCrtc(xcb_randr_crtc_t crtc) { m_crtcs.insert(crtc, new XRandRCrtc(crtc, this)); } void XRandRConfig::removeOutput(xcb_randr_output_t id) { delete m_outputs.take(id); } KScreen::ConfigPtr XRandRConfig::toKScreenConfig() const { KScreen::ConfigPtr config(new KScreen::Config); auto features = Config::Feature::Writable | Config::Feature::PrimaryDisplay; config->setSupportedFeatures(features); KScreen::OutputList kscreenOutputs; for (auto iter = m_outputs.constBegin(); iter != m_outputs.constEnd(); ++iter) { KScreen::OutputPtr kscreenOutput = (*iter)->toKScreenOutput(); kscreenOutputs.insert(kscreenOutput->id(), kscreenOutput); } config->setOutputs(kscreenOutputs); config->setScreen(m_screen->toKScreenScreen()); return config; } void XRandRConfig::applyKScreenConfig(const KScreen::ConfigPtr &config) { const KScreen::OutputList kscreenOutputs = config->outputs(); const QSize newScreenSize = screenSize(config); const QSize currentScreenSize = m_screen->currentSize(); // When the current screen configuration is bigger than the new size (like // when rotating an output), the XSetScreenSize can fail or apply the smaller // size only partially, because we apply the size (we have to) before the // output changes. To prevent all kinds of weird screen sizes from happening, // we initially set such screen size, that it can take the current as well // as the new configuration, then we apply the output changes, and finally then // (if necessary) we reduce the screen size to fix the new configuration precisely. const QSize intermediateScreenSize = QSize(qMax(newScreenSize.width(), currentScreenSize.width()), qMax(newScreenSize.height(), currentScreenSize.height())); int neededCrtcs = 0; xcb_randr_output_t primaryOutput = 0; xcb_randr_output_t oldPrimaryOutput = 0; Q_FOREACH (const XRandROutput *xrandrOutput, m_outputs) { if (xrandrOutput->isPrimary()) { oldPrimaryOutput = xrandrOutput->id(); break; } } KScreen::OutputList toDisable, toEnable, toChange; Q_FOREACH(const KScreen::OutputPtr &kscreenOutput, kscreenOutputs) { xcb_randr_output_t outputId = kscreenOutput->id(); XRandROutput *currentOutput = output(outputId); //Only set the output as primary if it is enabled. if (kscreenOutput->isPrimary() && kscreenOutput->isEnabled()) { primaryOutput = outputId; } const bool currentEnabled = currentOutput->isEnabled(); if (!kscreenOutput->isEnabled() && currentEnabled) { toDisable.insert(outputId, kscreenOutput); continue; } else if (kscreenOutput->isEnabled() && !currentEnabled) { toEnable.insert(outputId, kscreenOutput); ++neededCrtcs; continue; } else if (!kscreenOutput->isEnabled() && !currentEnabled) { continue; } ++neededCrtcs; if (kscreenOutput->currentModeId() != currentOutput->currentModeId()) { if (!toChange.contains(outputId)) { toChange.insert(outputId, kscreenOutput); } } if (kscreenOutput->pos() != currentOutput->position()) { if (!toChange.contains(outputId)) { toChange.insert(outputId, kscreenOutput); } } if (kscreenOutput->rotation() != currentOutput->rotation()) { if (!toChange.contains(outputId)) { toChange.insert(outputId, kscreenOutput); } } XRandRMode *currentMode = currentOutput->modes().value(kscreenOutput->currentModeId().toInt()); // For some reason, in some environments currentMode is null // which doesn't make sense because it is the *current* mode... // Since we haven't been able to figure out the reason why // this happens, we are adding this debug code to try to // figure out how this happened. if (!currentMode) { qWarning() << "Current mode is null:" << "ModeId:" << currentOutput->currentModeId() << "Mode: " << currentOutput->currentMode() << "Output: " << currentOutput->id(); // qDebug() << kRealBacktrace(256); printConfig(config); printInternalCond(); - return; + continue; } // If the output would not fit into new screen size, we need to disable // it and reposition it const QRect geom = kscreenOutput->geometry(); if (geom.right() > newScreenSize.width() || geom.bottom() > newScreenSize.height()) { if (!toDisable.contains(outputId)) { qCDebug(KSCREEN_XRANDR) << "The new output would not fit into screen - new geometry: " << geom << ", new screen size:" << newScreenSize; toDisable.insert(outputId, kscreenOutput); } } } // Q_FOREACH (const KScreen::OutputPtr &kscreenOutput, kscreenOutputs) const KScreen::ScreenPtr kscreenScreen = config->screen(); if (newScreenSize.width() > kscreenScreen->maxSize().width() || newScreenSize.height() > kscreenScreen->maxSize().height()) { qCDebug(KSCREEN_XRANDR) << "The new screen size is too big - requested: " << newScreenSize << ", maximum: " << kscreenScreen->maxSize(); return; } qCDebug(KSCREEN_XRANDR) << "Needed CRTCs: " << neededCrtcs; XCB::ScopedPointer screenResources(XRandR::screenResources()); if (neededCrtcs > screenResources->num_crtcs) { qCDebug(KSCREEN_XRANDR) << "We need more CRTCs than we have available - requested: " << neededCrtcs << ", available: " << screenResources->num_crtcs; return; } qCDebug(KSCREEN_XRANDR) << "Actions to perform:"; qCDebug(KSCREEN_XRANDR) << "\tPrimary Output:" << (primaryOutput != oldPrimaryOutput); if (primaryOutput != oldPrimaryOutput) { qCDebug(KSCREEN_XRANDR) << "\t\tOld:" << oldPrimaryOutput; qCDebug(KSCREEN_XRANDR) << "\t\tNew:" << primaryOutput; } qCDebug(KSCREEN_XRANDR) << "\tChange Screen Size:" << (newScreenSize != currentScreenSize); if (newScreenSize != currentScreenSize) { qCDebug(KSCREEN_XRANDR) << "\t\tOld:" << currentScreenSize; qCDebug(KSCREEN_XRANDR) << "\t\tIntermediate:" << intermediateScreenSize; qCDebug(KSCREEN_XRANDR) << "\t\tNew:" << newScreenSize; } qCDebug(KSCREEN_XRANDR) << "\tDisable outputs:" << !toDisable.isEmpty(); if (!toDisable.isEmpty()) { qCDebug(KSCREEN_XRANDR) << "\t\t" << toDisable.keys(); } qCDebug(KSCREEN_XRANDR) << "\tChange outputs:" << !toChange.isEmpty(); if (!toChange.isEmpty()) { qCDebug(KSCREEN_XRANDR) << "\t\t" << toChange.keys(); } qCDebug(KSCREEN_XRANDR) << "\tEnable outputs:" << !toEnable.isEmpty(); if (!toEnable.isEmpty()) { qCDebug(KSCREEN_XRANDR) << "\t\t" << toEnable.keys(); } // Grab the server so that no-one else can do changes to XRandR and to block // change notifications until we are done XCB::GrabServer grabber; //If there is nothing to do, not even bother if (oldPrimaryOutput == primaryOutput && toDisable.isEmpty() && toEnable.isEmpty() && toChange.isEmpty()) { if (newScreenSize != currentScreenSize) { setScreenSize(newScreenSize); } return; } Q_FOREACH(const KScreen::OutputPtr &output, toDisable) { disableOutput(output); } if (intermediateScreenSize != currentScreenSize) { setScreenSize(intermediateScreenSize); } bool forceScreenSizeUpdate = false; Q_FOREACH(const KScreen::OutputPtr &output, toChange) { if (!changeOutput(output)) { /* If we disabled the output before changing it and XRandR failed * to re-enable it, then update screen size too */ if (toDisable.contains(output->id())) { //output->setEnabled(false); qCDebug(KSCREEN_XRANDR) << "Output failed to change: " << output->name(); forceScreenSizeUpdate = true; } } } Q_FOREACH(const KScreen::OutputPtr &output, toEnable) { if (!enableOutput(output)) { //output->setEnabled(false); qCDebug(KSCREEN_XRANDR) << "Output failed to be Enabled: " << output->name(); forceScreenSizeUpdate = true; } } if (oldPrimaryOutput != primaryOutput) { setPrimaryOutput(primaryOutput); } if (forceScreenSizeUpdate || intermediateScreenSize != newScreenSize) { QSize newSize = newScreenSize; if (forceScreenSizeUpdate) { newSize = screenSize(config); qCDebug(KSCREEN_XRANDR) << "Forced to change screen size: " << newSize; } setScreenSize(newSize); } } void XRandRConfig::printConfig(const ConfigPtr &config) const { qCDebug(KSCREEN_XRANDR) << "KScreen version:" /*<< LIBKSCREEN_VERSION*/; if (!config) { qCDebug(KSCREEN_XRANDR) << "Config is invalid"; return; } if (!config->screen()) { qCDebug(KSCREEN_XRANDR) << "No screen in the configuration, broken backend"; return; } qCDebug(KSCREEN_XRANDR) << "Screen:"; qCDebug(KSCREEN_XRANDR) << "\tmaxSize:" << config->screen()->maxSize(); qCDebug(KSCREEN_XRANDR) << "\tminSize:" << config->screen()->minSize(); qCDebug(KSCREEN_XRANDR) << "\tcurrentSize:" << config->screen()->currentSize(); OutputList outputs = config->outputs(); Q_FOREACH(const OutputPtr &output, outputs) { qCDebug(KSCREEN_XRANDR) << "\n-----------------------------------------------------\n"; qCDebug(KSCREEN_XRANDR) << "Id: " << output->id(); qCDebug(KSCREEN_XRANDR) << "Name: " << output->name(); qCDebug(KSCREEN_XRANDR) << "Type: " << output->type(); qCDebug(KSCREEN_XRANDR) << "Connected: " << output->isConnected(); if (!output->isConnected()) { continue; } qCDebug(KSCREEN_XRANDR) << "Enabled: " << output->isEnabled(); qCDebug(KSCREEN_XRANDR) << "Primary: " << output->isPrimary(); qCDebug(KSCREEN_XRANDR) << "Rotation: " << output->rotation(); qCDebug(KSCREEN_XRANDR) << "Pos: " << output->pos(); qCDebug(KSCREEN_XRANDR) << "MMSize: " << output->sizeMm(); if (output->currentMode()) { qCDebug(KSCREEN_XRANDR) << "Size: " << output->currentMode()->size(); } if (output->clones().isEmpty()) { qCDebug(KSCREEN_XRANDR) << "Clones: " << "None"; } else { qCDebug(KSCREEN_XRANDR) << "Clones: " << output->clones().count(); } qCDebug(KSCREEN_XRANDR) << "Mode: " << output->currentModeId(); qCDebug(KSCREEN_XRANDR) << "Preferred Mode: " << output->preferredModeId(); qCDebug(KSCREEN_XRANDR) << "Preferred modes: " << output->preferredModes(); qCDebug(KSCREEN_XRANDR) << "Modes: "; ModeList modes = output->modes(); Q_FOREACH(const ModePtr &mode, modes) { qCDebug(KSCREEN_XRANDR) << "\t" << mode->id() << " " << mode->name() << " " << mode->size() << " " << mode->refreshRate(); } Edid* edid = output->edid(); qCDebug(KSCREEN_XRANDR) << "EDID Info: "; if (edid && edid->isValid()) { qCDebug(KSCREEN_XRANDR) << "\tDevice ID: " << edid->deviceId(); qCDebug(KSCREEN_XRANDR) << "\tName: " << edid->name(); qCDebug(KSCREEN_XRANDR) << "\tVendor: " << edid->vendor(); qCDebug(KSCREEN_XRANDR) << "\tSerial: " << edid->serial(); qCDebug(KSCREEN_XRANDR) << "\tEISA ID: " << edid->eisaId(); qCDebug(KSCREEN_XRANDR) << "\tHash: " << edid->hash(); qCDebug(KSCREEN_XRANDR) << "\tWidth: " << edid->width(); qCDebug(KSCREEN_XRANDR) << "\tHeight: " << edid->height(); qCDebug(KSCREEN_XRANDR) << "\tGamma: " << edid->gamma(); qCDebug(KSCREEN_XRANDR) << "\tRed: " << edid->red(); qCDebug(KSCREEN_XRANDR) << "\tGreen: " << edid->green(); qCDebug(KSCREEN_XRANDR) << "\tBlue: " << edid->blue(); qCDebug(KSCREEN_XRANDR) << "\tWhite: " << edid->white(); } else { qCDebug(KSCREEN_XRANDR) << "\tUnavailable"; } } } void XRandRConfig::printInternalCond() const { qCDebug(KSCREEN_XRANDR) << "Internal config in xrandr"; Q_FOREACH(XRandROutput *output, m_outputs) { qCDebug(KSCREEN_XRANDR) << "Id: " << output->id(); qCDebug(KSCREEN_XRANDR) << "Current Mode: " << output->currentMode(); qCDebug(KSCREEN_XRANDR) << "Current mode id: " << output->currentModeId(); qCDebug(KSCREEN_XRANDR) << "Connected: " << output->isConnected(); qCDebug(KSCREEN_XRANDR) << "Enabled: " << output->isEnabled(); qCDebug(KSCREEN_XRANDR) << "Primary: " << output->isPrimary(); if (!output->isEnabled()) { continue; } XRandRMode::Map modes = output->modes(); Q_FOREACH(XRandRMode *mode, modes) { qCDebug(KSCREEN_XRANDR) << "\t" << mode->id(); qCDebug(KSCREEN_XRANDR) << "\t" << mode->name(); qCDebug(KSCREEN_XRANDR) << "\t" << mode->size() << mode->refreshRate(); } } } QSize XRandRConfig::screenSize(const KScreen::ConfigPtr &config) const { QRect rect; Q_FOREACH(const KScreen::OutputPtr &output, config->outputs()) { if (!output->isConnected() || !output->isEnabled()) { continue; } const ModePtr currentMode = output->currentMode(); if (!currentMode) { qCDebug(KSCREEN_XRANDR) << "Output: " << output->name() << " has no current Mode!"; continue; } const QRect outputGeom = output->geometry(); rect = rect.united(outputGeom); } const QSize size = QSize(rect.width(), rect.height()); qCDebug(KSCREEN_XRANDR) << "Requested screen size is" << size; return size; } bool XRandRConfig::setScreenSize(const QSize &size) const { const double dpi = (25.4 * XRandR::screen()->height_in_pixels / XRandR::screen()->height_in_millimeters); const int widthMM = ((25.4 * size.width()) / dpi); const int heightMM = ((25.4 * size.height()) / dpi); qCDebug(KSCREEN_XRANDR) << "RRSetScreenSize"; qCDebug(KSCREEN_XRANDR) << "\tDPI:" << dpi; qCDebug(KSCREEN_XRANDR) << "\tSize:" << size; qCDebug(KSCREEN_XRANDR) << "\tSizeMM:" << QSize(widthMM, heightMM); xcb_randr_set_screen_size(XCB::connection(), XRandR::rootWindow(), size.width(), size.height(), widthMM, heightMM); m_screen->update(size); return true; } void XRandRConfig::setPrimaryOutput(xcb_randr_output_t outputId) const { qCDebug(KSCREEN_XRANDR) << "RRSetOutputPrimary"; qCDebug(KSCREEN_XRANDR) << "\tNew primary:" << outputId; xcb_randr_set_output_primary(XCB::connection(), XRandR::rootWindow(), outputId); for (XRandROutput *output : m_outputs) { output->setIsPrimary(output->id() == outputId); } } bool XRandRConfig::disableOutput(const OutputPtr &kscreenOutput) const { XRandROutput *xOutput = output(kscreenOutput->id()); Q_ASSERT(xOutput); Q_ASSERT(xOutput->crtc()); if (!xOutput->crtc()) { qCWarning(KSCREEN_XRANDR) << "Attempting to disable output without CRTC, wth?"; return false; } const xcb_randr_crtc_t crtc = xOutput->crtc()->crtc(); qCDebug(KSCREEN_XRANDR) << "RRSetCrtcConfig (disable output)"; qCDebug(KSCREEN_XRANDR) << "\tCRTC:" << crtc; auto cookie = xcb_randr_set_crtc_config(XCB::connection(), crtc, XCB_CURRENT_TIME, XCB_CURRENT_TIME, 0, 0, XCB_NONE, XCB_RANDR_ROTATION_ROTATE_0, 0, NULL); XCB::ScopedPointer reply(xcb_randr_set_crtc_config_reply(XCB::connection(), cookie, NULL)); if (!reply) { qCDebug(KSCREEN_XRANDR) << "\tResult: unknown (error)"; return false; } qCDebug(KSCREEN_XRANDR) << "\tResult:" << reply->status; // Update the cached output now, otherwise we get RRNotify_CrtcChange notification // for an outdated output, which can lead to a crash. if (reply->status == XCB_RANDR_SET_CONFIG_SUCCESS) { xOutput->update(XCB_NONE, XCB_NONE, xOutput->isConnected() ? XCB_RANDR_CONNECTION_CONNECTED : XCB_RANDR_CONNECTION_DISCONNECTED, kscreenOutput->isPrimary()); } return (reply->status == XCB_RANDR_SET_CONFIG_SUCCESS); } bool XRandRConfig::enableOutput(const OutputPtr &kscreenOutput) const { xcb_randr_output_t outputs[1] { static_cast(kscreenOutput->id()) }; XRandRCrtc *freeCrtc = Q_NULLPTR; qCDebug(KSCREEN_XRANDR) << m_crtcs; Q_FOREACH (XRandRCrtc *crtc, m_crtcs) { crtc->update(); qCDebug(KSCREEN_XRANDR) << "Testing CRTC" << crtc->crtc(); qCDebug(KSCREEN_XRANDR) << "\tFree:" << crtc->isFree(); qCDebug(KSCREEN_XRANDR) << "\tMode:" << crtc->mode(); qCDebug(KSCREEN_XRANDR) << "\tPossible outputs:" << crtc->possibleOutputs(); qCDebug(KSCREEN_XRANDR) << "\tConnected outputs:" << crtc->outputs(); qCDebug(KSCREEN_XRANDR) << "\tGeometry:" << crtc->geometry(); if (crtc->isFree() && crtc->possibleOutputs().contains(kscreenOutput->id())) { freeCrtc = crtc; break; } } if (!freeCrtc) { qCWarning(KSCREEN_XRANDR) << "Failed to get free CRTC for output" << kscreenOutput->id(); return false; } const int modeId = kscreenOutput->currentMode() ? kscreenOutput->currentModeId().toInt() : kscreenOutput->preferredModeId().toInt(); qCDebug(KSCREEN_XRANDR) << "RRSetCrtcConfig (enable output)"; qCDebug(KSCREEN_XRANDR) << "\tOutput:" << kscreenOutput->id() << "(" << kscreenOutput->name() << ")"; qCDebug(KSCREEN_XRANDR) << "\tNew CRTC:" << freeCrtc->crtc(); qCDebug(KSCREEN_XRANDR) << "\tPos:" << kscreenOutput->pos(); qCDebug(KSCREEN_XRANDR) << "\tMode:" << kscreenOutput->currentMode() << "Preferred:" << kscreenOutput->preferredModeId(); qCDebug(KSCREEN_XRANDR) << "\tRotation:" << kscreenOutput->rotation(); auto cookie = xcb_randr_set_crtc_config(XCB::connection(), freeCrtc->crtc(), XCB_CURRENT_TIME, XCB_CURRENT_TIME, kscreenOutput->pos().rx(), kscreenOutput->pos().ry(), modeId, kscreenOutput->rotation(), 1, outputs); XCB::ScopedPointer reply(xcb_randr_set_crtc_config_reply(XCB::connection(), cookie, NULL)); if (!reply) { qCDebug(KSCREEN_XRANDR) << "Result: unknown (error)"; return false; } qCDebug(KSCREEN_XRANDR) << "\tResult:" << reply->status; if (reply->status == XCB_RANDR_SET_CONFIG_SUCCESS) { XRandROutput *xOutput = output(kscreenOutput->id()); xOutput->update(freeCrtc->crtc(), modeId, XCB_RANDR_CONNECTION_CONNECTED, kscreenOutput->isPrimary()); } return (reply->status == XCB_RANDR_SET_CONFIG_SUCCESS); } bool XRandRConfig::changeOutput(const OutputPtr &kscreenOutput) const { XRandROutput *xOutput = output(kscreenOutput->id()); Q_ASSERT(xOutput); if (!xOutput->crtc()) { qCDebug(KSCREEN_XRANDR) << "Output" << kscreenOutput->id() << "has no CRTC, falling back to enableOutput()"; return enableOutput(kscreenOutput); } int modeId = kscreenOutput->currentMode() ? kscreenOutput->currentModeId().toInt() : kscreenOutput->preferredModeId().toInt(); qCDebug(KSCREEN_XRANDR) << "RRSetCrtcConfig (change output)"; qCDebug(KSCREEN_XRANDR) << "\tOutput:" << kscreenOutput->id() << "(" << kscreenOutput->name() << ")"; qCDebug(KSCREEN_XRANDR) << "\tCRTC:" << xOutput->crtc()->crtc(); qCDebug(KSCREEN_XRANDR) << "\tPos:" << kscreenOutput->pos(); qCDebug(KSCREEN_XRANDR) << "\tMode:" << modeId << kscreenOutput->currentMode(); qCDebug(KSCREEN_XRANDR) << "\tRotation:" << kscreenOutput->rotation(); xcb_randr_output_t outputs[1] { static_cast(kscreenOutput->id()) }; auto cookie = xcb_randr_set_crtc_config(XCB::connection(), xOutput->crtc()->crtc(), XCB_CURRENT_TIME, XCB_CURRENT_TIME, kscreenOutput->pos().rx(), kscreenOutput->pos().ry(), modeId, kscreenOutput->rotation(), 1, outputs); XCB::ScopedPointer reply(xcb_randr_set_crtc_config_reply(XCB::connection(), cookie, NULL)); if (!reply) { qCDebug(KSCREEN_XRANDR) << "\tResult: unknown (error)"; return false; } qCDebug(KSCREEN_XRANDR) << "\tResult: " << reply->status; if (reply->status == XCB_RANDR_SET_CONFIG_SUCCESS) { xOutput->update(xOutput->crtc()->crtc(), modeId, XCB_RANDR_CONNECTION_CONNECTED, kscreenOutput->isPrimary()); } return (reply->status == XCB_RANDR_SET_CONFIG_SUCCESS); } diff --git a/src/config.cpp b/src/config.cpp index 6e8ba6c..c11fedc 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -1,366 +1,366 @@ /************************************************************************************* * Copyright (C) 2012 by Alejandro Fiestas Olivares * * Copyright (C) 2014 by Daniel Vrátil * * * * 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 St, Fifth Floor, Boston, MA 02110-1301 USA * *************************************************************************************/ #include "config.h" #include "output.h" #include "backendmanager_p.h" #include "abstractbackend.h" #include "debug_p.h" #include #include using namespace KScreen; class Config::Private : public QObject { Q_OBJECT public: Private(Config *parent) : QObject(parent) , valid(true) , supportedFeatures(Config::Feature::None) , q(parent) { } KScreen::OutputPtr findPrimaryOutput() const { auto iter = std::find_if(outputs.constBegin(), outputs.constEnd(), [](const KScreen::OutputPtr &output) -> bool { return output->isPrimary(); }); return iter == outputs.constEnd() ? KScreen::OutputPtr() : iter.value(); } void onPrimaryOutputChanged() { const KScreen::OutputPtr output(qobject_cast(sender()), [](void *) {}); Q_ASSERT(output); if (output->isPrimary()) { q->setPrimaryOutput(output); } else { q->setPrimaryOutput(findPrimaryOutput()); } } OutputList::Iterator removeOutput(OutputList::Iterator iter) { if (iter == outputs.end()) { return iter; } OutputPtr output = iter.value(); if (!output) { return outputs.erase(iter); } const int outputId = iter.key(); iter = outputs.erase(iter); if (primaryOutput == output) { q->setPrimaryOutput(OutputPtr()); } output->disconnect(q); Q_EMIT q->outputRemoved(outputId); return iter; } bool valid; ScreenPtr screen; OutputPtr primaryOutput; OutputList outputs; Features supportedFeatures; private: Config *q; }; bool Config::canBeApplied(const ConfigPtr &config) { return canBeApplied(config, ValidityFlag::None); } bool Config::canBeApplied(const ConfigPtr &config, ValidityFlags flags) { if (!config) { qCDebug(KSCREEN) << "canBeApplied: Config not available, returning false"; return false; } ConfigPtr currentConfig = BackendManager::instance()->config(); if (!currentConfig) { qCDebug(KSCREEN) << "canBeApplied: Current config not available, returning false"; return false; } QRect rect; QSize outputSize; OutputPtr currentOutput; const OutputList outputs = config->outputs(); int enabledOutputsCount = 0; Q_FOREACH(const OutputPtr &output, outputs) { if (!output->isEnabled()) { continue; } ++enabledOutputsCount; currentOutput = currentConfig->output(output->id()); //If there is no such output if (!currentOutput) { qCDebug(KSCREEN) << "canBeApplied: The output:" << output->id() << "does not exists"; return false; } //If the output is not connected if (!currentOutput->isConnected()) { qCDebug(KSCREEN) << "canBeApplied: The output:" << output->id() << "is not connected"; return false; } //if there is no currentMode if (output->currentModeId().isEmpty()) { qCDebug(KSCREEN) << "canBeApplied: The output:" << output->id() << "has no currentModeId"; return false; } //If the mode is not found in the current output if (!currentOutput->mode(output->currentModeId())) { qCDebug(KSCREEN) << "canBeApplied: The output:" << output->id() << "has no mode:" << output->currentModeId(); return false; } const ModePtr currentMode = output->currentMode(); const QSize outputSize = currentMode->size(); if (output->pos().x() < rect.x()) { rect.setX(output->pos().x()); } if (output->pos().y() < rect.y()) { rect.setY(output->pos().y()); } QPoint bottomRight; if (output->isHorizontal()) { bottomRight = QPoint(output->pos().x() + outputSize.width(), output->pos().y() + outputSize.height()); } else { bottomRight = QPoint(output->pos().x() + outputSize.height(), output->pos().y() + outputSize.width()); } if (bottomRight.x() > rect.width()) { rect.setWidth(bottomRight.x()); } if (bottomRight.y() > rect.height()) { rect.setHeight(bottomRight.y()); } } if (flags & ValidityFlag::RequireAtLeastOneEnabledScreen && enabledOutputsCount == 0) { qCDebug(KSCREEN) << "canBeAppled: There are no enabled screens, at least one required"; return false; } const int maxEnabledOutputsCount = config->screen()->maxActiveOutputsCount(); if (enabledOutputsCount > maxEnabledOutputsCount) { qCDebug(KSCREEN) << "canBeApplied: Too many active screens. Requested: " << enabledOutputsCount << ", Max: " << maxEnabledOutputsCount; return false; } if (rect.width() > config->screen()->maxSize().width()) { qCDebug(KSCREEN) << "canBeApplied: The configuration is too wide:" << rect.width(); return false; } if (rect.height() > config->screen()->maxSize().height()) { qCDebug(KSCREEN) << "canBeApplied: The configuration is too high:" << rect.height(); return false; } return true; } Config::Config() : QObject(0) , d(new Private(this)) { } Config::~Config() { delete d; } ConfigPtr Config::clone() const { ConfigPtr newConfig(new Config()); newConfig->d->screen = d->screen->clone(); for (const OutputPtr &ourOutput : d->outputs) { newConfig->addOutput(ourOutput->clone()); } newConfig->d->primaryOutput = newConfig->d->findPrimaryOutput(); - + newConfig->setSupportedFeatures(supportedFeatures()); return newConfig; } ScreenPtr Config::screen() const { return d->screen; } void Config::setScreen(const ScreenPtr &screen) { d->screen = screen; } OutputPtr Config::output(int outputId) const { return d->outputs.value(outputId); } Config::Features Config::supportedFeatures() const { return d->supportedFeatures; } void Config::setSupportedFeatures(const Config::Features &features) { d->supportedFeatures = features; } OutputList Config::outputs() const { return d->outputs; } OutputList Config::connectedOutputs() const { OutputList outputs; Q_FOREACH(const OutputPtr &output, d->outputs) { if (!output->isConnected()) { continue; } outputs.insert(output->id(), output); } return outputs; } OutputPtr Config::primaryOutput() const { if (d->primaryOutput) { return d->primaryOutput; } d->primaryOutput = d->findPrimaryOutput(); return d->primaryOutput; } void Config::setPrimaryOutput(const OutputPtr &newPrimary) { // Don't call primaryOutput(): at this point d->primaryOutput is either // initialized, or we need to look for the primary anyway if (d->primaryOutput == newPrimary) { return; } // qCDebug(KSCREEN) << "Primary output changed from" << primaryOutput() // << "(" << (primaryOutput().isNull() ? "none" : primaryOutput()->name()) << ") to" // << newPrimary << "(" << (newPrimary.isNull() ? "none" : newPrimary->name()) << ")"; for (OutputPtr &output : d->outputs) { disconnect(output.data(), &KScreen::Output::isPrimaryChanged, d, &KScreen::Config::Private::onPrimaryOutputChanged); output->setPrimary(output == newPrimary); connect(output.data(), &KScreen::Output::isPrimaryChanged, d, &KScreen::Config::Private::onPrimaryOutputChanged); } d->primaryOutput = newPrimary; Q_EMIT primaryOutputChanged(newPrimary); } void Config::addOutput(const OutputPtr &output) { d->outputs.insert(output->id(), output); connect(output.data(), &KScreen::Output::isPrimaryChanged, d, &KScreen::Config::Private::onPrimaryOutputChanged); Q_EMIT outputAdded(output); if (output->isPrimary()) { setPrimaryOutput(output); } } void Config::removeOutput(int outputId) { d->removeOutput(d->outputs.find(outputId)); } void Config::setOutputs(OutputList outputs) { for (auto iter = d->outputs.begin(), end = d->outputs.end(); iter != end; ) { iter = d->removeOutput(iter); end = d->outputs.end(); } for (const OutputPtr &output : outputs) { addOutput(output); } } bool Config::isValid() const { return d->valid; } void Config::setValid(bool valid) { d->valid = valid; } void Config::apply(const ConfigPtr& other) { d->screen->apply(other->screen()); // Remove removed outputs Q_FOREACH (const OutputPtr &output, d->outputs) { if (!other->d->outputs.contains(output->id())) { removeOutput(output->id()); } } Q_FOREACH (const OutputPtr &otherOutput, other->d->outputs) { // Add new outputs if (!d->outputs.contains(otherOutput->id())) { addOutput(otherOutput->clone()); } else { // Update existing outputs d->outputs[otherOutput->id()]->apply(otherOutput); } } // Update validity setValid(other->isValid()); } #include "config.moc" diff --git a/src/doctor/doctor.cpp b/src/doctor/doctor.cpp index 21479ec..11013bd 100644 --- a/src/doctor/doctor.cpp +++ b/src/doctor/doctor.cpp @@ -1,427 +1,447 @@ /************************************************************************************* * Copyright 2014-2016 Sebastian Kügler * * * * 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 St, Fifth Floor, Boston, MA 02110-1301 USA * *************************************************************************************/ #include "doctor.h" #include "dpmsclient.h" #include #include #include #include #include #include #include #include #include #include #include #include "../backendmanager_p.h" #include "../config.h" #include "../configoperation.h" #include "../getconfigoperation.h" #include "../setconfigoperation.h" #include "../edid.h" #include "../log.h" #include "../output.h" Q_LOGGING_CATEGORY(KSCREEN_DOCTOR, "kscreen.doctor") static QTextStream cout(stdout); static QTextStream cerr(stderr); const static QString green = QStringLiteral("\033[01;32m"); const static QString red = QStringLiteral("\033[01;31m"); const static QString yellow = QStringLiteral("\033[01;33m"); const static QString blue = QStringLiteral("\033[01;34m"); const static QString bold = QStringLiteral("\033[01;39m"); const static QString cr = QStringLiteral("\033[0;0m"); namespace KScreen { namespace ConfigSerializer { // Exported private symbol in configserializer_p.h in KScreen extern QJsonObject serializeConfig(const KScreen::ConfigPtr &config); } } using namespace KScreen; Doctor::Doctor(QObject *parent) : QObject(parent) , m_config(nullptr) , m_changed(false) , m_dpmsClient(nullptr) { } Doctor::~Doctor() { } void Doctor::start(QCommandLineParser *parser) { m_parser = parser; if (m_parser->isSet("info")) { showBackends(); } if (parser->isSet("json") || parser->isSet("outputs") || !m_positionalArgs.isEmpty()) { KScreen::GetConfigOperation *op = new KScreen::GetConfigOperation(); connect(op, &KScreen::GetConfigOperation::finished, this, [this](KScreen::ConfigOperation *op) { configReceived(op); }); return; } if (m_parser->isSet("dpms")) { if (!QGuiApplication::platformName().startsWith(QLatin1String("wayland"))) { cerr << "DPMS is only supported on Wayland." << endl; // We need to kick the event loop, otherwise .quit() hangs QTimer::singleShot(0, qApp->quit); return; } m_dpmsClient = new DpmsClient(this); connect(m_dpmsClient, &DpmsClient::finished, qApp, &QCoreApplication::quit); const QString dpmsArg = m_parser->value(QStringLiteral("dpms")); if (dpmsArg == QStringLiteral("show")) { showDpms(); } else { setDpms(dpmsArg); } return; } if (m_parser->isSet("log")) { const QString logmsg = m_parser->value(QStringLiteral("log")); if (!Log::instance()->enabled()) { qCWarning(KSCREEN_DOCTOR) << "Logging is disabled, unset KSCREEN_LOGGING in your environment."; } else { Log::log(logmsg); } } // We need to kick the event loop, otherwise .quit() hangs QTimer::singleShot(0, qApp->quit); } void KScreen::Doctor::setDpms(const QString& dpmsArg) { qDebug() << "SetDpms: " << dpmsArg; connect(m_dpmsClient, &DpmsClient::ready, this, [this, dpmsArg]() { cout << "DPMS.ready()"; if (dpmsArg == QStringLiteral("off")) { m_dpmsClient->off(); } else if (dpmsArg == QStringLiteral("on")) { m_dpmsClient->on(); } else { cout << "--dpms argument not understood (" << dpmsArg << ")"; } }); m_dpmsClient->connect(); } void Doctor::showDpms() { m_dpmsClient = new DpmsClient(this); connect(m_dpmsClient, &DpmsClient::ready, this, []() { cout << "DPMS.ready()"; }); m_dpmsClient->connect(); } void Doctor::showBackends() const { cout << "Environment: " << endl; auto env_kscreen_backend = (qgetenv("KSCREEN_BACKEND").isEmpty()) ? QStringLiteral("[not set]") : qgetenv("KSCREEN_BACKEND"); cout << " * KSCREEN_BACKEND : " << env_kscreen_backend << endl; auto env_kscreen_backend_inprocess = (qgetenv("KSCREEN_BACKEND_INPROCESS").isEmpty()) ? QStringLiteral("[not set]") : qgetenv("KSCREEN_BACKEND_INPROCESS"); cout << " * KSCREEN_BACKEND_INPROCESS : " << env_kscreen_backend_inprocess << endl; auto env_kscreen_logging = (qgetenv("KSCREEN_LOGGING").isEmpty()) ? QStringLiteral("[not set]") : qgetenv("KSCREEN_LOGGING"); cout << " * KSCREEN_LOGGING : " << env_kscreen_logging << endl; cout << "Logging to : " << (Log::instance()->enabled() ? Log::instance()->logFile() : "[logging disabled]") << endl; auto backends = BackendManager::instance()->listBackends(); auto preferred = BackendManager::instance()->preferredBackend(); cout << "Preferred KScreen backend : " << green << preferred.fileName() << cr << endl; cout << "Available KScreen backends:" << endl; Q_FOREACH(const QFileInfo f, backends) { auto c = blue; if (preferred == f) { c = green; } cout << " * " << c << f.fileName() << cr << ": " << f.absoluteFilePath() << endl; } cout << endl; } void Doctor::setOptionList(const QStringList &positionalArgs) { m_positionalArgs = positionalArgs; } void Doctor::parsePositionalArgs() { //qCDebug(KSCREEN_DOCTOR) << "POSARGS" << m_positionalArgs; Q_FOREACH(const QString &op, m_positionalArgs) { auto ops = op.split('.'); if (ops.count() > 2) { bool ok; int output_id = -1; if (ops[0] == QStringLiteral("output")) { Q_FOREACH (const auto &output, m_config->outputs()) { if (output->name() == ops[1]) { output_id = output->id(); } } if (output_id == -1) { - output_id = parseInt(ops[1], ok); + output_id = ops[1].toInt(&ok); if (!ok) { cerr << "Unable to parse output id" << ops[1] << endl; qApp->exit(3); return; } } if (ops.count() == 3 && ops[2] == QStringLiteral("enable")) { if (!setEnabled(output_id, true)) { qApp->exit(1); return; }; } else if (ops.count() == 3 && ops[2] == QStringLiteral("disable")) { if (!setEnabled(output_id, false)) { qApp->exit(1); return; }; } else if (ops.count() == 4 && ops[2] == QStringLiteral("mode")) { QString mode_id = ops[3]; // set mode if (!setMode(output_id, mode_id)) { qApp->exit(9); return; } qCDebug(KSCREEN_DOCTOR) << "Output" << output_id << "set mode" << mode_id; } else if (ops.count() == 4 && ops[2] == QStringLiteral("position")) { QStringList _pos = ops[3].split(','); if (_pos.count() != 2) { qCWarning(KSCREEN_DOCTOR) << "Invalid position:" << ops[3]; qApp->exit(5); return; } - int x = parseInt(_pos[0], ok); - int y = parseInt(_pos[1], ok); + int x = _pos[0].toInt(&ok); + int y = _pos[1].toInt(&ok); if (!ok) { cerr << "Unable to parse position" << ops[3] << endl; qApp->exit(5); return; } QPoint p(x, y); qCDebug(KSCREEN_DOCTOR) << "Output position" << p; if (!setPosition(output_id, p)) { qApp->exit(1); return; } + } else if ((ops.count() == 4 || ops.count() == 5) && ops[2] == QStringLiteral("scale")) { + // be lenient about . vs. comma as separator + qreal scale = ops[3].replace(QStringLiteral(","), QStringLiteral(".")).toDouble(&ok); + if (ops.count() == 5) { + const auto dbl = ops[3] + QStringLiteral(".") + ops[4]; + scale = dbl.toDouble(&ok); + }; + // set scale + if (!ok || scale == 0 || !setScale(output_id, scale)) { + qCDebug(KSCREEN_DOCTOR) << "Could not set scale " << scale << " to output " << output_id; + qApp->exit(9); + return; + } } else { cerr << "Unable to parse arguments" << op << endl; qApp->exit(2); return; } } } } } -int Doctor::parseInt(const QString &str, bool &ok) const -{ - int _id = str.toInt(); - if (QString::number(_id) == str) { - ok = true; - return _id; - } - ok = false; - return 0; -} - void Doctor::configReceived(KScreen::ConfigOperation *op) { m_config = op->config(); if (m_parser->isSet("json")) { showJson(); qApp->quit(); } if (m_parser->isSet("outputs")) { showOutputs(); qApp->quit(); } parsePositionalArgs(); if (m_changed) { applyConfig(); m_changed = false; } } int Doctor::outputCount() const { if (!m_config) { qCWarning(KSCREEN_DOCTOR) << "Invalid config."; return 0; } return m_config->outputs().count(); } void Doctor::showOutputs() const { if (!m_config) { qCWarning(KSCREEN_DOCTOR) << "Invalid config."; return; } QHash typeString; typeString[KScreen::Output::Unknown] = QStringLiteral("Unknown"); typeString[KScreen::Output::VGA] = QStringLiteral("VGA"); typeString[KScreen::Output::DVI] = QStringLiteral("DVI"); typeString[KScreen::Output::DVII] = QStringLiteral("DVII"); typeString[KScreen::Output::DVIA] = QStringLiteral("DVIA"); typeString[KScreen::Output::DVID] = QStringLiteral("DVID"); typeString[KScreen::Output::HDMI] = QStringLiteral("HDMI"); typeString[KScreen::Output::Panel] = QStringLiteral("Panel"); typeString[KScreen::Output::TV] = QStringLiteral("TV"); typeString[KScreen::Output::TVComposite] = QStringLiteral("TVComposite"); typeString[KScreen::Output::TVSVideo] = QStringLiteral("TVSVideo"); typeString[KScreen::Output::TVComponent] = QStringLiteral("TVComponent"); typeString[KScreen::Output::TVSCART] = QStringLiteral("TVSCART"); typeString[KScreen::Output::TVC4] = QStringLiteral("TVC4"); typeString[KScreen::Output::DisplayPort] = QStringLiteral("DisplayPort"); Q_FOREACH (const auto &output, m_config->outputs()) { cout << green << "Output: " << cr << output->id() << " " << output->name(); cout << " " << (output->isEnabled() ? green + "enabled" : red + "disabled"); cout << " " << (output->isConnected() ? green + "connected" : red + "disconnected"); cout << " " << (output->isPrimary() ? green + "primary" : QString()); auto _type = typeString[output->type()]; cout << " " << yellow << (_type.isEmpty() ? "UnmappedOutputType" : _type); cout << blue << " Modes: " << cr; Q_FOREACH (auto mode, output->modes()) { auto name = QString("%1x%2@%3").arg(QString::number(mode->size().width()), QString::number(mode->size().height()), QString::number(qRound(mode->refreshRate()))); if (mode == output->currentMode()) { name = green + name + "*" + cr; } if (mode == output->preferredMode()) { name = name + "!"; } cout << mode->id() << ":" << name << " "; } const auto g = output->geometry(); cout << yellow << "Geometry: " << cr << g.x() << "," << g.y() << " " << g.width() << "x" << g.height(); cout << endl; } } void Doctor::showJson() const { QJsonDocument doc(KScreen::ConfigSerializer::serializeConfig(m_config)); cout << doc.toJson(QJsonDocument::Indented); } bool Doctor::setEnabled(int id, bool enabled = true) { if (!m_config) { qCWarning(KSCREEN_DOCTOR) << "Invalid config."; return false; } Q_FOREACH (const auto &output, m_config->outputs()) { if (output->id() == id) { cout << (enabled ? "Enabling " : "Disabling ") << "output " << id << endl; output->setEnabled(enabled); m_changed = true; return true; } } cerr << "Output with id " << id << " not found." << endl; qApp->exit(8); return false; } bool Doctor::setPosition(int id, const QPoint &pos) { if (!m_config) { qCWarning(KSCREEN_DOCTOR) << "Invalid config."; return false; } Q_FOREACH (const auto &output, m_config->outputs()) { if (output->id() == id) { qCDebug(KSCREEN_DOCTOR) << "Set output position" << pos; output->setPos(pos); m_changed = true; return true; } } cout << "Output with id " << id << " not found." << endl; return false; } bool Doctor::setMode(int id, const QString &mode_id) { if (!m_config) { qCWarning(KSCREEN_DOCTOR) << "Invalid config."; return false; } Q_FOREACH (const auto &output, m_config->outputs()) { if (output->id() == id) { // find mode Q_FOREACH (const KScreen::ModePtr mode, output->modes()) { if (mode->id() == mode_id) { qCDebug(KSCREEN_DOCTOR) << "Taddaaa! Found mode" << mode->id() << mode->name(); output->setCurrentModeId(mode->id()); m_changed = true; return true; } } } } cout << "Output mode " << id << " not found." << endl; return false; } +bool Doctor::setScale(int id, qreal scale) +{ + if (!m_config) { + qCWarning(KSCREEN_DOCTOR) << "Invalid config."; + return false; + } + + Q_FOREACH (const auto &output, m_config->outputs()) { + if (output->id() == id) { + output->setScale(scale); + m_changed = true; + return true; + } + } + cout << "Output scale " << id << " invalid." << endl; + return false; +} + void Doctor::applyConfig() { if (!m_changed) { return; } auto setop = new SetConfigOperation(m_config, this); setop->exec(); qCDebug(KSCREEN_DOCTOR) << "setop exec returned"; qApp->exit(0); } diff --git a/src/doctor/doctor.h b/src/doctor/doctor.h index 2f0e952..c11f415 100644 --- a/src/doctor/doctor.h +++ b/src/doctor/doctor.h @@ -1,75 +1,76 @@ /************************************************************************************* * Copyright 2015 Sebastian Kügler * * * * 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 St, Fifth Floor, Boston, MA 02110-1301 USA * *************************************************************************************/ #ifndef KSCREEN_DOCTOR_H #define KSCREEN_DOCTOR_H #include #include #include "../config.h" namespace KScreen { class ConfigOperation; class DpmsClient; //static const QString s_socketName = QStringLiteral("libkscreen-test-wayland-backend-0"); class Doctor : public QObject { Q_OBJECT public: explicit Doctor(QObject *parent = 0); virtual ~Doctor(); void setOptionList(const QStringList &positionalArgs); void start(QCommandLineParser *m_parser); void configReceived(KScreen::ConfigOperation *op); void showDpms(); void showBackends() const; void showOutputs() const; void showJson() const; int outputCount() const; void setDpms(const QString &dpmsArg); bool setEnabled(int id, bool enabled); bool setPosition(int id, const QPoint &pos); bool setMode(int id, const QString &mode_id); + bool setScale(int id, qreal scale); Q_SIGNALS: void outputsChanged(); void started(); void configChanged(); private: //static QString modeString(KWayland::Server::OutputDeviceInterface* outputdevice, int mid); void applyConfig(); void parsePositionalArgs(); int parseInt(const QString &str, bool &ok) const; KScreen::ConfigPtr m_config; QCommandLineParser* m_parser; bool m_changed; QStringList m_positionalArgs; DpmsClient *m_dpmsClient; }; } // namespace #endif // KSCREEN_WAYLAND_SCREEN_H diff --git a/src/output.cpp b/src/output.cpp index c65dcd7..b9cfa57 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -1,590 +1,594 @@ /************************************************************************************* * Copyright (C) 2012 by Alejandro Fiestas Olivares * * Copyright (C) 2014 by Daniel Vrátil * * * * 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 St, Fifth Floor, Boston, MA 02110-1301 USA * *************************************************************************************/ #include "output.h" #include "mode.h" #include "edid.h" #include "abstractbackend.h" #include "backendmanager_p.h" #include "debug_p.h" #include #include #include using namespace KScreen; class Output::Private { public: Private(): id(0), type(Unknown), rotation(None), scale(1.0), connected(false), enabled(false), primary(false), edid(0) {} Private(const Private &other): id(other.id), name(other.name), type(other.type), icon(other.icon), clones(other.clones), currentMode(other.currentMode), preferredMode(other.preferredMode), preferredModes(other.preferredModes), sizeMm(other.sizeMm), pos(other.pos), size(other.size), rotation(other.rotation), scale(other.scale), connected(other.connected), enabled(other.enabled), primary(other.primary) { Q_FOREACH (const ModePtr &otherMode, other.modeList) { modeList.insert(otherMode->id(), otherMode->clone()); } if (other.edid) { edid = other.edid->clone(); } } QString biggestMode(const ModeList& modes) const; bool compareModeList(const ModeList& before, const ModeList& after); int id; QString name; Type type; QString icon; ModeList modeList; QList clones; QString currentMode; QString preferredMode; QStringList preferredModes; QSize sizeMm; QPoint pos; QSize size; Rotation rotation; qreal scale; bool connected; bool enabled; bool primary; mutable QPointer edid; }; bool Output::Private::compareModeList(const ModeList& before, const ModeList &after) { if (before.keys() != after.keys()) { return false; } for (const QString &key : before.keys()) { const auto mb = before.value(key); const auto ma = after.value(key); if (mb->id() != ma->id()) { return false; } if (mb->size() != ma->size()) { return false; } if (mb->refreshRate() != ma->refreshRate()) { return false; } if (mb->name() != ma->name()) { return false; } } // They're the same return true; } QString Output::Private::biggestMode(const ModeList& modes) const { int area, total = 0; KScreen::ModePtr biggest; Q_FOREACH(const KScreen::ModePtr &mode, modes) { area = mode->size().width() * mode->size().height(); if (area < total) { continue; } if (area == total && mode->refreshRate() < biggest->refreshRate()) { continue; } if (area == total && mode->refreshRate() > biggest->refreshRate()) { biggest = mode; continue; } total = area; biggest = mode; } if (!biggest) { return 0; } return biggest->id(); } Output::Output() : QObject(0) , d(new Private()) { } Output::Output(Output::Private *dd) : QObject() , d(dd) { } Output::~Output() { delete d; } OutputPtr Output::clone() const { return OutputPtr(new Output(new Private(*d))); } int Output::id() const { return d->id; } void Output::setId(int id) { if (d->id == id) { return; } d->id = id; Q_EMIT outputChanged(); } QString Output::name() const { return d->name; } void Output::setName(const QString& name) { if (d->name == name) { return; } d->name = name; Q_EMIT outputChanged(); } Output::Type Output::type() const { return d->type; } void Output::setType(Type type) { if (d->type == type) { return; } d->type = type; Q_EMIT outputChanged(); } QString Output::icon() const { return d->icon; } void Output::setIcon(const QString& icon) { if (d->icon == icon) { return; } d->icon = icon; Q_EMIT outputChanged(); } ModePtr Output::mode(const QString& id) const { if (!d->modeList.contains(id)) { return ModePtr(); } return d->modeList[id]; } ModeList Output::modes() const { return d->modeList; } void Output::setModes(const ModeList &modes) { bool changed = !d->compareModeList(d->modeList, modes); d->modeList = modes; if (changed) { emit modesChanged(); emit outputChanged(); } } QString Output::currentModeId() const { return d->currentMode; } void Output::setCurrentModeId(const QString& mode) { if (d->currentMode == mode) { return; } d->currentMode = mode; Q_EMIT currentModeIdChanged(); } ModePtr Output::currentMode() const { return d->modeList.value(d->currentMode); } void Output::setPreferredModes(const QStringList &modes) { d->preferredMode = QString(); d->preferredModes = modes; } QStringList Output::preferredModes() const { return d->preferredModes; } QString Output::preferredModeId() const { if (!d->preferredMode.isEmpty()) { return d->preferredMode; } if (d->preferredModes.isEmpty()) { return d->biggestMode(modes()); } int area, total = 0; KScreen::ModePtr biggest; KScreen::ModePtr candidateMode; Q_FOREACH(const QString &modeId, d->preferredModes) { candidateMode = mode(modeId); area = candidateMode->size().width() * candidateMode->size().height(); if (area < total) { continue; } if (area == total && biggest && candidateMode->refreshRate() < biggest->refreshRate()) { continue; } if (area == total && biggest && candidateMode->refreshRate() > biggest->refreshRate()) { biggest = candidateMode; continue; } total = area; biggest = candidateMode; } Q_ASSERT_X(biggest, "preferredModeId", "biggest mode must exist"); d->preferredMode = biggest->id(); return d->preferredMode; } ModePtr Output::preferredMode() const { return d->modeList.value(preferredModeId()); } QPoint Output::pos() const { return d->pos; } void Output::setPos(const QPoint& pos) { if (d->pos == pos) { return; } d->pos = pos; Q_EMIT posChanged(); } QSize Output::size() const { return d->size; } void Output::setSize(const QSize& size) { if (d->size == size) { return; } d->size = size; Q_EMIT sizeChanged(); } Output::Rotation Output::rotation() const { return d->rotation; } void Output::setRotation(Output::Rotation rotation) { if (d->rotation == rotation) { return; } d->rotation = rotation; Q_EMIT rotationChanged(); } qreal Output::scale() const { return d->scale; } void Output::setScale(qreal factor) { if (d->scale == factor) { return; } d->scale = factor; emit scaleChanged(); } bool Output::isConnected() const { return d->connected; } void Output::setConnected(bool connected) { if (d->connected == connected) { return; } d->connected = connected; Q_EMIT isConnectedChanged(); } bool Output::isEnabled() const { return d->enabled; } void Output::setEnabled(bool enabled) { if (d->enabled == enabled) { return; } d->enabled = enabled; Q_EMIT isEnabledChanged(); } bool Output::isPrimary() const { return d->primary; } void Output::setPrimary(bool primary) { if (d->primary == primary) { return; } d->primary = primary; Q_EMIT isPrimaryChanged(); } QList Output::clones() const { return d->clones; } void Output::setClones(QList outputlist) { if (d->clones == outputlist) { return; } d->clones = outputlist; Q_EMIT clonesChanged(); } void Output::setEdid(const QByteArray& rawData) { Q_ASSERT(d->edid == 0); d->edid = new Edid(rawData); } Edid *Output::edid() const { return d->edid; } QSize Output::sizeMm() const { return d->sizeMm; } void Output::setSizeMm(const QSize &size) { d->sizeMm = size; } QRect Output::geometry() const { if (!currentMode()) { return QRect(); } // We can't use QRect(d->pos, d->size), because d->size does not reflect the // actual rotation() set by caller, it's only updated when we get update from // KScreen, but not when user changes mode or rotation manually QSize size = currentMode()->size() / d->scale; if (!isHorizontal()) { size = size.transposed(); } return QRect(d->pos, size); } void Output::apply(const OutputPtr& other) { typedef void (KScreen::Output::*ChangeSignal)(); QList changes; // We block all signals, and emit them only after we have set up everything // This is necessary in order to prevent clients from accessing inconsistent // outputs from intermediate change signals const bool keepBlocked = signalsBlocked(); blockSignals(true); if (d->name != other->d->name) { changes << &Output::outputChanged; setName(other->d->name); } if (d->type != other->d->type) { changes << &Output::outputChanged; setType(other->d->type); } if (d->icon != other->d->icon) { changes << &Output::outputChanged; setIcon(other->d->icon); } if (d->pos != other->d->pos) { changes << &Output::posChanged; setPos(other->pos()); } if (d->rotation != other->d->rotation) { changes << &Output::rotationChanged; setRotation(other->d->rotation); } + if (d->scale != other->d->scale) { + changes << &Output::scaleChanged; + setScale(other->d->scale); + } if (d->currentMode != other->d->currentMode) { changes << &Output::currentModeIdChanged; setCurrentModeId(other->d->currentMode); } if (d->connected != other->d->connected) { changes << &Output::isConnectedChanged; setConnected(other->d->connected); } if (d->enabled != other->d->enabled) { changes << &Output::isEnabledChanged; setEnabled(other->d->enabled); } if (d->primary != other->d->primary) { changes << &Output::isPrimaryChanged; setPrimary(other->d->primary); } if (d->clones != other->d->clones) { changes << &Output::clonesChanged; setClones(other->d->clones);; } if (!d->compareModeList(d->modeList, other->d->modeList)) { changes << &Output::outputChanged; } setPreferredModes(other->d->preferredModes); ModeList modes; Q_FOREACH (const ModePtr &otherMode, other->modes()) { modes.insert(otherMode->id(), otherMode->clone()); } setModes(modes); // Non-notifyable changes if (other->d->edid) { delete d->edid; d->edid = other->d->edid->clone(); } blockSignals(keepBlocked); while (!changes.isEmpty()) { const ChangeSignal &sig = changes.first(); Q_EMIT (this->*sig)(); changes.removeAll(sig); } } QDebug operator<<(QDebug dbg, const KScreen::OutputPtr &output) { if(output) { dbg << "KScreen::Output(" << output->id() << " " << output->name() << (output->isConnected() ? "connected" : "disconnected") << (output->isEnabled() ? "enabled" : "disabled") << output->pos() << output->size() << output->currentModeId() << ")"; } else { dbg << "KScreen::Output(NULL)"; } return dbg; } diff --git a/tests/kwayland/waylandtestserver.cpp b/tests/kwayland/waylandtestserver.cpp index 8aa0954..08599f1 100644 --- a/tests/kwayland/waylandtestserver.cpp +++ b/tests/kwayland/waylandtestserver.cpp @@ -1,179 +1,179 @@ /************************************************************************************* * Copyright 2014-2015 Sebastian Kügler * * * * 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 St, Fifth Floor, Boston, MA 02110-1301 USA * *************************************************************************************/ #include "waylandtestserver.h" #include "waylandconfigreader.h" #include #include #include #include #include #include #include #include "../src/edid.h" Q_LOGGING_CATEGORY(KSCREEN_WAYLAND_TESTSERVER, "kscreen.kwayland.testserver") using namespace KScreen; using namespace KWayland::Server; WaylandTestServer::WaylandTestServer(QObject *parent) : QObject(parent) , m_configFile(TEST_DATA + QStringLiteral("default.json")) , m_display(nullptr) , m_outputManagement(nullptr) , m_dpmsManager(nullptr) { } WaylandTestServer::~WaylandTestServer() { stop(); qCDebug(KSCREEN_WAYLAND_TESTSERVER) << "Wayland server shut down."; } void WaylandTestServer::start() { using namespace KWayland::Server; delete m_display; m_display = new KWayland::Server::Display(this); if (qgetenv("WAYLAND_DISPLAY").isEmpty()) { m_display->setSocketName(s_socketName); } else { m_display->setSocketName(qgetenv("WAYLAND_DISPLAY").constData()); } m_display->start(); auto manager = m_display->createDpmsManager(); manager->create(); m_outputManagement = m_display->createOutputManagement(); m_outputManagement->create(); connect(m_outputManagement, &OutputManagementInterface::configurationChangeRequested, this, &WaylandTestServer::configurationChangeRequested); KScreen::WaylandConfigReader::outputsFromConfig(m_configFile, m_display, m_outputs); qCDebug(KSCREEN_WAYLAND_TESTSERVER) << QString("export WAYLAND_DISPLAY="+m_display->socketName()); qCDebug(KSCREEN_WAYLAND_TESTSERVER) << QString("You can specify the WAYLAND_DISPLAY for this server by exporting it in the environment"); //showOutputs(); } void WaylandTestServer::stop() { Q_FOREACH (const auto &o, m_outputs) { delete o; } m_outputs.clear(); // actually stop the Wayland server delete m_display; m_display = nullptr; } KWayland::Server::Display* WaylandTestServer::display() { return m_display; } void WaylandTestServer::setConfig(const QString& configfile) { qCDebug(KSCREEN_WAYLAND_TESTSERVER) << "Creating Wayland server from " << configfile; m_configFile = configfile; } int WaylandTestServer::outputCount() const { return m_outputs.count(); } QList WaylandTestServer::outputs() const { return m_outputs; } void WaylandTestServer::configurationChangeRequested(KWayland::Server::OutputConfigurationInterface* configurationInterface) { qCDebug(KSCREEN_WAYLAND_TESTSERVER) << "Server received change request, changes:" << configurationInterface->changes().count(); auto changes = configurationInterface->changes(); Q_FOREACH (const auto &outputdevice, changes.keys()) { auto c = changes[outputdevice]; if (c->enabledChanged()) { qCDebug(KSCREEN_WAYLAND_TESTSERVER) << "Setting enabled:"; outputdevice->setEnabled(c->enabled()); } if (c->modeChanged()) { qCDebug(KSCREEN_WAYLAND_TESTSERVER) << "Setting new mode:" << c->mode() << modeString(outputdevice, c->mode()); outputdevice->setCurrentMode(c->mode()); } if (c->transformChanged()) { qCDebug(KSCREEN_WAYLAND_TESTSERVER) << "Server setting transform: " << (int)(c->transform()); outputdevice->setTransform(c->transform()); } if (c->positionChanged()) { qCDebug(KSCREEN_WAYLAND_TESTSERVER) << "Server setting position: " << c->position(); outputdevice->setGlobalPosition(c->position()); } if (c->scaleChanged()) { - qCDebug(KSCREEN_WAYLAND_TESTSERVER) << "Setting enabled:"; + qCDebug(KSCREEN_WAYLAND_TESTSERVER) << "Setting scale:" << c->scale(); outputdevice->setScale(c->scale()); } } configurationInterface->setApplied(); //showOutputs(); Q_EMIT configChanged(); } void WaylandTestServer::showOutputs() { qCDebug(KSCREEN_WAYLAND_TESTSERVER) << "******** Wayland server running: " << m_outputs.count() << " outputs. ********"; foreach (auto o, m_outputs) { bool enabled = (o->enabled() == KWayland::Server::OutputDeviceInterface::Enablement::Enabled); qCDebug(KSCREEN_WAYLAND_TESTSERVER) << " * Output id: " << o->uuid(); qCDebug(KSCREEN_WAYLAND_TESTSERVER) << " Enabled: " << (enabled ? "enabled" : "disabled"); qCDebug(KSCREEN_WAYLAND_TESTSERVER) << " Name: " << QString("%2-%3").arg(o->manufacturer(), o->model()); qCDebug(KSCREEN_WAYLAND_TESTSERVER) << " Mode: " << modeString(o, o->currentModeId()); qCDebug(KSCREEN_WAYLAND_TESTSERVER) << " Pos: " << o->globalPosition(); qCDebug(KSCREEN_WAYLAND_TESTSERVER) << " Edid: " << o->edid(); // << o->currentMode().size(); } qCDebug(KSCREEN_WAYLAND_TESTSERVER) << "******************************************************"; } QString WaylandTestServer::modeString(KWayland::Server::OutputDeviceInterface* outputdevice, int mid) { QString s; QString ids; int _i = 0; Q_FOREACH (const auto &_m, outputdevice->modes()) { _i++; if (_i < 6) { ids.append(QString::number(_m.id) + ", "); } else { ids.append("."); } if (_m.id == mid) { s = QString("%1x%2 @%3").arg(QString::number(_m.size.width()), \ QString::number(_m.size.height()), QString::number(_m.refreshRate)); } } return QString("[%1] %2 (%4 modes: %3)").arg(QString::number(mid), s, ids, QString::number(outputdevice->modes().count())); }