Commit a127049b authored by Dan Vrátil's avatar Dan Vrátil

Introduce out-of-process KScreen backends

Until now KScreen was loading the backend plugin into each KScreen-enabled
application. This patch changes the design completely: The KScreen library
now requests a backend (which is represented by a DBus interface) from
BackendManager (a replacement for BackendLoader). BackendManager will then
start (if not running) kscreen_backend_launcher process (BackendLauncher),
that will detect the correct plugin for the current platform, load it, and
will create a DBus service called org.kde.KScreen.Backend.%backendName%
with org.kde.KScreen.Backend interface. The library will now communicate
with the backend through this DBus interface.

The out-of-process design solves many issues, including blocking calls in
main thread, better performance (backend aggregates notifications before
emitting them to clients, reducing number of "changes" clients have to handle,
and better stability (backend crash does not crash the application).
parent b1cef7fd
......@@ -15,7 +15,7 @@ include(FeatureSummary)
include(CheckCXXCompilerFlag)
set(REQUIRED_QT_VERSION 5.2.0)
find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Core Gui Test X11Extras)
find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Core DBus Gui Test X11Extras)
find_package(XCB COMPONENTS XCB RANDR)
set_package_properties(XCB PROPERTIES
......
include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${QT_INCLUDES})
add_subdirectory(backendlauncher)
set(libkscreen_SRCS
abstractbackend.cpp
backendloader.cpp
backendmanager.cpp
config.cpp
configmonitor.cpp
configserializer.cpp
screen.cpp
output.cpp
edid.cpp
......@@ -12,9 +15,17 @@ set(libkscreen_SRCS
debug_p.cpp
)
qt5_add_dbus_interface(libkscreen_SRCS ${CMAKE_SOURCE_DIR}/interfaces/org.kde.KScreen.Backend.xml backendinterface)
add_library(KF5Screen SHARED ${libkscreen_SRCS})
target_link_libraries(KF5Screen PUBLIC Qt5::Core PRIVATE Qt5::X11Extras)
target_link_libraries(KF5Screen
PUBLIC
Qt5::Core
PRIVATE
Qt5::DBus
Qt5::X11Extras
)
set_target_properties(KF5Screen PROPERTIES
VERSION "${KSCREEN_VERSION_STRING}"
......
include_directories(${CMAKE_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}/..
${CMAKE_CURRENT_SOURCE_DIR}/..
)
set(backendlauncher_SRCS
main.cpp
backendloader.cpp
backenddbuswrapper.cpp
)
add_executable(kscreen_backend_launcher ${backendlauncher_SRCS})
target_link_libraries(kscreen_backend_launcher
KF5Screen
Qt5::Core
Qt5::X11Extras
Qt5::DBus
)
install(TARGETS kscreen_backend_launcher DESTINATION ${CMAKE_INSTALL_FULL_LIBEXECDIR_KF5})
/*
* Copyright (C) 2014 Daniel Vratil <[email protected]>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "backenddbuswrapper.h"
#include "backendloader.h"
#include "src/configserializer_p.h"
#include "src/config.h"
#include "src/abstractbackend.h"
#include <QDBusConnection>
#include <QDBusError>
BackendDBusWrapper::BackendDBusWrapper(KScreen::AbstractBackend* backend)
: QObject()
, mBackend(backend)
{
connect(mBackend, &KScreen::AbstractBackend::configChanged,
this, &BackendDBusWrapper::backendConfigChanged);
mChangeCollector.setSingleShot(true);
mChangeCollector.setInterval(500); // wait for 500 msecs without any change
// before actually emitting configChanged
connect(&mChangeCollector, &QTimer::timeout,
this, &BackendDBusWrapper::doEmitConfigChanged);
}
BackendDBusWrapper::~BackendDBusWrapper()
{
}
bool BackendDBusWrapper::init()
{
QDBusConnection dbus = QDBusConnection::sessionBus();
if (!dbus.registerService(mBackend->serviceName())) {
qCWarning(KSCREEN_BACKEND_LAUNCHER) << "Failed to register as DBus service: another launcher already running?";
qCWarning(KSCREEN_BACKEND_LAUNCHER) << dbus.lastError().message();
return false;
}
if (!dbus.registerObject(QLatin1String("/"), this, QDBusConnection::ExportAllSlots | QDBusConnection::ExportAllSignals)) {
qCWarning(KSCREEN_BACKEND_LAUNCHER) << "Failed to export backend to DBus: another launcher already running?";
qCWarning(KSCREEN_BACKEND_LAUNCHER) << dbus.lastError().message();
return false;
}
return true;
}
QVariantMap BackendDBusWrapper::getConfig() const
{
const KScreen::ConfigPtr config = mBackend->config();
Q_ASSERT(!config.isNull());
if (!config) {
qCWarning(KSCREEN_BACKEND_LAUNCHER) << "Backend provided an empty config!";
return QVariantMap();
}
const QJsonObject obj = KScreen::ConfigSerializer::serializeConfig(mBackend->config());
Q_ASSERT(!obj.isEmpty());
return obj.toVariantMap();
}
QVariantMap BackendDBusWrapper::setConfig(const QVariantMap &configMap)
{
if (configMap.isEmpty()) {
qCWarning(KSCREEN_BACKEND_LAUNCHER) << "Received an empty config map";
return QVariantMap();
}
const QJsonObject obj = QJsonObject::fromVariantMap(configMap);
const KScreen::ConfigPtr config = KScreen::ConfigSerializer::deserializeConfig(obj);
mBackend->setConfig(config);
// TODO: setConfig should return adjusted config that we should use
return getConfig();
}
QByteArray BackendDBusWrapper::edid(int output) const
{
const QByteArray edidData = mBackend->edid(output);
if (edidData.isEmpty()) {
return QByteArray();
}
return edidData;
}
void BackendDBusWrapper::backendConfigChanged(const KScreen::ConfigPtr &config)
{
Q_ASSERT(!config.isNull());
if (!config) {
qCWarning(KSCREEN_BACKEND_LAUNCHER) << "Backend provided an empty config!";
return;
}
mCurrentConfig = config;
mChangeCollector.start();
}
void BackendDBusWrapper::doEmitConfigChanged()
{
Q_ASSERT(!mCurrentConfig.isNull());
if (mCurrentConfig.isNull()) {
return;
}
const QJsonObject obj = KScreen::ConfigSerializer::serializeConfig(mCurrentConfig);
Q_EMIT configChanged(obj.toVariantMap());
mCurrentConfig.clear();
}
/*
* Copyright (C) 2014 Daniel Vratil <[email protected]>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef BACKENDDBUSWRAPPER_H
#define BACKENDDBUSWRAPPER_H
#include <QObject>
#include <QTimer>
#include "src/types.h"
namespace KScreen
{
class AbstractBackend;
}
class BackendDBusWrapper : public QObject
{
Q_OBJECT
public:
explicit BackendDBusWrapper(KScreen::AbstractBackend *backend);
virtual ~BackendDBusWrapper();
bool init();
Q_INVOKABLE QVariantMap getConfig() const;
Q_INVOKABLE QVariantMap setConfig(const QVariantMap &config);
Q_INVOKABLE QByteArray edid(int output) const;
Q_SIGNALS:
void configChanged(const QVariantMap &config);
private Q_SLOTS:
void backendConfigChanged(const KScreen::ConfigPtr &config);
void doEmitConfigChanged();
private:
KScreen::AbstractBackend *mBackend;
QTimer mChangeCollector;
KScreen::ConfigPtr mCurrentConfig;
};
#endif // BACKENDDBUSWRAPPER_H
/*
* Copyright (C) 2014 Daniel Vratil <[email protected]>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "backendloader.h"
#include "src/abstractbackend.h"
#include <QCoreApplication>
#include <QDir>
#include <QPluginLoader>
#include <QX11Info>
#include <QDBusConnection>
#include <QDBusInterface>
Q_LOGGING_CATEGORY(KSCREEN_BACKEND_LAUNCHER, "org.kde.KScreen.BackendLauncher")
BackendLoader::BackendLoader()
: QObject()
{
}
BackendLoader::~BackendLoader()
{
}
bool BackendLoader::loadBackend(const QString& backend)
{
qCDebug(KSCREEN_BACKEND_LAUNCHER) << "Requested backend:" << backend;
const QString backendFilter = QString::fromLatin1("KSC_%1*").arg(backend);
const QStringList paths = QCoreApplication::libraryPaths();
qCDebug(KSCREEN_BACKEND_LAUNCHER) << "Lookup paths: " << paths;
Q_FOREACH (const QString &path, paths) {
const QDir dir(path + QDir::separator() + QLatin1String("/kf5/kscreen/"),
backendFilter,
QDir::SortFlags(QDir::QDir::NoSort),
QDir::NoDotAndDotDot | QDir::Files);
const QFileInfoList finfos = dir.entryInfoList();
Q_FOREACH (const QFileInfo &finfo, finfos) {
// Skip "Fake" backend unless explicitly specified via KSCREEN_BACKEND
if (backend.isEmpty() && (finfo.fileName().contains(QLatin1String("KSC_Fake")) || finfo.fileName().contains(QLatin1String("KSC_FakeUI")))) {
continue;
}
// When on X11, skip the QScreen backend, instead use the XRandR backend,
// if not specified in KSCREEN_BACKEND
if (backend.isEmpty() &&
finfo.fileName().contains(QLatin1String("KSC_QScreen")) &&
QX11Info::isPlatformX11()) {
continue;
}
// When not on X11, skip the XRandR backend, and fall back to QSCreen
// if not specified in KSCREEN_BACKEND
if (backend.isEmpty() &&
finfo.fileName().contains(QLatin1String("KSC_XRandR")) &&
!QX11Info::isPlatformX11()) {
continue;
}
qCDebug(KSCREEN_BACKEND_LAUNCHER) << "Trying" << finfo.filePath();
QPluginLoader loader(finfo.filePath());
loader.load();
QObject *instance = loader.instance();
if (!instance) {
qCDebug(KSCREEN_BACKEND_LAUNCHER) << loader.errorString();
loader.unload();
continue;
}
mBackend = qobject_cast<KScreen::AbstractBackend*>(instance);
if (mBackend) {
if (!mBackend->isValid()) {
qCDebug(KSCREEN_BACKEND_LAUNCHER) << "Skipping" << mBackend->name() << "backend";
delete mBackend;
mBackend = 0;
loader.unload();
continue;
}
qCDebug(KSCREEN_BACKEND_LAUNCHER) << "Loading" << mBackend->name() << "backend";
return true;
} else {
qCDebug(KSCREEN_BACKEND_LAUNCHER) << finfo.fileName() << "does not provide valid KScreen backend";
}
}
}
return false;
}
KScreen::AbstractBackend* BackendLoader::backend() const
{
return mBackend;
}
bool BackendLoader::checkIsAlreadyRunning()
{
QDBusInterface *iface = new QDBusInterface(mBackend->serviceName(),
QLatin1String("/"),
QLatin1String("org.kde.KScreen.Backend"),
QDBusConnection::sessionBus(),
this);
const bool valid = iface->isValid();
delete iface;
return valid;
}
/*
* Copyright (C) 2014 Daniel Vratil <[email protected]>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef BACKENDLAUNCHER_H
#define BACKENDLAUNCHER_H
#include <QObject>
#include <QLoggingCategory>
namespace KScreen
{
class AbstractBackend;
}
class BackendLoader : public QObject
{
Q_OBJECT
public:
enum State {
BackendLoaded = 0,
BackendAlreadyExists = 1,
BackendFailedToLoad = 2
};
explicit BackendLoader();
~BackendLoader();
bool loadBackend(const QString &backendName = QString());
bool checkIsAlreadyRunning();
KScreen::AbstractBackend* backend() const;
private:
KScreen::AbstractBackend* mBackend;
};
Q_DECLARE_LOGGING_CATEGORY(KSCREEN_BACKEND_LAUNCHER)
#endif // BACKENDLAUNCHER_H
/*
* Copyright (C) 2014 Daniel Vratil <[email protected]>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <QCoreApplication>
#include <QCommandLineParser>
#include <QDebug>
#include "backendloader.h"
#include "backenddbuswrapper.h"
#include <src/abstractbackend.h>
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
BackendLoader launcher;
QCommandLineOption backendOption(QLatin1String("backend"),
QLatin1String("Backend to load. When not specified, BackendLauncher will "
"try to load the best backend for current platform."),
QLatin1String("backend"));
QCommandLineParser parser;
parser.addOption(backendOption);
parser.addHelpOption();
parser.process(app);
bool success = false;
if (parser.isSet(backendOption)) {
success = launcher.loadBackend(parser.value(backendOption));
} else {
success = launcher.loadBackend();
}
// We failed to load any backend: abort immediatelly
if (!success) {
return BackendLoader::BackendFailedToLoad;
}
// Check if another Backend Launcher with this particual backend is already running
const bool alreadyRunning = launcher.checkIsAlreadyRunning();
if (alreadyRunning) {
// If it is, let caller now it's DBus service name and terminate
printf("%s", qPrintable(launcher.backend()->serviceName()));
fflush(stdout);
return BackendLoader::BackendAlreadyExists;
}
// Create BackendDBusWrapper that takes implements the DBus interface and translates
// DBus calls to backend implementations. It will also take care of terminating this
// launcher when no other KScreen-enabled processes are running
BackendDBusWrapper backendWrapper(launcher.backend());
if (!backendWrapper.init()) {
return BackendLoader::BackendFailedToLoad;
}
// Now let caller now what's our DBus service name, so it can connect to us
printf("%s", qPrintable(launcher.backend()->serviceName()));
fflush(stdout);
// And go!
return app.exec();
}
/*
* Copyright (C) 2014 Daniel Vratil <[email protected]>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "backendmanager_p.h"
#include "backendlauncher/backendloader.h"
#include "debug_p.h"
#include <QDBusConnection>
#include <QDBusPendingCall>
#include <QDBusPendingCallWatcher>
#include <QDBusPendingReply>
#include <QDBusConnectionInterface>
#include "config-libkscreen.h"
#include <QProcess>
using namespace KScreen;
Q_DECLARE_METATYPE(org::kde::kscreen::Backend*)
BackendManager *BackendManager::sInstance = 0;
BackendManager *BackendManager::instance()
{
if (!sInstance) {
sInstance = new BackendManager();
}
return sInstance;
}
BackendManager::BackendManager()
: QObject()
, mInterface(0)
, mCrashCount(0)
{
qRegisterMetaType<org::kde::kscreen::Backend*>("OrgKdeKscreenBackendInterface");
}
BackendManager::~BackendManager()
{
}
void BackendManager::requestBackend()
{
if (mInterface) {
QMetaObject::invokeMethod(this, "backendReady", Qt::QueuedConnection,
Q_ARG(org::kde::kscreen::Backend*, mInterface));
return;
}
// If an explicit backend is requested, then start it
if (!qgetenv("KSCREEN_BACKEND").isEmpty()) {
startBackend(QString::fromLatin1(qgetenv("KSCREEN_BACKEND")));
return;
}
startBackend();
}
void BackendManager::startBackend(const QString &backend)
{
if (mLauncher && mLauncher->state() == QProcess::Running) {
mLauncher->terminate();
}
mLauncher = new QProcess(this);
connect(mLauncher, static_cast<void(QProcess::*)(int,QProcess::ExitStatus)>(&QProcess::finished),
this, &BackendManager::launcherFinished);
connect(mLauncher, &QProcess::readyReadStandardOutput,
this, &BackendManager::launcherDataAvailable);
QString launcher = QString::fromLatin1(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "/kscreen_backend_launcher");
if (!QFile::exists(launcher)) {
launcher = QStandardPaths::findExecutable("kscreen_backend_launcher");
if (launcher.isEmpty()) {