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.master
parent
a482d73de2
commit
c766e5da6d
@ -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>
|
@ -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
|
@ -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)
|
@ -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
|
@ -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
|
@ -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)
|
@ -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"
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"KPlugin": {
|
||||
"EnabledByDefault": true,
|
||||
"Id": "kwin5_plugin_screencast"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue