Commit c766e5da authored by Vlad Zahorodnii's avatar Vlad Zahorodnii

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.
parent a482d73d
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}
)
......
......@@ -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()
......
#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
......
......@@ -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
......@@ -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
......@@ -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()
......
......@@ -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
......
<!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>
/*
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
/*
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)
/*
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
/*
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
......@@ -9,3 +9,6 @@ add_subdirectory(kpackage)
if (KWIN_BUILD_DECORATIONS)
add_subdirectory(kdecorations)
endif()
if (PipeWire_FOUND)
add_subdirectory(screencast)
endif()
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)
/*
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;