Introduce infrastructure for compositor extensions

The scripting api is not suitable for implementing all features that
should not be implemented in libkwin. For example, the krunner
integration or screencasting are the things that don't belong to be
compiled right into kwin and yet we don't have any other choice.

This change introduces a quick and dirty plugin infrastructure that
can be used to implement things such as colord integration, krunner
integration, etc.
This commit is contained in:
Vlad Zahorodnii 2020-11-07 21:34:55 +02:00
parent a482d73de2
commit c766e5da6d
25 changed files with 516 additions and 33 deletions

View file

@ -1,8 +1,7 @@
cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
project(KWin)
set(PROJECT_VERSION "5.20.80")
set(PROJECT_VERSION_MAJOR 5)
set(PROJECT_VERSION "5.20.80") # Handled by release scripts
project(KWin VERSION ${PROJECT_VERSION})
set(QT_MIN_VERSION "5.15.0")
set(KF5_MIN_VERSION "5.74")
@ -506,6 +505,8 @@ set(kwin_SRCS
overlaywindow.cpp
placement.cpp
platform.cpp
plugin.cpp
pluginmanager.cpp
pointer_input.cpp
popup_input_filter.cpp
rootinfo_filter.cpp
@ -633,6 +634,7 @@ qt5_add_dbus_adaptor(kwin_SRCS org.kde.kwin.ColorCorrect.xml colorcorrection/col
qt5_add_dbus_adaptor(kwin_SRCS ${kwin_effects_dbus_xml} effects.h KWin::EffectsHandlerImpl)
qt5_add_dbus_adaptor(kwin_SRCS org.kde.KWin.VirtualDesktopManager.xml dbusinterface.h KWin::VirtualDesktopManagerDBusInterface)
qt5_add_dbus_adaptor(kwin_SRCS org.kde.KWin.Session.xml sm.h KWin::SessionManager)
qt5_add_dbus_adaptor(kwin_SRCS org.kde.KWin.Plugins.xml dbusinterface.h KWin::PluginManagerDBusInterface)
if (KWIN_BUILD_RUNNERS)
qt5_add_dbus_adaptor(kwin_SRCS "runners/org.kde.krunner1.xml" runners/windowsrunnerinterface.h KWin::WindowsRunner)
endif()
@ -781,30 +783,8 @@ set(kwin_WAYLAND_SRCS
main_wayland.cpp
tabletmodemanager.cpp
)
ecm_qt_declare_logging_category(kwin_WAYLAND_SRCS
HEADER
kwinscreencast_logging.h
IDENTIFIER
KWIN_SCREENCAST
CATEGORY_NAME
kwin_screencast
DEFAULT_SEVERITY
Warning
)
add_executable(kwin_wayland ${kwin_WAYLAND_SRCS} ${kwin_XWAYLAND_SRCS})
if (PipeWire_FOUND)
target_sources(kwin_wayland
PRIVATE
screencast/eglnativefence.cpp
screencast/screencastmanager.cpp
screencast/pipewirecore.cpp
screencast/pipewirestream.cpp)
target_link_libraries(kwin_wayland PkgConfig::PipeWire)
endif()
target_link_libraries(kwin_wayland
kwin
KF5::Crash
@ -834,6 +814,10 @@ target_link_libraries(kwin_wayland
KF5IdleTimeKWinPlugin
)
if (PipeWire_FOUND)
target_link_libraries(kwin_wayland KWinScreencastPlugin)
endif()
########### install files ###############
install(FILES kwin.kcfg DESTINATION ${KCFG_INSTALL_DIR} RENAME ${KWIN_NAME}.kcfg)
@ -846,6 +830,7 @@ install(
org.kde.kwin.ColorCorrect.xml
org.kde.kwin.Compositing.xml
org.kde.kwin.Effects.xml
org.kde.KWin.Plugins.xml
DESTINATION
${KDE_INSTALL_DBUSINTERFACEDIR}
)

View file

@ -8,6 +8,7 @@
*/
#include "kwin_wayland_test.h"
#include "../../platform.h"
#include "../../pluginmanager.h"
#include "../../composite.h"
#include "../../effects.h"
#include "../../wayland_server.h"
@ -130,6 +131,7 @@ void WaylandTestApplication::performStartup()
// try creating the Wayland Backend
createInput();
createBackend();
PluginManager::create(this);
}
void WaylandTestApplication::createBackend()

View file

@ -1,3 +1,7 @@
#define KWIN_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}
#define KWIN_VERSION_MINOR ${PROJECT_VERSION_MINOR}
#define KWIN_VERSION_PATCH ${PROJECT_VERSION_PATCH}
#cmakedefine KWIN_BUILD_DECORATIONS 1
#cmakedefine KWIN_BUILD_TABBOX 1
#cmakedefine KWIN_BUILD_ACTIVITIES 1

View file

@ -10,6 +10,7 @@
// own
#include "dbusinterface.h"
#include "compositingadaptor.h"
#include "pluginsadaptor.h"
#include "virtualdesktopmanageradaptor.h"
// kwin
@ -20,6 +21,7 @@
#include "main.h"
#include "placement.h"
#include "platform.h"
#include "pluginmanager.h"
#include "kwinadaptor.h"
#include "scene.h"
#include "unmanaged.h"
@ -515,4 +517,35 @@ void VirtualDesktopManagerDBusInterface::removeDesktop(const QString &id)
m_manager->removeVirtualDesktop(id.toUtf8());
}
PluginManagerDBusInterface::PluginManagerDBusInterface(PluginManager *manager)
: QObject(manager)
, m_manager(manager)
{
new PluginsAdaptor(this);
QDBusConnection::sessionBus().registerObject(QStringLiteral("/Plugins"),
QStringLiteral("org.kde.KWin.Plugins"),
this);
}
QStringList PluginManagerDBusInterface::loadedPlugins() const
{
return m_manager->loadedPlugins();
}
QStringList PluginManagerDBusInterface::availablePlugins() const
{
return m_manager->availablePlugins();
}
bool PluginManagerDBusInterface::LoadPlugin(const QString &name)
{
return m_manager->loadPlugin(name);
}
void PluginManagerDBusInterface::UnloadPlugin(const QString &name)
{
m_manager->unloadPlugin(name);
}
} // namespace

View file

@ -19,6 +19,7 @@ namespace KWin
{
class Compositor;
class PluginManager;
class VirtualDesktopManager;
/**
@ -238,6 +239,28 @@ private:
VirtualDesktopManager *m_manager;
};
class PluginManagerDBusInterface : public QObject
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.KWin.Plugins")
Q_PROPERTY(QStringList LoadedPlugins READ loadedPlugins)
Q_PROPERTY(QStringList AvailablePlugins READ availablePlugins)
public:
explicit PluginManagerDBusInterface(PluginManager *manager);
QStringList loadedPlugins() const;
QStringList availablePlugins() const;
public Q_SLOTS:
bool LoadPlugin(const QString &name);
void UnloadPlugin(const QString &name);
private:
PluginManager *m_manager;
};
} // namespace
#endif // KWIN_DBUS_INTERFACE_H

View file

@ -14,11 +14,9 @@
// kwin
#include "platform.h"
#include "effects.h"
#include "pluginmanager.h"
#include "tabletmodemanager.h"
#ifdef PipeWire_FOUND
#include "screencast/screencastmanager.h"
#endif
#include "wayland_server.h"
#include "xwl/xwayland.h"
@ -64,6 +62,9 @@ Q_IMPORT_PLUGIN(KWinIntegrationPlugin)
Q_IMPORT_PLUGIN(KGlobalAccelImpl)
Q_IMPORT_PLUGIN(KWindowSystemKWinPlugin)
Q_IMPORT_PLUGIN(KWinIdleTimePoller)
#ifdef PipeWire_FOUND
Q_IMPORT_PLUGIN(ScreencastManagerFactory)
#endif
namespace KWin
{
@ -157,9 +158,7 @@ void ApplicationWayland::performStartup()
InputMethod::create(this);
createBackend();
TabletModeManager::create(this);
#ifdef PipeWire_FOUND
new ScreencastManager(this);
#endif
PluginManager::create(this);
}
void ApplicationWayland::createBackend()

View file

@ -13,6 +13,7 @@
#include <config-kwin.h>
#include "platform.h"
#include "pluginmanager.h"
#include "sm.h"
#include "workspace.h"
#include "xcbutils.h"
@ -248,6 +249,7 @@ void ApplicationX11::performStartup()
connect(platform(), &Platform::screensQueried, this,
[this] {
createWorkspace();
PluginManager::create(this);
Xcb::sync(); // Trigger possible errors, there's still a chance to abort

32
org.kde.KWin.Plugins.xml Normal file
View file

@ -0,0 +1,32 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.KWin.Plugins">
<!--
The list of all currently loaded plugins.
-->
<property name="LoadedPlugins" type="as" access="read"/>
<!--
The list of all available plugins.
-->
<property name="AvailablePlugins" type="as" access="read"/>
<!--
Loads a plugin with the specified @a name.
If the plugin has been loaded successfully, @c true will be returned;
otherwise @c false is returned.
-->
<method name="LoadPlugin">
<arg type="b" direction="out"/>
<arg name="name" type="s" direction="in"/>
</method>
<!--
Unloads a plugin with the specified @a name.
-->
<method name="UnloadPlugin">
<arg name="name" type="s" direction="in"/>
</method>
</interface>
</node>

22
plugin.cpp Normal file
View file

@ -0,0 +1,22 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "plugin.h"
namespace KWin
{
Plugin::Plugin(QObject *parent)
: QObject(parent)
{
}
PluginFactory::PluginFactory(QObject *parent)
: QObject(parent)
{
}
} // namespace KWin

52
plugin.h Normal file
View file

@ -0,0 +1,52 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <config-kwin.h>
#include "kwinglobals.h"
#include <QObject>
namespace KWin
{
#define KWIN_PLUGIN_API_VERSION QT_VERSION_CHECK(KWIN_VERSION_MAJOR, \
KWIN_VERSION_MINOR, \
KWIN_VERSION_PATCH)
#define PluginFactory_iid "org.kde.kwin.PluginFactoryInterface"
/**
* The Plugin class is the baseclass for all binary compositor extensions.
*
* Note that a binary extension must be recompiled with every new KWin release.
*/
class KWIN_EXPORT Plugin : public QObject
{
Q_OBJECT
public:
explicit Plugin(QObject *parent = nullptr);
};
/**
* The PluginFactory class creates binary compositor extensions.
*/
class KWIN_EXPORT PluginFactory : public QObject
{
Q_OBJECT
public:
explicit PluginFactory(QObject *parent = nullptr);
virtual Plugin *create() const = 0;
};
} // namespace KWin
Q_DECLARE_INTERFACE(KWin::PluginFactory, PluginFactory_iid)

188
pluginmanager.cpp Normal file
View file

@ -0,0 +1,188 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "pluginmanager.h"
#include "dbusinterface.h"
#include "main.h"
#include "plugin.h"
#include "utils.h"
#include <KConfigGroup>
#include <KPluginFactory>
#include <KPluginLoader>
#include <KPluginMetaData>
namespace KWin
{
KWIN_SINGLETON_FACTORY(PluginManager)
static const QString s_pluginDirectory = QStringLiteral("kwin/plugins");
static QJsonValue readPluginInfo(const QJsonObject &metadata, const QString &key)
{
return metadata.value(QLatin1String("KPlugin")).toObject().value(key);
}
PluginManager::PluginManager(QObject *parent)
: QObject(parent)
{
const KConfigGroup config(kwinApp()->config(), QStringLiteral("Plugins"));
auto checkEnabled = [&config](const QString &pluginId, const QJsonObject &metadata) {
const QString configKey = pluginId + QLatin1String("Enabled");
if (config.hasKey(configKey)) {
return config.readEntry(configKey, false);
}
return readPluginInfo(metadata, QStringLiteral("EnabledByDefault")).toBool(false);
};
const QVector<QStaticPlugin> staticPlugins = QPluginLoader::staticPlugins();
for (const QStaticPlugin &staticPlugin : staticPlugins) {
const QJsonObject rootMetaData = staticPlugin.metaData();
if (rootMetaData.value(QLatin1String("IID")) != QLatin1String(PluginFactory_iid)) {
continue;
}
const QJsonObject pluginMetaData = rootMetaData.value(QLatin1String("MetaData")).toObject();
const QString pluginId = readPluginInfo(pluginMetaData, QStringLiteral("Id")).toString();
if (pluginId.isEmpty()) {
continue;
}
if (m_staticPlugins.contains(pluginId)) {
qCWarning(KWIN_CORE) << "Conflicting plugin id" << pluginId;
continue;
}
m_staticPlugins.insert(pluginId, staticPlugin);
if (checkEnabled(pluginId, pluginMetaData)) {
loadStaticPlugin(pluginId);
}
}
const QVector<KPluginMetaData> plugins = KPluginLoader::findPlugins(s_pluginDirectory);
for (const KPluginMetaData &metadata : plugins) {
if (m_plugins.contains(metadata.pluginId())) {
qCWarning(KWIN_CORE) << "Conflicting plugin id" << metadata.pluginId();
continue;
}
if (checkEnabled(metadata.pluginId(), metadata.rawData())) {
loadDynamicPlugin(metadata);
}
}
new PluginManagerDBusInterface(this);
}
PluginManager::~PluginManager()
{
s_self = nullptr;
}
QStringList PluginManager::loadedPlugins() const
{
return m_plugins.keys();
}
QStringList PluginManager::availablePlugins() const
{
QStringList ret = m_staticPlugins.keys();
const QVector<KPluginMetaData> plugins = KPluginLoader::findPlugins(s_pluginDirectory);
for (const KPluginMetaData &metadata : plugins) {
ret.append(metadata.pluginId());
}
return ret;
}
bool PluginManager::loadPlugin(const QString &pluginId)
{
if (m_plugins.contains(pluginId)) {
qCDebug(KWIN_CORE) << "Plugin with id" << pluginId << "is already loaded";
return false;
}
return loadStaticPlugin(pluginId) || loadDynamicPlugin(pluginId);
}
bool PluginManager::loadStaticPlugin(const QString &pluginId)
{
auto staticIt = m_staticPlugins.find(pluginId);
if (staticIt == m_staticPlugins.end()) {
return false;
}
QScopedPointer<PluginFactory> factory(qobject_cast<PluginFactory *>(staticIt->instance()));
if (!factory) {
qCWarning(KWIN_CORE) << "Failed to get plugin factory for" << pluginId;
return false;
}
return instantiatePlugin(pluginId, factory.data());
}
bool PluginManager::loadDynamicPlugin(const QString &pluginId)
{
const auto offers = KPluginLoader::findPluginsById(s_pluginDirectory, pluginId);
for (const KPluginMetaData &metadata : offers) {
if (loadDynamicPlugin(metadata)) {
return true;
}
}
return false;
}
bool PluginManager::loadDynamicPlugin(const KPluginMetaData &metadata)
{
if (!metadata.isValid()) {
qCDebug(KWIN_CORE) << "PluginManager::loadPlugin needs a valid plugin metadata";
return false;
}
const QString pluginId = metadata.pluginId();
KPluginLoader pluginLoader(metadata.fileName());
if (pluginLoader.pluginVersion() != KWIN_PLUGIN_API_VERSION) {
qCWarning(KWIN_CORE) << pluginId << "has mismatching plugin version";
return false;
}
QScopedPointer<PluginFactory> factory(qobject_cast<PluginFactory *>(pluginLoader.instance()));
if (!factory) {
qCWarning(KWIN_CORE) << "Failed to get plugin factory for" << pluginId;
return false;
}
return instantiatePlugin(pluginId, factory.data());
}
bool PluginManager::instantiatePlugin(const QString &pluginId, PluginFactory *factory)
{
Plugin *plugin = factory->create();
if (!plugin) {
return false;
}
m_plugins.insert(pluginId, plugin);
plugin->setParent(this);
connect(plugin, &QObject::destroyed, this, [this, pluginId]() {
m_plugins.remove(pluginId);
});
return true;
}
void PluginManager::unloadPlugin(const QString &pluginId)
{
Plugin *plugin = m_plugins.take(pluginId);
if (!plugin) {
qCWarning(KWIN_CORE) << "No plugin with the specified id:" << pluginId;
}
delete plugin;
}
} // namespace KWin

51
pluginmanager.h Normal file
View file

@ -0,0 +1,51 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "kwinglobals.h"
#include <QHash>
#include <QObject>
#include <QStaticPlugin>
#include <KPluginMetaData>
namespace KWin
{
class Plugin;
class PluginFactory;
/**
* The PluginManager class loads and unloads binary compositor extensions.
*/
class KWIN_EXPORT PluginManager : public QObject
{
Q_OBJECT
public:
~PluginManager() override;
QStringList loadedPlugins() const;
QStringList availablePlugins() const;
public Q_SLOTS:
bool loadPlugin(const QString &pluginId);
void unloadPlugin(const QString &pluginId);
private:
bool loadStaticPlugin(const QString &pluginId);
bool loadDynamicPlugin(const KPluginMetaData &metadata);
bool loadDynamicPlugin(const QString &pluginId);
bool instantiatePlugin(const QString &pluginId, PluginFactory *factory);
QHash<QString, Plugin *> m_plugins;
QHash<QString, QStaticPlugin> m_staticPlugins;
KWIN_SINGLETON(PluginManager)
};
} // namespace KWin

View file

@ -9,3 +9,6 @@ add_subdirectory(kpackage)
if (KWIN_BUILD_DECORATIONS)
add_subdirectory(kdecorations)
endif()
if (PipeWire_FOUND)
add_subdirectory(screencast)
endif()

