diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,9 +33,9 @@ * test: addition of a new test or correction of an existing one * Only the following optional scopes are allowed: * api - * kwayland * qscreen * randr + * wayland * Angular's [Revert][angular-revert] and [Subject][angular-subject] policies are applied. * Breaking changes are supposed to be pointed out only in prose in the commit body. * When a commit closes a bug on [Bugzilla][bugzilla] or when the commit has an associated Phabricator review special keywords must be used in the commit body to link the respective bug or review. See [here][commit-policy-keywords] for more information on these and other possible keywords. [Arcanist][arcanist] should be used for automating usage of the review keyword. diff --git a/backends/kwayland/CMakeLists.txt b/backends/kwayland/CMakeLists.txt --- a/backends/kwayland/CMakeLists.txt +++ b/backends/kwayland/CMakeLists.txt @@ -1,22 +1,117 @@ +find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS + CoreAddons +) set(wayland_SRCS waylandbackend.cpp waylandconfig.cpp + wayland_interface.cpp waylandoutput.cpp waylandscreen.cpp ../utils.cpp ) + +ecm_qt_declare_logging_category( + wayland_SRCS + HEADER wayland_logging.h + IDENTIFIER KSCREEN_WAYLAND + CATEGORY_NAME org.kde.kscreen.wayland +) + qt5_add_dbus_interface(wayland_SRCS org.kde.KWin.TabletModeManager.xml tabletmodemanager_interface) +set(wayland_LIBS + Qt5::Core + Qt5::DBus + KF5::Screen + KF5::CoreAddons +) + +########### +# Library # +########### +add_library(KScreenWayland SHARED ${wayland_SRCS}) +generate_export_header(KScreenWayland BASE_NAME kscreen_wayland) + +target_include_directories(KScreenWayland PUBLIC + "$" +) + +target_link_libraries(KScreenWayland PUBLIC ${wayland_LIBS}) + +set_target_properties(KScreenWayland PROPERTIES + VERSION "${KSCREEN_VERSION_STRING}" + SOVERSION "${KSCREEN_SOVERSION}" + EXPORT_NAME KScreenWayland +) + +install(TARGETS KScreenWayland EXPORT KScreenWaylandTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) + +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/kscreen_wayland_export.h + wayland_interface.h + waylandoutput.h + DESTINATION + ${KF5_INCLUDE_INSTALL_DIR}/KScreen/kscreen/wayland +) + +if(NOT WIN32) + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/libkscreen-wayland.pc.in + ${CMAKE_CURRENT_BINARY_DIR}/libkscreen-wayland.pc @ONLY + ) + install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/libkscreen-wayland.pc + DESTINATION ${KDE_INSTALL_LIBDIR}/pkgconfig + ) +endif(NOT WIN32) + +ecm_setup_version( + ${KF5_MIN_VERSION} + VARIABLE_PREFIX KSCREENWAYLAND + VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kscreen_wayland_version.h" + PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KScreenWaylandConfigVersion.cmake" +) + +set(CMAKECONFIG_INSTALL_DIR "${CMAKECONFIG_INSTALL_PREFIX}/KScreenWayland") + +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/KScreenWaylandConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/KScreenWaylandConfig.cmake" + INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} +) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/KScreenWaylandConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/KScreenWaylandConfigVersion.cmake" + DESTINATION "${CMAKECONFIG_INSTALL_DIR}" + COMPONENT Devel +) + +install(EXPORT + KScreenWaylandTargets + DESTINATION "${CMAKECONFIG_INSTALL_DIR}" + FILE KScreenWaylandTargets.cmake + COMPONENT Devel +) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/kscreen_wayland_version.h" + DESTINATION "${KF5_INCLUDE_INSTALL_DIR}" + COMPONENT Devel +) + +# Standard plugins +add_subdirectory(plugins) + +################## +# Wayland Plugin # +################## add_library(KSC_KWayland MODULE ${wayland_SRCS}) set_target_properties(KSC_KWayland PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kscreen") set_target_properties(KSC_KWayland PROPERTIES PREFIX "") -target_link_libraries(KSC_KWayland Qt5::Core - Qt5::DBus - Qt5::Gui - KF5::Screen - KF5::WaylandClient -) +target_link_libraries(KSC_KWayland ${wayland_LIBS}) install(TARGETS KSC_KWayland DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kscreen/) diff --git a/backends/kwayland/KScreenWaylandConfig.cmake.in b/backends/kwayland/KScreenWaylandConfig.cmake.in new file mode 100644 --- /dev/null +++ b/backends/kwayland/KScreenWaylandConfig.cmake.in @@ -0,0 +1,7 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(Qt5Core @QT_MIN_VERSION@) + +include("${CMAKE_CURRENT_LIST_DIR}/KScreenWaylandTargets.cmake") +@PACKAGE_INCLUDE_QCHTARGETS@ diff --git a/backends/kwayland/plugins/CMakeLists.txt b/backends/kwayland/plugins/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/backends/kwayland/plugins/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(kwayland) diff --git a/backends/kwayland/plugins/kwayland/CMakeLists.txt b/backends/kwayland/plugins/kwayland/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/backends/kwayland/plugins/kwayland/CMakeLists.txt @@ -0,0 +1,30 @@ +set(kwayland_SRCS + kwayland_interface.cpp + kwayland_output.cpp + kwayland_logging.cpp +) + +ecm_qt_declare_logging_category( + kwayland_SRCS + HEADER kwayland_logging.h + IDENTIFIER KSCREEN_WAYLAND + CATEGORY_NAME org.kde.kscreen.wayland +) + +add_library(kwayland MODULE ${kwayland_SRCS}) + +set_target_properties(kwayland + PROPERTIES LIBRARY_OUTPUT_DIRECTORY + "${CMAKE_BINARY_DIR}/bin/org.kde.libkscreen.backends/wayland/" +) + +target_link_libraries(kwayland + Qt5::Core + KScreenWayland + KF5::WaylandClient +) + +install( + TARGETS kwayland + DESTINATION ${PLUGIN_INSTALL_DIR}/org.kde.libkscreen.backends/wayland/ +) diff --git a/backends/kwayland/plugins/kwayland/kwayland.json b/backends/kwayland/plugins/kwayland/kwayland.json new file mode 100644 --- /dev/null +++ b/backends/kwayland/plugins/kwayland/kwayland.json @@ -0,0 +1,7 @@ +{ + "KPlugin": { + "Description": "Wayland backend using KWayland library's output management interface.", + "Id": "LibkscreenWaylandPluginKWayland", + "Name": "kwayland" + } +} diff --git a/backends/kwayland/plugins/kwayland/kwayland_interface.h b/backends/kwayland/plugins/kwayland/kwayland_interface.h new file mode 100644 --- /dev/null +++ b/backends/kwayland/plugins/kwayland/kwayland_interface.h @@ -0,0 +1,93 @@ +/************************************************************************* +Copyright © 2014-2015 Sebastian Kügler +Copyright © 2019-2020 Roman Gilg + +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 +**************************************************************************/ +#pragma once + +#include "wayland_interface.h" +#include "config.h" + +namespace KWayland +{ +namespace Client +{ +class ConnectionThread; +class EventQueue; +class Registry; +class OutputManagement; +} +} + +namespace KScreen +{ +class Output; +class KWaylandOutput; +class WaylandOutput; +class WaylandScreen; + +class KWaylandFactory : public WaylandFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.kde.libkscreen.waylandinterface" FILE "kwayland.json") + +public: + WaylandInterface* createInterface(QObject *parent = nullptr) override; +}; + +class KWaylandInterface : public WaylandInterface +{ + Q_OBJECT + +public: + explicit KWaylandInterface(QObject *parent = nullptr); + ~KWaylandInterface() override = default; + bool isInitialized() const override; + + QMap outputMap() const override; + + void applyConfig(const KScreen::ConfigPtr &newConfig) override; + void updateConfig(KScreen::ConfigPtr &config) override; + +protected: + void initConnection(QThread *thread) override; + void insertOutput(WaylandOutput *output) override; + WaylandOutput* takeOutput(WaylandOutput *output) override; + void handleDisconnect() override; + +private: + void setupRegistry(); + void addOutputDevice(quint32 name, quint32 version); + void tryPendingConfig(); + + KWayland::Client::ConnectionThread *m_connection; + KWayland::Client::EventQueue *m_queue; + + KWayland::Client::Registry *m_registry; + KWayland::Client::OutputManagement *m_outputManagement; + + // KWayland names as keys + QMap m_outputMap; + + // KWayland names + int m_lastOutputId = -1; + + bool m_registryInitialized; + bool m_blockSignals; + KScreen::ConfigPtr m_kscreenPendingConfig; +}; + +} diff --git a/backends/kwayland/plugins/kwayland/kwayland_interface.cpp b/backends/kwayland/plugins/kwayland/kwayland_interface.cpp new file mode 100644 --- /dev/null +++ b/backends/kwayland/plugins/kwayland/kwayland_interface.cpp @@ -0,0 +1,240 @@ +/************************************************************************* +Copyright © 2013 Martin Gräßlin +Copyright © 2014-2015 Sebastian Kügler +Copyright © 2019-2020 Roman Gilg + +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 "kwayland_interface.h" + +#include "waylandbackend.h" +#include "kwayland_output.h" +#include "waylandscreen.h" + +#include "kwayland_logging.h" + +#include +#include +#include +#include +#include + +#include + +using namespace KScreen; + + +WaylandInterface* KWaylandFactory::createInterface(QObject *parent) +{ + return new KWaylandInterface(parent); +} + +KWaylandInterface::KWaylandInterface(QObject *parent) + : WaylandInterface(parent) + , m_outputManagement(nullptr) + , m_registryInitialized(false) + , m_kscreenPendingConfig(nullptr) +{ +} + +void KWaylandInterface::initConnection(QThread *thread) +{ + m_connection = new KWayland::Client::ConnectionThread; + + connect(m_connection, &KWayland::Client::ConnectionThread::connected, + this, &KWaylandInterface::setupRegistry, Qt::QueuedConnection); + + connect(m_connection, &KWayland::Client::ConnectionThread::connectionDied, + this, &KWaylandInterface::handleDisconnect, Qt::QueuedConnection); + + connect(m_connection, &KWayland::Client::ConnectionThread::failed, this, [this] { + qCWarning(KSCREEN_WAYLAND) << "Failed to connect to Wayland server at socket:" + << m_connection->socketName(); + Q_EMIT connectionFailed(m_connection->socketName()); + }); + + m_connection->moveToThread(thread); + thread->start(); + m_connection->initConnection(); +} + +bool KWaylandInterface::isInitialized() const +{ + return m_registryInitialized && m_outputManagement != nullptr + && WaylandInterface::isInitialized(); +} + +void KWaylandInterface::handleDisconnect() +{ + qDeleteAll(m_outputMap); + m_outputMap.clear(); + + // Clean up + if (m_queue) { + delete m_queue; + m_queue = nullptr; + } + + m_connection->deleteLater(); + m_connection = nullptr; + + WaylandInterface::handleDisconnect(); +} + +void KWaylandInterface::setupRegistry() +{ + m_queue = new KWayland::Client::EventQueue(this); + m_queue->setup(m_connection); + + m_registry = new KWayland::Client::Registry(this); + + connect(m_registry, &KWayland::Client::Registry::outputDeviceAnnounced, + this, &KWaylandInterface::addOutputDevice); + + connect(m_registry, &KWayland::Client::Registry::outputManagementAnnounced, + this, [this](quint32 name, quint32 version) { + m_outputManagement = m_registry->createOutputManagement(name, version, m_registry); + } + ); + + connect(m_registry, &KWayland::Client::Registry::interfacesAnnounced, + this, [this] { + m_registryInitialized = true; + unblockSignals(); + checkInitialized(); + } + ); + + m_registry->create(m_connection); + m_registry->setEventQueue(m_queue); + m_registry->setup(); +} + +int s_outputId = 0; + +void KWaylandInterface::addOutputDevice(quint32 name, quint32 version) +{ + KWaylandOutput *output = new KWaylandOutput(++s_outputId, this); + output->createOutputDevice(m_registry, name, version); + addOutput(output); +} + +void KWaylandInterface::insertOutput(WaylandOutput *output) +{ + auto *out = static_cast(output); + m_outputMap.insert(out->id(), out); +} + +WaylandOutput* KWaylandInterface::takeOutput(WaylandOutput *output) +{ + auto *out = static_cast(output); + return m_outputMap.take(out->id()); +} + +void KWaylandInterface::updateConfig(KScreen::ConfigPtr &config) +{ + config->setSupportedFeatures(Config::Feature::Writable | Config::Feature::PerOutputScaling + | Config::Feature::AutoRotation | Config::Feature::TabletMode); + config->setValid(m_connection->display()); + + //Removing removed outputs + const KScreen::OutputList outputs = config->outputs(); + for (const auto &output : outputs) { + if (!m_outputMap.contains(output->id())) { + config->removeOutput(output->id()); + } + } + + // Add KScreen::Outputs that aren't in the list yet, handle primaryOutput + KScreen::OutputList kscreenOutputs = config->outputs(); + for (const auto &output : m_outputMap) { + KScreen::OutputPtr kscreenOutput = kscreenOutputs[output->id()]; + if (!kscreenOutput) { + kscreenOutput = output->toKScreenOutput(); + kscreenOutputs.insert(kscreenOutput->id(), kscreenOutput); + } + if (kscreenOutput && m_outputMap.count() == 1) { + kscreenOutput->setPrimary(true); + } else if (m_outputMap.count() > 1) { + // primaryScreen concept doesn't exist in Wayland, so we don't set one + } + output->updateKScreenOutput(kscreenOutput); + } + config->setOutputs(kscreenOutputs); +} + +QMap KWaylandInterface::outputMap() const +{ + QMap ret; + + QMap::const_iterator it = m_outputMap.constBegin(); + while (it != m_outputMap.constEnd()) { + ret[it.key()] = it.value(); + ++it; + } + return ret; +} + +void KWaylandInterface::tryPendingConfig() +{ + if (!m_kscreenPendingConfig) { + return; + } + applyConfig(m_kscreenPendingConfig); + m_kscreenPendingConfig = nullptr; +} + +void KWaylandInterface::applyConfig(const KScreen::ConfigPtr &newConfig) +{ + using namespace KWayland::Client; + + // Create a new configuration object + auto wlConfig = m_outputManagement->createConfiguration(); + bool changed = false; + + if (signalsBlocked()) { + /* Last apply still pending, remember new changes and apply afterwards */ + m_kscreenPendingConfig = newConfig; + return; + } + + for (const auto &output : newConfig->outputs()) { + changed |= m_outputMap[output->id()]->setWlConfig(wlConfig, output); + } + + if (!changed) { + return; + } + + // We now block changes in order to compress events while the compositor is doing its thing + // once it's done or failed, we'll trigger configChanged() only once, and not per individual + // property change. + connect(wlConfig, &OutputConfiguration::applied, this, [this, wlConfig] { + wlConfig->deleteLater(); + unblockSignals(); + Q_EMIT configChanged(); + tryPendingConfig(); + }); + connect(wlConfig, &OutputConfiguration::failed, this, [this, wlConfig] { + wlConfig->deleteLater(); + unblockSignals(); + Q_EMIT configChanged(); + tryPendingConfig(); + }); + + // Now block signals and ask the compositor to apply the changes. + blockSignals(); + wlConfig->apply(); +} diff --git a/backends/kwayland/plugins/kwayland/kwayland_output.h b/backends/kwayland/plugins/kwayland/kwayland_output.h new file mode 100644 --- /dev/null +++ b/backends/kwayland/plugins/kwayland/kwayland_output.h @@ -0,0 +1,73 @@ +/************************************************************************* +Copyright © 2014-2015 Sebastian Kügler +Copyright © 2019-2020 Roman Gilg + +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 +**************************************************************************/ +#pragma once + +#include "waylandoutput.h" + +#include "output.h" + +#include +#include + +#include + +namespace KWayland +{ +namespace Client +{ +class OutputConfiguration; +} +} + +namespace KScreen +{ + +class KWaylandOutput : public WaylandOutput +{ + Q_OBJECT + +public: + explicit KWaylandOutput(quint32 id, QObject *parent = nullptr); + ~KWaylandOutput() override = default; + + void updateKScreenOutput(KScreen::OutputPtr &output) override; + + QString name() const; + QByteArray edid() const override; + bool enabled() const override; + QRectF geometry() const override; + + KWayland::Client::OutputDevice* outputDevice() const; + void createOutputDevice(KWayland::Client::Registry *registry, quint32 name, quint32 version); + + bool setWlConfig(KWayland::Client::OutputConfiguration *wlConfig, + const KScreen::OutputPtr &output); + +private: + void showOutput(); + QString modeName(const KWayland::Client::OutputDevice::Mode &m) const; + + KWayland::Client::OutputDevice *m_device; + KWayland::Client::Registry *m_registry; + + // left-hand-side: KScreen::Mode, right-hand-side: KWayland's mode.id + QMap m_modeIdMap; +}; + +} diff --git a/backends/kwayland/waylandoutput.cpp b/backends/kwayland/plugins/kwayland/kwayland_output.cpp copy from backends/kwayland/waylandoutput.cpp copy to backends/kwayland/plugins/kwayland/kwayland_output.cpp --- a/backends/kwayland/waylandoutput.cpp +++ b/backends/kwayland/plugins/kwayland/kwayland_output.cpp @@ -1,28 +1,28 @@ -/************************************************************************************* - * 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 "waylandoutput.h" -#include "waylandbackend.h" -#include "waylandconfig.h" -#include "../utils.h" +/************************************************************************* +Copyright © 2014-2015 Sebastian Kügler +Copyright © 2019-2020 Roman Gilg + +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 "kwayland_output.h" #include #include +#include "kwayland_logging.h" + #include #include @@ -52,53 +52,47 @@ return s_rotationMap.key(rotation); } -WaylandOutput::WaylandOutput(quint32 id, WaylandConfig *parent) - : QObject(parent) - , m_id(id) +KWaylandOutput::KWaylandOutput(quint32 id, QObject *parent) + : WaylandOutput(id, parent) , m_device(nullptr) { } -quint32 WaylandOutput::id() const +bool KWaylandOutput::enabled() const { - Q_ASSERT(m_device); - return m_id; + return m_device != nullptr; } -bool WaylandOutput::enabled() const +QByteArray KWaylandOutput::edid() const { - return m_device != nullptr; + return m_device->edid(); +} + +QRectF KWaylandOutput::geometry() const +{ + return m_device->geometry(); } -Wl::OutputDevice* WaylandOutput::outputDevice() const +Wl::OutputDevice* KWaylandOutput::outputDevice() const { return m_device; } -void WaylandOutput::createOutputDevice(Wl::Registry *registry, quint32 name, quint32 version) +void KWaylandOutput::createOutputDevice(Wl::Registry *registry, quint32 name, quint32 version) { Q_ASSERT(!m_device); m_device = registry->createOutputDevice(name, version); - connect(m_device, &Wl::OutputDevice::removed, this, &WaylandOutput::deviceRemoved); + connect(m_device, &Wl::OutputDevice::removed, this, &KWaylandOutput::removed); connect(m_device, &Wl::OutputDevice::done, this, [this]() { - Q_EMIT complete(); - connect(m_device, &Wl::OutputDevice::changed, this, &WaylandOutput::changed); + Q_EMIT dataReceived(); + connect(m_device, &Wl::OutputDevice::changed, this, &KWaylandOutput::changed); }); } -OutputPtr WaylandOutput::toKScreenOutput() -{ - OutputPtr output(new Output()); - output->setId(m_id); - updateKScreenOutput(output); - return output; -} - -void WaylandOutput::updateKScreenOutput(OutputPtr &output) +void KWaylandOutput::updateKScreenOutput(OutputPtr &output) { // Initialize primary output - output->setId(m_id); output->setEnabled(m_device->enabled() == Wl::OutputDevice::Enablement::Enabled); output->setConnected(true); output->setPrimary(true); // FIXME: wayland doesn't have the concept of a primary display @@ -154,10 +148,10 @@ output->setPreferredModes(preferredModeIds); output->setModes(modeList); output->setScale(m_device->scaleF()); - output->setType(Utils::guessOutputType(m_device->model(), m_device->model())); + output->setType(guessType(m_device->model(), m_device->model())); } -bool WaylandOutput::setWlConfig(Wl::OutputConfiguration *wlConfig, +bool KWaylandOutput::setWlConfig(Wl::OutputConfiguration *wlConfig, const KScreen::OutputPtr &output) { bool changed = false; @@ -203,22 +197,22 @@ return changed; } -QString WaylandOutput::modeName(const Wl::OutputDevice::Mode &m) const +QString KWaylandOutput::modeName(const Wl::OutputDevice::Mode &m) const { return QString::number(m.size.width()) + QLatin1Char('x') + QString::number(m.size.height()) + QLatin1Char('@') + QString::number(qRound(m.refreshRate/1000.0)); } -QString WaylandOutput::name() const +QString KWaylandOutput::name() const { Q_ASSERT(m_device); return QStringLiteral("%1 %2").arg(m_device->manufacturer(), m_device->model()); } -QDebug operator<<(QDebug dbg, const WaylandOutput *output) +QDebug operator<<(QDebug dbg, const KWaylandOutput *output) { - dbg << "WaylandOutput(Id:" << output->id() <<", Name:" << \ + dbg << "KWaylandOutput(Id:" << output->id() <<", Name:" << \ QString(output->outputDevice()->manufacturer() + QLatin1Char(' ') + \ output->outputDevice()->model()) << ")"; return dbg; diff --git a/backends/kwayland/wayland_interface.h b/backends/kwayland/wayland_interface.h new file mode 100644 --- /dev/null +++ b/backends/kwayland/wayland_interface.h @@ -0,0 +1,105 @@ +/************************************************************************* +Copyright © 2020 Roman Gilg + +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 +**************************************************************************/ +#pragma once + +#include + +#include "kscreen_wayland_export.h" + +#include +#include +#include + +class QThread; + +namespace KScreen +{ +class Output; +class WaylandOutput; +class WaylandScreen; + +class KSCREEN_WAYLAND_EXPORT WaylandInterface : public QObject +{ + Q_OBJECT + +public: + ~WaylandInterface() override; + + virtual void initConnection(QThread *thread) = 0; + virtual bool isInitialized() const; + + // Compositor side names as keys + virtual QMap outputMap() const = 0; + + virtual void applyConfig(const KScreen::ConfigPtr &newConfig); + virtual void updateConfig(KScreen::ConfigPtr &config) = 0; + +Q_SIGNALS: + void configChanged(); + void initialized(); + void connectionFailed(const QString &socketName); + void outputsChanged(); + +protected: + explicit WaylandInterface(QObject *parent = nullptr); + + virtual void insertOutput(WaylandOutput *output) = 0; + virtual WaylandOutput* takeOutput(WaylandOutput *output) = 0; + + bool signalsBlocked() const; + void blockSignals(); + void unblockSignals(); + + void checkInitialized(); + + void addOutput(WaylandOutput *output); + virtual void handleDisconnect(); + +private: + void removeOutput(WaylandOutput *output); + + /** + * Finalize: when the output is is initialized, we put it in the known outputs map, + * remove it from the list of initializing outputs, and emit configChanged(). + */ + virtual void initOutput(WaylandOutput *output); + + void tryPendingConfig(); + + // Compositor side names + QList m_initializingOutputs; + int m_lastOutputId = -1; + + bool m_blockSignals; + KScreen::ConfigPtr m_kscreenConfig; + WaylandScreen *m_screen; +}; + +class KSCREEN_EXPORT WaylandFactory : public QObject +{ + Q_OBJECT +public: + WaylandFactory(QObject *parent = nullptr) : QObject(parent) {} + ~WaylandFactory() override = default; + + virtual WaylandInterface* createInterface(QObject *parent = nullptr) = 0; +}; + +} + +Q_DECLARE_INTERFACE(KScreen::WaylandFactory, "org.kde.libkscreen.waylandinterface") diff --git a/backends/kwayland/wayland_interface.cpp b/backends/kwayland/wayland_interface.cpp new file mode 100644 --- /dev/null +++ b/backends/kwayland/wayland_interface.cpp @@ -0,0 +1,128 @@ +/************************************************************************* +Copyright © 2020 Roman Gilg + +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 "wayland_interface.h" + +#include "waylandbackend.h" +#include "waylandoutput.h" +#include "waylandscreen.h" + +#include +#include + +#include "../../wayland_logging.h" + +#include +#include + +using namespace KScreen; + +WaylandInterface::WaylandInterface(QObject *parent) + : QObject(parent) + , m_blockSignals(true) + , m_kscreenConfig(new Config) +{ +} + +WaylandInterface::~WaylandInterface() = default; + +bool WaylandInterface::signalsBlocked() const +{ + return m_blockSignals; +} + +void WaylandInterface::blockSignals() +{ + Q_ASSERT(m_blockSignals == false); + m_blockSignals = true; +} + +void WaylandInterface::unblockSignals() +{ + Q_ASSERT(m_blockSignals == true); + m_blockSignals = false; +} + +void WaylandInterface::handleDisconnect() +{ + qCWarning(KSCREEN_WAYLAND) << "Wayland disconnected, cleaning up."; + Q_EMIT configChanged(); +} + +void WaylandInterface::addOutput(WaylandOutput *output) +{ + m_initializingOutputs << output; + + connect(output, &WaylandOutput::removed, + this, [this, output]() { removeOutput(output); }); + connect(output, &WaylandOutput::dataReceived, + this, [this, output]() { initOutput(output); }); +} + +void WaylandInterface::initOutput(WaylandOutput *output) +{ + insertOutput(output); + m_initializingOutputs.removeOne(output); + checkInitialized(); + + if (!signalsBlocked() && m_initializingOutputs.empty()) { + Q_EMIT outputsChanged(); + Q_EMIT configChanged(); + } + + connect(output, &WaylandOutput::changed, this, [this]() { + if (!signalsBlocked()) { + Q_EMIT configChanged(); + } + }); +} + +void WaylandInterface::removeOutput(WaylandOutput *output) +{ + if (m_initializingOutputs.removeOne(output)) { + // Output was not yet fully initialized, just remove here and return. + delete output; + return; + } + + // remove the output from output mapping + const auto removedOutput = takeOutput(output); + Q_ASSERT(removedOutput == output); Q_UNUSED(removedOutput); + Q_EMIT outputsChanged(); + delete output; + + if (!m_blockSignals) { + Q_EMIT configChanged(); + } +} + +void WaylandInterface::checkInitialized() +{ + if (isInitialized()) { + Q_EMIT initialized(); + } +} + +bool WaylandInterface::isInitialized() const +{ + return !m_blockSignals && m_initializingOutputs.isEmpty(); +} + +void WaylandInterface::applyConfig(const KScreen::ConfigPtr &newConfig) +{ + Q_UNUSED(newConfig) +} diff --git a/backends/kwayland/waylandbackend.h b/backends/kwayland/waylandbackend.h --- a/backends/kwayland/waylandbackend.h +++ b/backends/kwayland/waylandbackend.h @@ -21,8 +21,6 @@ #include "abstractbackend.h" -#include - namespace KScreen { @@ -49,5 +47,3 @@ }; } - -Q_DECLARE_LOGGING_CATEGORY(KSCREEN_WAYLAND) diff --git a/backends/kwayland/waylandbackend.cpp b/backends/kwayland/waylandbackend.cpp --- a/backends/kwayland/waylandbackend.cpp +++ b/backends/kwayland/waylandbackend.cpp @@ -22,17 +22,16 @@ #include "waylandconfig.h" #include "waylandoutput.h" +#include "wayland_logging.h" + #include #include #include #include using namespace KScreen; -Q_LOGGING_CATEGORY(KSCREEN_WAYLAND, "kscreen.kwayland") - - WaylandBackend::WaylandBackend() : KScreen::AbstractBackend() , m_internalConfig(new WaylandConfig(this)) @@ -75,7 +74,7 @@ if (!output) { return QByteArray(); } - return output->outputDevice()->edid(); + return output->edid(); } bool WaylandBackend::isValid() const diff --git a/backends/kwayland/waylandconfig.h b/backends/kwayland/waylandconfig.h --- a/backends/kwayland/waylandconfig.h +++ b/backends/kwayland/waylandconfig.h @@ -1,47 +1,37 @@ -/************************************************************************************* - * 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 * - *************************************************************************************/ +/************************************************************************* +Copyright © 2014-2015 Sebastian Kügler +Copyright © 2019-2020 Roman Gilg + +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 +**************************************************************************/ #pragma once #include "abstractbackend.h" #include "config.h" -#include #include -#include -#include -#include -#include -#include +#include -namespace KWayland -{ -namespace Client -{ -class ConnectionThread; -class EventQueue; -class Registry; -class OutputManagement; -} -} +#include + +class KPluginMetaData; namespace KScreen { class Output; +class WaylandInterface; class WaylandOutput; class WaylandScreen; @@ -66,8 +56,8 @@ explicit WaylandConfig(QObject *parent = nullptr); ~WaylandConfig() override; - KScreen::ConfigPtr currentConfig(); - QMap outputMap() const; + virtual KScreen::ConfigPtr currentConfig(); + virtual QMap outputMap() const; void applyConfig(const KScreen::ConfigPtr &newConfig); @@ -78,43 +68,33 @@ void initialized(); private: - void setupRegistry(); - void checkInitialized(); - void disconnected(); + struct PendingInterface { + bool operator ==(const PendingInterface &other) { + return name == other.name; + } + QString name; + WaylandInterface *interface; + QThread *thread; + }; void initKWinTabletMode(); - void initConnection(); - - void addOutput(quint32 name, quint32 version); - void removeOutput(WaylandOutput *output); - - void blockSignals(); - void unblockSignals(); - void tryPendingConfig(); + void setScreenOutputs(); - KWayland::Client::ConnectionThread *m_connection; - KWayland::Client::EventQueue *m_queue; - QThread *m_thread; + void queryInterfaces(); + void queryInterface(KPluginMetaData *plugin); + void takeInterface(const PendingInterface &pending); + void rejectInterface(const PendingInterface &pending); - KWayland::Client::Registry *m_registry; - KWayland::Client::OutputManagement *m_outputManagement; - - // KWayland names as keys - QMap m_outputMap; - - // KWayland names - QList m_initializingOutputs; - int m_lastOutputId = -1; - - bool m_registryInitialized; - bool m_blockSignals; - QEventLoop m_syncLoop; KScreen::ConfigPtr m_kscreenConfig; - KScreen::ConfigPtr m_kscreenPendingConfig; WaylandScreen *m_screen; + QPointer m_interface; bool m_tabletModeAvailable; bool m_tabletModeEngaged; + + QEventLoop m_syncLoop; + + std::vector m_pendingInterfaces; }; } diff --git a/backends/kwayland/waylandconfig.cpp b/backends/kwayland/waylandconfig.cpp --- a/backends/kwayland/waylandconfig.cpp +++ b/backends/kwayland/waylandconfig.cpp @@ -1,75 +1,55 @@ -/************************************************************************************* - * Copyright 2014-2015 Sebastian Kügler * - * Copyright 2013 Martin Gräßlin * - * * - * 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 * - *************************************************************************************/ +/************************************************************************* +Copyright © 2013 Martin Gräßlin +Copyright © 2014-2015 Sebastian Kügler +Copyright © 2019-2020 Roman Gilg + +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 "waylandconfig.h" #include "waylandbackend.h" +#include "wayland_interface.h" #include "waylandoutput.h" #include "waylandscreen.h" #include "tabletmodemanager_interface.h" +#include "wayland_logging.h" -#include -#include +#include +#include -#include -#include -#include -#include -#include - -#include - -using namespace KScreen; +namespace KScreen +{ WaylandConfig::WaylandConfig(QObject *parent) : QObject(parent) - , m_outputManagement(nullptr) - , m_registryInitialized(false) - , m_blockSignals(true) , m_kscreenConfig(new Config) - , m_kscreenPendingConfig(nullptr) , m_screen(new WaylandScreen(this)) , m_tabletModeAvailable(false) , m_tabletModeEngaged(false) { initKWinTabletMode(); - - connect(this, &WaylandConfig::initialized, &m_syncLoop, &QEventLoop::quit); - QTimer::singleShot(3000, this, [this] { - if (m_syncLoop.isRunning()) { - qCWarning(KSCREEN_WAYLAND) << "Connection to Wayland server at socket:" - << m_connection->socketName() << "timed out."; - m_syncLoop.quit(); - m_thread->quit(); - m_thread->wait(); - } - }); - - initConnection(); - m_syncLoop.exec(); + queryInterfaces(); } WaylandConfig::~WaylandConfig() { - m_thread->quit(); - m_thread->wait(); - m_syncLoop.quit(); + for (auto pending : m_pendingInterfaces) { + rejectInterface(pending); + } + m_pendingInterfaces.clear(); } void WaylandConfig::initKWinTabletMode() @@ -92,7 +72,7 @@ return; } m_tabletModeEngaged = tabletMode; - if (!m_blockSignals && m_initializingOutputs.empty()) { + if (m_interface && m_interface->isInitialized()) { Q_EMIT configChanged(); } } @@ -103,271 +83,132 @@ return; } m_tabletModeAvailable = available; - if (!m_blockSignals && m_initializingOutputs.empty()) { + if (m_interface && m_interface->isInitialized()) { Q_EMIT configChanged(); } }); } -void WaylandConfig::initConnection() +bool WaylandConfig::isInitialized() const { - m_thread = new QThread(this); - m_connection = new KWayland::Client::ConnectionThread; - - connect(m_connection, &KWayland::Client::ConnectionThread::connected, - this, &WaylandConfig::setupRegistry, Qt::QueuedConnection); - - connect(m_connection, &KWayland::Client::ConnectionThread::connectionDied, - this, &WaylandConfig::disconnected, Qt::QueuedConnection); + return m_interface && m_interface->isInitialized(); +} - connect(m_connection, &KWayland::Client::ConnectionThread::failed, this, [this] { - qCWarning(KSCREEN_WAYLAND) << "Failed to connect to Wayland server at socket:" - << m_connection->socketName(); - m_syncLoop.quit(); - m_thread->quit(); - m_thread->wait(); - }); +ConfigPtr WaylandConfig::currentConfig() +{ + // TODO: do this setScreen call less clunky + m_kscreenConfig->setScreen(m_screen->toKScreenScreen(m_kscreenConfig)); - m_thread->start(); - m_connection->moveToThread(m_thread); - m_connection->initConnection(); + m_interface->updateConfig(m_kscreenConfig); -} + ScreenPtr screen = m_kscreenConfig->screen(); + m_screen->updateKScreenScreen(screen); -void WaylandConfig::blockSignals() -{ - Q_ASSERT(m_blockSignals == false); - m_blockSignals = true; + return m_kscreenConfig; } -void WaylandConfig::unblockSignals() +void WaylandConfig::setScreenOutputs() { - Q_ASSERT(m_blockSignals == true); - m_blockSignals = false; + m_screen->setOutputs(outputMap().values()); } -void WaylandConfig::disconnected() +QMap WaylandConfig::outputMap() const { - qCWarning(KSCREEN_WAYLAND) << "Wayland disconnected, cleaning up."; - qDeleteAll(m_outputMap); - m_outputMap.clear(); - - // Clean up - if (m_queue) { - delete m_queue; - m_queue = nullptr; - } - - m_connection->deleteLater(); - m_connection = nullptr; - - if (m_thread) { - m_thread->quit(); - if (!m_thread->wait(3000)) { - m_thread->terminate(); - m_thread->wait(); - } - delete m_thread; - m_thread = nullptr; - } - - Q_EMIT configChanged(); + return m_interface->outputMap(); } -void WaylandConfig::setupRegistry() +void WaylandConfig::applyConfig(const ConfigPtr &newConfig) { - m_queue = new KWayland::Client::EventQueue(this); - m_queue->setup(m_connection); - - m_registry = new KWayland::Client::Registry(this); - - connect(m_registry, &KWayland::Client::Registry::outputDeviceAnnounced, - this, &WaylandConfig::addOutput); - - connect(m_registry, &KWayland::Client::Registry::outputManagementAnnounced, - this, [this](quint32 name, quint32 version) { - m_outputManagement = m_registry->createOutputManagement(name, version, m_registry); - checkInitialized(); - } - ); - - connect(m_registry, &KWayland::Client::Registry::interfacesAnnounced, - this, [this] { - m_registryInitialized = true; - unblockSignals(); - checkInitialized(); - } - ); - - m_registry->create(m_connection); - m_registry->setEventQueue(m_queue); - m_registry->setup(); + m_interface->applyConfig(newConfig); } -int s_outputId = 0; - -void WaylandConfig::addOutput(quint32 name, quint32 version) +void WaylandConfig::queryInterfaces() { - WaylandOutput *waylandoutput = new WaylandOutput(++s_outputId, this); - m_initializingOutputs << waylandoutput; - - connect(waylandoutput, &WaylandOutput::deviceRemoved, this, [this, waylandoutput]() { - removeOutput(waylandoutput); - }); - waylandoutput->createOutputDevice(m_registry, name, version); - - // finalize: when the output is done, we put it in the known outputs map, - // remove if from the list of initializing outputs, and emit configChanged() - connect(waylandoutput, &WaylandOutput::complete, this, [this, waylandoutput]{ - m_outputMap.insert(waylandoutput->id(), waylandoutput); - m_initializingOutputs.removeOne(waylandoutput); - checkInitialized(); - - if (!m_blockSignals && m_initializingOutputs.empty()) { - m_screen->setOutputs(m_outputMap.values()); - Q_EMIT configChanged(); + QTimer::singleShot(3000, this, [this] { + for (auto pending : m_pendingInterfaces) { + qCWarning(KSCREEN_WAYLAND) << pending.name << "backend could not be aquired in time."; + rejectInterface(pending); } - - connect(waylandoutput, &WaylandOutput::changed, this, [this]() { - if (!m_blockSignals) { - Q_EMIT configChanged(); - } - }); + if (m_syncLoop.isRunning()) { + qCWarning(KSCREEN_WAYLAND) << "Connection to Wayland server timed out. Does the " + "compositor support output management?"; + m_syncLoop.quit(); + } + m_pendingInterfaces.clear(); }); -} - -void WaylandConfig::removeOutput(WaylandOutput *output) -{ - if (m_initializingOutputs.removeOne(output)) { - // output was not yet fully initialized, just remove here and return - delete output; - return; - } - // remove the output from output mapping - const auto removedOutput = m_outputMap.take(output->id()); - Q_ASSERT(removedOutput == output); Q_UNUSED(removedOutput); - m_screen->setOutputs(m_outputMap.values()); - delete output; + auto availableInterfacePlugins + = KPluginLoader::findPlugins(QStringLiteral("org.kde.libkscreen.backends/wayland")); - if (!m_blockSignals) { - Q_EMIT configChanged(); + for (auto plugin : availableInterfacePlugins) { + queryInterface(&plugin); } + m_syncLoop.exec(); } -bool WaylandConfig::isInitialized() const -{ - return !m_blockSignals - && m_registryInitialized - && m_initializingOutputs.isEmpty() - && m_outputMap.count() > 0 - && m_outputManagement != nullptr; -} - -void WaylandConfig::checkInitialized() +void WaylandConfig::queryInterface(KPluginMetaData *plugin) { - if (isInitialized()) { - m_screen->setOutputs(m_outputMap.values()); - Q_EMIT initialized(); - } -} + PendingInterface pending; -KScreen::ConfigPtr WaylandConfig::currentConfig() -{ - // TODO: do this setScreen call less clunky - m_kscreenConfig->setScreen(m_screen->toKScreenScreen(m_kscreenConfig)); + pending.name = plugin->name(); - const auto features = Config::Feature::Writable | Config::Feature::PerOutputScaling - | Config::Feature::AutoRotation | Config::Feature::TabletMode; - m_kscreenConfig->setSupportedFeatures(features); - m_kscreenConfig->setValid(m_connection->display()); - - KScreen::ScreenPtr screen = m_kscreenConfig->screen(); - m_screen->updateKScreenScreen(screen); - - //Removing removed outputs - const KScreen::OutputList outputs = m_kscreenConfig->outputs(); - for (const auto &output : outputs) { - if (!m_outputMap.contains(output->id())) { - m_kscreenConfig->removeOutput(output->id()); - } + // TODO: qobject_cast not working here. Why? + auto *factory = dynamic_cast(plugin->instantiate()); + if (!factory) { + return; } + pending.interface = factory->createInterface(this); + pending.thread = new QThread(this); + + m_pendingInterfaces.push_back(pending); + connect(pending.interface, &WaylandInterface::connectionFailed, this, [this, &pending] { + qCWarning(KSCREEN_WAYLAND) << "Backend" << pending.name << "failed."; + rejectInterface(pending); + m_pendingInterfaces.erase(std::remove(m_pendingInterfaces.begin(), + m_pendingInterfaces.end(), pending), + m_pendingInterfaces.end()); + }); - // Add KScreen::Outputs that aren't in the list yet, handle primaryOutput - KScreen::OutputList kscreenOutputs = m_kscreenConfig->outputs(); - for (const auto &output : m_outputMap) { - KScreen::OutputPtr kscreenOutput = kscreenOutputs[output->id()]; - if (!kscreenOutput) { - kscreenOutput = output->toKScreenOutput(); - kscreenOutputs.insert(kscreenOutput->id(), kscreenOutput); + connect(pending.interface, &WaylandInterface::initialized, this, [this, pending] { + if (m_interface) { + // Too late. Already have an interface initialized. + return; } - if (kscreenOutput && m_outputMap.count() == 1) { - kscreenOutput->setPrimary(true); - } else if (m_outputMap.count() > 1) { - // primaryScreen concept doesn't exist in kwayland, so we don't set one + + for (auto other : m_pendingInterfaces) { + if (other.interface != pending.interface) { + rejectInterface(other); + } } - output->updateKScreenOutput(kscreenOutput); - } - m_kscreenConfig->setOutputs(kscreenOutputs); + m_pendingInterfaces.clear(); - m_kscreenConfig->setTabletModeAvailable(m_tabletModeAvailable); - m_kscreenConfig->setTabletModeEngaged(m_tabletModeEngaged); + takeInterface(pending); + m_syncLoop.quit(); + }); - return m_kscreenConfig; + pending.interface->initConnection(pending.thread); } -QMap WaylandConfig::outputMap() const +void WaylandConfig::takeInterface(const PendingInterface &pending) { - return m_outputMap; -} + m_interface = pending.interface; + connect(m_interface, &WaylandInterface::configChanged, this, &WaylandConfig::configChanged); -void WaylandConfig::tryPendingConfig() -{ - if (!m_kscreenPendingConfig) { - return; - } - applyConfig(m_kscreenPendingConfig); - m_kscreenPendingConfig = nullptr; + setScreenOutputs(); + connect(m_interface, &WaylandInterface::outputsChanged, this, &WaylandConfig::setScreenOutputs); + + qCDebug(KSCREEN_WAYLAND) << "Backend" << pending.name << "initialized."; + Q_EMIT initialized(); } -void WaylandConfig::applyConfig(const KScreen::ConfigPtr &newConfig) +void WaylandConfig::rejectInterface(const PendingInterface &pending) { - using namespace KWayland::Client; - // Create a new configuration object - auto wlConfig = m_outputManagement->createConfiguration(); - bool changed = false; - - if (m_blockSignals) { - /* Last apply still pending, remember new changes and apply afterwards */ - m_kscreenPendingConfig = newConfig; - return; - } + pending.thread->quit(); + pending.thread->wait(); + delete pending.thread; + delete pending.interface; - for (const auto &output : newConfig->outputs()) { - changed |= m_outputMap[output->id()]->setWlConfig(wlConfig, output); - } - - if (!changed) { - return; - } - - // We now block changes in order to compress events while the compositor is doing its thing - // once it's done or failed, we'll trigger configChanged() only once, and not per individual - // property change. - connect(wlConfig, &OutputConfiguration::applied, this, [this, wlConfig] { - wlConfig->deleteLater(); - unblockSignals(); - Q_EMIT configChanged(); - tryPendingConfig(); - }); - connect(wlConfig, &OutputConfiguration::failed, this, [this, wlConfig] { - wlConfig->deleteLater(); - unblockSignals(); - Q_EMIT configChanged(); - tryPendingConfig(); - }); + qCDebug(KSCREEN_WAYLAND) << "Backend" << pending.name << "rejected."; +} - // Now block signals and ask the compositor to apply the changes. - blockSignals(); - wlConfig->apply(); } diff --git a/backends/kwayland/waylandoutput.h b/backends/kwayland/waylandoutput.h --- a/backends/kwayland/waylandoutput.h +++ b/backends/kwayland/waylandoutput.h @@ -1,85 +1,61 @@ -/************************************************************************************* - * 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 * - *************************************************************************************/ +/************************************************************************* +Copyright © 2014-2015 Sebastian Kügler +Copyright © 2019-2020 Roman Gilg + +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 +**************************************************************************/ #pragma once -#include "waylandconfig.h" +#include -#include "abstractbackend.h" -#include "output.h" +#include "kscreen_wayland_export.h" -#include -#include - -#include -#include -#include - -namespace KWayland -{ -namespace Client -{ -class OutputConfiguration; -} -} +#include namespace KScreen { +class WaylandInterface; -class WaylandOutput : public QObject +class KSCREEN_WAYLAND_EXPORT WaylandOutput : public QObject { Q_OBJECT public: - explicit WaylandOutput(quint32 id, WaylandConfig *parent = nullptr); + explicit WaylandOutput(quint32 id, QObject *parent = nullptr); ~WaylandOutput() override = default; KScreen::OutputPtr toKScreenOutput(); - void updateKScreenOutput(KScreen::OutputPtr &output); - - quint32 id() const; - QString name() const; - bool enabled() const; + virtual void updateKScreenOutput(KScreen::OutputPtr &output) = 0; - KWayland::Client::OutputDevice* outputDevice() const; - void createOutputDevice(KWayland::Client::Registry *registry, quint32 name, quint32 version); + virtual quint32 id() const; + virtual QByteArray edid() const = 0; + virtual bool enabled() const = 0; + virtual QRectF geometry() const = 0; - bool setWlConfig(KWayland::Client::OutputConfiguration *wlConfig, - const KScreen::OutputPtr &output); + KScreen::Output::Type guessType(const QString &type, const QString &name) const; Q_SIGNALS: - void deviceRemoved(); - - void complete(); - // only emitted after complete signal + void dataReceived(); void changed(); + void removed(); private: - void showOutput(); - QString modeName(const KWayland::Client::OutputDevice::Mode &m) const; - quint32 m_id; - KWayland::Client::OutputDevice *m_device; - KWayland::Client::Registry *m_registry; - // left-hand-side: KScreen::Mode, right-hand-side: KWayland's mode.id + // left-hand-side: KScreen::Mode, right-hand-side: Compositor's mode id QMap m_modeIdMap; }; } - -KSCREEN_EXPORT QDebug operator<<(QDebug dbg, const KScreen::WaylandOutput *output); diff --git a/backends/kwayland/waylandoutput.cpp b/backends/kwayland/waylandoutput.cpp --- a/backends/kwayland/waylandoutput.cpp +++ b/backends/kwayland/waylandoutput.cpp @@ -1,225 +1,46 @@ -/************************************************************************************* - * 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 * - *************************************************************************************/ +/************************************************************************* +Copyright © 2014-2015 Sebastian Kügler +Copyright © 2019-2020 Roman Gilg + +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 "waylandoutput.h" -#include "waylandbackend.h" -#include "waylandconfig.h" #include "../utils.h" -#include -#include - -#include -#include - using namespace KScreen; -namespace Wl = KWayland::Client; - -const QMap -s_rotationMap = { - {Wl::OutputDevice::Transform::Normal, Output::None}, - {Wl::OutputDevice::Transform::Rotated90, Output::Right}, - {Wl::OutputDevice::Transform::Rotated180, Output::Inverted}, - {Wl::OutputDevice::Transform::Rotated270, Output::Left}, - {Wl::OutputDevice::Transform::Flipped, Output::None}, - {Wl::OutputDevice::Transform::Flipped90, Output::Right}, - {Wl::OutputDevice::Transform::Flipped180, Output::Inverted}, - {Wl::OutputDevice::Transform::Flipped270, Output::Left} -}; - -Output::Rotation toKScreenRotation(const Wl::OutputDevice::Transform transform) -{ - auto it = s_rotationMap.constFind(transform); - return it.value(); -} -Wl::OutputDevice::Transform toKWaylandTransform(const Output::Rotation rotation) -{ - return s_rotationMap.key(rotation); -} - -WaylandOutput::WaylandOutput(quint32 id, WaylandConfig *parent) +WaylandOutput::WaylandOutput(quint32 id, QObject *parent) : QObject(parent) , m_id(id) - , m_device(nullptr) { } quint32 WaylandOutput::id() const { - Q_ASSERT(m_device); return m_id; } -bool WaylandOutput::enabled() const -{ - return m_device != nullptr; -} - -Wl::OutputDevice* WaylandOutput::outputDevice() const -{ - return m_device; -} - -void WaylandOutput::createOutputDevice(Wl::Registry *registry, quint32 name, quint32 version) -{ - Q_ASSERT(!m_device); - m_device = registry->createOutputDevice(name, version); - - connect(m_device, &Wl::OutputDevice::removed, this, &WaylandOutput::deviceRemoved); - connect(m_device, &Wl::OutputDevice::done, this, [this]() { - Q_EMIT complete(); - connect(m_device, &Wl::OutputDevice::changed, this, &WaylandOutput::changed); - }); -} - OutputPtr WaylandOutput::toKScreenOutput() { OutputPtr output(new Output()); output->setId(m_id); updateKScreenOutput(output); return output; } -void WaylandOutput::updateKScreenOutput(OutputPtr &output) -{ - // Initialize primary output - output->setId(m_id); - output->setEnabled(m_device->enabled() == Wl::OutputDevice::Enablement::Enabled); - output->setConnected(true); - output->setPrimary(true); // FIXME: wayland doesn't have the concept of a primary display - output->setName(name()); - output->setSizeMm(m_device->physicalSize()); - output->setPos(m_device->globalPosition()); - output->setRotation(s_rotationMap[m_device->transform()]); - - ModeList modeList; - QStringList preferredModeIds; - m_modeIdMap.clear(); - QString currentModeId = QStringLiteral("-1"); - - for (const Wl::OutputDevice::Mode &wlMode : m_device->modes()) { - ModePtr mode(new Mode()); - const QString name = modeName(wlMode); - - QString modeId = QString::number(wlMode.id); - if (modeId.isEmpty()) { - qCDebug(KSCREEN_WAYLAND) << "Could not create mode id from" - << wlMode.id << ", using" << name << "instead."; - modeId = name; - } - - if (m_modeIdMap.contains(modeId)) { - qCWarning(KSCREEN_WAYLAND) << "Mode id already in use:" << modeId; - } - mode->setId(modeId); - - // KWayland gives the refresh rate as int in mHz - mode->setRefreshRate(wlMode.refreshRate / 1000.0); - mode->setSize(wlMode.size); - mode->setName(name); - - if (wlMode.flags.testFlag(Wl::OutputDevice::Mode::Flag::Current)) { - currentModeId = modeId; - } - if (wlMode.flags.testFlag(Wl::OutputDevice::Mode::Flag::Preferred)) { - preferredModeIds << modeId; - } - - // Update the kscreen => kwayland mode id translation map - m_modeIdMap.insert(modeId, wlMode.id); - // Add to the modelist which gets set on the output - modeList[modeId] = mode; - } - - if (currentModeId == QLatin1String("-1")) { - qCWarning(KSCREEN_WAYLAND) << "Could not find the current mode id" << modeList; - } - - output->setCurrentModeId(currentModeId); - output->setPreferredModes(preferredModeIds); - output->setModes(modeList); - output->setScale(m_device->scaleF()); - output->setType(Utils::guessOutputType(m_device->model(), m_device->model())); -} - -bool WaylandOutput::setWlConfig(Wl::OutputConfiguration *wlConfig, - const KScreen::OutputPtr &output) -{ - bool changed = false; - - // enabled? - if ((m_device->enabled() == Wl::OutputDevice::Enablement::Enabled) - != output->isEnabled()) { - changed = true; - const auto enablement = output->isEnabled() ? Wl::OutputDevice::Enablement::Enabled : - Wl::OutputDevice::Enablement::Disabled; - wlConfig->setEnabled(m_device, enablement); - } - - // position - if (m_device->globalPosition() != output->pos()) { - changed = true; - wlConfig->setPosition(m_device, output->pos()); - } - - // scale - if (!qFuzzyCompare(m_device->scaleF(), output->scale())) { - changed = true; - wlConfig->setScaleF(m_device, output->scale()); - } - - // rotation - if (toKScreenRotation(m_device->transform()) != output->rotation()) { - changed = true; - wlConfig->setTransform(m_device, toKWaylandTransform(output->rotation())); - } - - // mode - if (m_modeIdMap.contains(output->currentModeId())) { - const int newModeId = m_modeIdMap.value(output->currentModeId(), -1); - if (newModeId != m_device->currentMode().id) { - changed = true; - wlConfig->setMode(m_device, newModeId); - } - } else { - qCWarning(KSCREEN_WAYLAND) << "Invalid kscreen mode id:" << output->currentModeId() - << "\n\n" << m_modeIdMap; - } - return changed; -} - -QString WaylandOutput::modeName(const Wl::OutputDevice::Mode &m) const -{ - return QString::number(m.size.width()) + QLatin1Char('x') + - QString::number(m.size.height()) + QLatin1Char('@') + - QString::number(qRound(m.refreshRate/1000.0)); -} - -QString WaylandOutput::name() const -{ - Q_ASSERT(m_device); - return QStringLiteral("%1 %2").arg(m_device->manufacturer(), m_device->model()); -} - -QDebug operator<<(QDebug dbg, const WaylandOutput *output) +KScreen::Output::Type WaylandOutput::guessType(const QString &type, const QString &name) const { - dbg << "WaylandOutput(Id:" << output->id() <<", Name:" << \ - QString(output->outputDevice()->manufacturer() + QLatin1Char(' ') + \ - output->outputDevice()->model()) << ")"; - return dbg; + return Utils::guessOutputType(type, name); } diff --git a/backends/kwayland/waylandscreen.cpp b/backends/kwayland/waylandscreen.cpp --- a/backends/kwayland/waylandscreen.cpp +++ b/backends/kwayland/waylandscreen.cpp @@ -22,6 +22,8 @@ #include +#include + using namespace KScreen; WaylandScreen::WaylandScreen(WaylandConfig *config) @@ -46,8 +48,7 @@ QRect r; for (const auto *out : outputs) { if (out->enabled()) { - const auto *dev = out->outputDevice(); - r |= QRect(dev->globalPosition(), dev->pixelSize() / dev->scale()); + r |= out->geometry().toRect(); } } m_size = r.size();