View file

@ -0,0 +1,18 @@
set(screencast_SOURCES
eglnativefence.cpp
main.cpp
pipewirecore.cpp
pipewirestream.cpp
screencastmanager.cpp
)
ecm_qt_declare_logging_category(screencast_SOURCES
HEADER kwinscreencast_logging.h
IDENTIFIER KWIN_SCREENCAST
CATEGORY_NAME kwin_screencast
DEFAULT_SEVERITY Warning
)
add_library(KWinScreencastPlugin OBJECT ${screencast_SOURCES})
target_compile_definitions(KWinScreencastPlugin PRIVATE QT_STATICPLUGIN)
target_link_libraries(KWinScreencastPlugin kwin PkgConfig::PipeWire)

View file

@ -0,0 +1,46 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "screencastmanager.h"
#include "main.h"
#include <KPluginFactory>
using namespace KWin;
class KWIN_EXPORT ScreencastManagerFactory : public PluginFactory
{
Q_OBJECT
Q_PLUGIN_METADATA(IID PluginFactory_iid FILE "metadata.json")
Q_INTERFACES(KWin::PluginFactory)
public:
explicit ScreencastManagerFactory(QObject *parent = nullptr);
Plugin *create() const override;
};
ScreencastManagerFactory::ScreencastManagerFactory(QObject *parent)
: PluginFactory(parent)
{
}
Plugin *ScreencastManagerFactory::create() const
{
switch (kwinApp()->operationMode()) {
case Application::OperationModeX11:
return nullptr;
case Application::OperationModeXwayland:
case Application::OperationModeWaylandOnly:
return new ScreencastManager();
default:
return nullptr;
}
}
K_EXPORT_PLUGIN_VERSION(KWIN_PLUGIN_API_VERSION)
#include "main.moc"

View file

@ -0,0 +1,6 @@
{
"KPlugin": {
"EnabledByDefault": true,
"Id": "kwin5_plugin_screencast"
}
}

View file

@ -28,7 +28,7 @@ namespace KWin
{
ScreencastManager::ScreencastManager(QObject *parent)
: QObject(parent)
: Plugin(parent)
, m_screencast(waylandServer()->display()->createScreencastV1Interface(this))
{
connect(m_screencast, &KWaylandServer::ScreencastV1Interface::windowScreencastRequested,

View file

@ -8,6 +8,8 @@
#pragma once
#include "plugin.h"
#include <KWaylandServer/screencast_v1_interface.h>
namespace KWin
@ -15,7 +17,7 @@ namespace KWin
class PipeWireStream;
class ScreencastManager : public QObject
class ScreencastManager : public Plugin
{
Q_OBJECT

View file

@ -33,6 +33,7 @@
#include "netinfo.h"
#include "outline.h"
#include "placement.h"
#include "pluginmanager.h"
#include "rules.h"
#include "screenedge.h"
#include "screens.h"
@ -1669,6 +1670,20 @@ QString Workspace::supportInformation() const
support.append(static_cast<EffectsHandlerImpl*>(effects)->supportInformation(effect));
support.append(QStringLiteral("\n"));
}
support.append(QLatin1String("\nLoaded Plugins:\n"));
support.append(QLatin1String("---------------\n"));
QStringList loadedPlugins = PluginManager::self()->loadedPlugins();
loadedPlugins.sort();
for (const QString &plugin : qAsConst(loadedPlugins)) {
support.append(plugin + QLatin1Char('\n'));
}
support.append(QLatin1String("\nAvailable Plugins:\n"));
support.append(QLatin1String("------------------\n"));
QStringList availablePlugins = PluginManager::self()->availablePlugins();
availablePlugins.sort();
for (const QString &plugin : qAsConst(availablePlugins)) {
support.append(plugin + QLatin1Char('\n'));
}
} else {
support.append(QStringLiteral("Compositing is not active\n"));
}