From 264ebe63778b4adc35f928515efde9f9fc37ac5a Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Fri, 10 Feb 2023 21:07:18 +0200 Subject: [PATCH] scripting: Add qml effect bindings This allows creating third party qtquick scene effects without linking to libkwineffects and thus rebuilding the effect every kwin release. --- examples/quick-effect/.gitignore | 1 + examples/quick-effect/CMakeLists.txt | 14 ++ .../package/contents/config/main.xml | 12 ++ .../package/contents/config/main.xml.license | 2 + .../package/contents/ui/config.ui | 39 ++++++ .../package/contents/ui/config.ui.license | 2 + .../quick-effect/package/contents/ui/main.qml | 42 ++++++ examples/quick-effect/package/metadata.json | 20 +++ .../package/metadata.json.license | 2 + src/CMakeLists.txt | 2 + src/effectloader.cpp | 59 ++++++++ src/effectloader.h | 3 + src/libkwineffects/kwinquickeffect.cpp | 57 +++++--- src/libkwineffects/kwinquickeffect.h | 10 +- src/scripting/scriptedquicksceneeffect.cpp | 126 ++++++++++++++++++ src/scripting/scriptedquicksceneeffect.h | 93 +++++++++++++ src/scripting/scripting.cpp | 4 + 17 files changed, 472 insertions(+), 16 deletions(-) create mode 100644 examples/quick-effect/.gitignore create mode 100644 examples/quick-effect/CMakeLists.txt create mode 100644 examples/quick-effect/package/contents/config/main.xml create mode 100644 examples/quick-effect/package/contents/config/main.xml.license create mode 100644 examples/quick-effect/package/contents/ui/config.ui create mode 100644 examples/quick-effect/package/contents/ui/config.ui.license create mode 100644 examples/quick-effect/package/contents/ui/main.qml create mode 100644 examples/quick-effect/package/metadata.json create mode 100644 examples/quick-effect/package/metadata.json.license create mode 100644 src/scripting/scriptedquicksceneeffect.cpp create mode 100644 src/scripting/scriptedquicksceneeffect.h diff --git a/examples/quick-effect/.gitignore b/examples/quick-effect/.gitignore new file mode 100644 index 0000000000..378eac25d3 --- /dev/null +++ b/examples/quick-effect/.gitignore @@ -0,0 +1 @@ +build diff --git a/examples/quick-effect/CMakeLists.txt b/examples/quick-effect/CMakeLists.txt new file mode 100644 index 0000000000..27b5ea3f14 --- /dev/null +++ b/examples/quick-effect/CMakeLists.txt @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: None +# SPDX-License-Identifier: CC0-1.0 + +cmake_minimum_required(VERSION 3.16) +project(quick-effect) + +find_package(ECM 5.240 REQUIRED NO_MODULE) +set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) + +find_package(KF6 5.240 REQUIRED COMPONENTS + Package +) + +kpackage_install_package(package quick-effect effects kwin) diff --git a/examples/quick-effect/package/contents/config/main.xml b/examples/quick-effect/package/contents/config/main.xml new file mode 100644 index 0000000000..47bbc2c975 --- /dev/null +++ b/examples/quick-effect/package/contents/config/main.xml @@ -0,0 +1,12 @@ + + + + + + #ff00ff + + + diff --git a/examples/quick-effect/package/contents/config/main.xml.license b/examples/quick-effect/package/contents/config/main.xml.license new file mode 100644 index 0000000000..cf53d2bce1 --- /dev/null +++ b/examples/quick-effect/package/contents/config/main.xml.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: None +SPDX-License-Identifier: CC0-1.0 diff --git a/examples/quick-effect/package/contents/ui/config.ui b/examples/quick-effect/package/contents/ui/config.ui new file mode 100644 index 0000000000..57ed22e80c --- /dev/null +++ b/examples/quick-effect/package/contents/ui/config.ui @@ -0,0 +1,39 @@ + + + QuickEffectConfig + + + + 0 + 0 + 455 + 177 + + + + + + + Background color: + + + + + + + false + + + + + + + + KColorButton + QPushButton +
kcolorbutton.h
+
+
+ + +
diff --git a/examples/quick-effect/package/contents/ui/config.ui.license b/examples/quick-effect/package/contents/ui/config.ui.license new file mode 100644 index 0000000000..cf53d2bce1 --- /dev/null +++ b/examples/quick-effect/package/contents/ui/config.ui.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: None +SPDX-License-Identifier: CC0-1.0 diff --git a/examples/quick-effect/package/contents/ui/main.qml b/examples/quick-effect/package/contents/ui/main.qml new file mode 100644 index 0000000000..024554e921 --- /dev/null +++ b/examples/quick-effect/package/contents/ui/main.qml @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: None +// SPDX-License-Identifier: CC0-1.0 + +import QtQuick +import org.kde.kwin + +SceneEffect { + id: effect + + delegate: Rectangle { + color: effect.configuration.BackgroundColor + + Text { + anchors.centerIn: parent + text: SceneView.screen.name + } + + MouseArea { + anchors.fill: parent + onClicked: effect.visible = false + } + } + + ScreenEdgeHandler { + enabled: true + edge: ScreenEdgeHandler.TopEdge + onActivated: effect.visible = !effect.visible + } + + ShortcutHandler { + name: "Toggle Quick Effect" + text: "Toggle Quick Effect" + sequence: "Meta+Ctrl+Q" + onActivated: effect.visible = !effect.visible + } + + PinchGestureHandler { + direction: PinchGestureHandler.Direction.Contracting + fingerCount: 3 + onActivated: effect.visible = !effect.visible + } +} diff --git a/examples/quick-effect/package/metadata.json b/examples/quick-effect/package/metadata.json new file mode 100644 index 0000000000..5c6e4e8009 --- /dev/null +++ b/examples/quick-effect/package/metadata.json @@ -0,0 +1,20 @@ +{ + "KPackageStructure": "KWin/Effect", + "KPlugin": { + "Authors": [ + { + "Email": "user@example.com", + "Name": "Real Name" + } + ], + "Category": "Appearance", + "Description": "Quick Effect", + "EnabledByDefault": true, + "Id": "quick-effect", + "License": "GPL", + "Name": "Quick Effect" + }, + "X-KDE-Ordering": 60, + "X-KDE-PluginKeyword": "quick-effect", + "X-Plasma-API": "declarativescript" +} diff --git a/examples/quick-effect/package/metadata.json.license b/examples/quick-effect/package/metadata.json.license new file mode 100644 index 0000000000..cf53d2bce1 --- /dev/null +++ b/examples/quick-effect/package/metadata.json.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: None +SPDX-License-Identifier: CC0-1.0 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index eff5968219..3ec068e912 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -156,6 +156,7 @@ target_sources(kwin PRIVATE scripting/gesturehandler.cpp scripting/screenedgehandler.cpp scripting/scriptedeffect.cpp + scripting/scriptedquicksceneeffect.cpp scripting/scripting.cpp scripting/scripting_logging.cpp scripting/scriptingutils.cpp @@ -212,6 +213,7 @@ target_link_libraries(kwin PRIVATE KF6::ConfigCore + KF6::ConfigQml KF6::ConfigWidgets KF6::CoreAddons KF6::Crash diff --git a/src/effectloader.cpp b/src/effectloader.cpp index 5ee2dbd893..c14c105bb3 100644 --- a/src/effectloader.cpp +++ b/src/effectloader.cpp @@ -14,6 +14,8 @@ #include "libkwineffects/kwineffects.h" #include "plugin.h" #include "scripting/scriptedeffect.h" +#include "scripting/scriptedquicksceneeffect.h" +#include "scripting/scripting.h" #include "utils/common.h" // KDE #include @@ -23,6 +25,8 @@ #include #include #include +#include +#include #include #include #include @@ -116,11 +120,28 @@ bool ScriptedEffectLoader::loadEffect(const KPluginMetaData &effect, LoadEffectF qCDebug(KWIN_CORE) << "Loading flags disable effect: " << name; return false; } + if (m_loadedEffects.contains(name)) { qCDebug(KWIN_CORE) << name << "already loaded"; return false; } + const QString api = effect.value(QStringLiteral("X-Plasma-API")); + if (api == QLatin1String("javascript")) { + return loadJavascriptEffect(effect); + } else if (api == QLatin1String("declarativescript")) { + return loadDeclarativeEffect(effect); + } else { + qCWarning(KWIN_CORE, "Failed to load %s effect: invalid X-Plasma-API field: %s. " + "Available options are javascript, and declarativescript", qPrintable(name), qPrintable(api)); + } + + return false; +} + +bool ScriptedEffectLoader::loadJavascriptEffect(const KPluginMetaData &effect) +{ + const QString name = effect.pluginId(); if (!ScriptedEffect::supported()) { qCDebug(KWIN_CORE) << "Effect is not supported: " << name; return false; @@ -141,6 +162,44 @@ bool ScriptedEffectLoader::loadEffect(const KPluginMetaData &effect, LoadEffectF return true; } +bool ScriptedEffectLoader::loadDeclarativeEffect(const KPluginMetaData &metadata) +{ + const QString name = metadata.pluginId(); + const QString scriptFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, + QLatin1String("kwin/effects/") + name + QLatin1String("/contents/ui/main.qml")); + if (scriptFile.isNull()) { + qCWarning(KWIN_CORE) << "Could not locate the effect script"; + return false; + } + + QQmlEngine *engine = Scripting::self()->qmlEngine(); + QQmlComponent component(engine); + component.loadUrl(QUrl::fromLocalFile(scriptFile)); + if (component.isError()) { + qCWarning(KWIN_CORE).nospace() << "Failed to load " << scriptFile << ": " << component.errors(); + return false; + } + + QObject *object = component.beginCreate(engine->rootContext()); + auto effect = qobject_cast(object); + if (!effect) { + qCDebug(KWIN_CORE) << "Could not initialize scripted effect: " << name; + delete object; + return false; + } + effect->setMetaData(metadata); + component.completeCreate(); + + connect(effect, &Effect::destroyed, this, [this, name]() { + m_loadedEffects.removeAll(name); + }); + + qCDebug(KWIN_CORE) << "Successfully loaded scripted effect: " << name; + Q_EMIT effectLoaded(effect, name); + m_loadedEffects << name; + return true; +} + void ScriptedEffectLoader::queryAndLoadAll() { if (m_queryConnection) { diff --git a/src/effectloader.h b/src/effectloader.h index 1fdb802041..ddf690c894 100644 --- a/src/effectloader.h +++ b/src/effectloader.h @@ -290,6 +290,9 @@ public: private: QList findAllEffects() const; KPluginMetaData findEffect(const QString &name) const; + bool loadJavascriptEffect(const KPluginMetaData &effect); + bool loadDeclarativeEffect(const KPluginMetaData &effect); + QStringList m_loadedEffects; EffectLoadQueue *m_queue; QMetaObject::Connection m_queryConnection; diff --git a/src/libkwineffects/kwinquickeffect.cpp b/src/libkwineffects/kwinquickeffect.cpp index 389e370e94..9769d6beef 100644 --- a/src/libkwineffects/kwinquickeffect.cpp +++ b/src/libkwineffects/kwinquickeffect.cpp @@ -8,6 +8,7 @@ #include "logging_p.h" +#include #include #include #include @@ -62,8 +63,9 @@ public: } bool isItemOnScreen(QQuickItem *item, EffectScreen *screen) const; - std::unique_ptr qmlComponent; + std::unique_ptr delegate; QUrl source; + std::map> contexts; std::map> incubators; std::map> views; QPointer mouseImplicitGrab; @@ -244,7 +246,25 @@ void QuickSceneEffect::setSource(const QUrl &url) } if (d->source != url) { d->source = url; - d->qmlComponent.reset(); + d->delegate.reset(); + } +} + +QQmlComponent *QuickSceneEffect::delegate() const +{ + return d->delegate.get(); +} + +void QuickSceneEffect::setDelegate(QQmlComponent *delegate) +{ + if (isRunning()) { + qWarning() << "Cannot change QuickSceneEffect.source while running"; + return; + } + if (d->delegate.get() != delegate) { + d->source = QUrl(); + d->delegate.reset(delegate); + Q_EMIT delegateChanged(); } } @@ -393,6 +413,7 @@ void QuickSceneEffect::handleScreenRemoved(EffectScreen *screen) { d->views.erase(screen); d->incubators.erase(screen); + d->contexts.erase(screen); } void QuickSceneEffect::addScreen(EffectScreen *screen) @@ -415,14 +436,18 @@ void QuickSceneEffect::addScreen(EffectScreen *screen) view->scheduleRepaint(); d->views[screen] = std::move(view); } else if (incubator->isError()) { - qCWarning(LIBKWINEFFECTS) << "Could not create a view for QML file" << d->qmlComponent->url(); + qCWarning(LIBKWINEFFECTS) << "Could not create a view for QML file" << d->delegate->url(); qCWarning(LIBKWINEFFECTS) << incubator->errors(); } }); - incubator->setInitialProperties(properties); + + QQmlContext *creationContext = d->delegate->creationContext(); + QQmlContext *context = new QQmlContext(creationContext ? creationContext : qmlContext(this)); + + d->contexts[screen].reset(context); d->incubators[screen].reset(incubator); - d->qmlComponent->create(*incubator); + d->delegate->create(*incubator, context); } void QuickSceneEffect::startInternal() @@ -431,19 +456,20 @@ void QuickSceneEffect::startInternal() return; } - if (Q_UNLIKELY(d->source.isEmpty())) { - qWarning() << "QuickSceneEffect.source is empty. Did you forget to call setSource()?"; - return; - } + if (!d->delegate) { + if (Q_UNLIKELY(d->source.isEmpty())) { + qWarning() << "QuickSceneEffect.source is empty. Did you forget to call setSource()?"; + return; + } - if (!d->qmlComponent) { - d->qmlComponent = std::make_unique(effects->qmlEngine()); - d->qmlComponent->loadUrl(d->source); - if (d->qmlComponent->isError()) { - qWarning().nospace() << "Failed to load " << d->source << ": " << d->qmlComponent->errors(); - d->qmlComponent.reset(); + d->delegate = std::make_unique(effects->qmlEngine()); + d->delegate->loadUrl(d->source); + if (d->delegate->isError()) { + qWarning().nospace() << "Failed to load " << d->source << ": " << d->delegate->errors(); + d->delegate.reset(); return; } + Q_EMIT delegateChanged(); } effects->setActiveFullScreenEffect(this); @@ -474,6 +500,7 @@ void QuickSceneEffect::stopInternal() d->incubators.clear(); d->views.clear(); + d->contexts.clear(); d->running = false; qApp->removeEventFilter(this); effects->ungrabKeyboard(); diff --git a/src/libkwineffects/kwinquickeffect.h b/src/libkwineffects/kwinquickeffect.h index 1bdfac6761..7c70dd43db 100644 --- a/src/libkwineffects/kwinquickeffect.h +++ b/src/libkwineffects/kwinquickeffect.h @@ -9,7 +9,7 @@ #include "libkwineffects/kwineffects.h" #include "libkwineffects/kwinoffscreenquickview.h" -#include +#include namespace KWin { @@ -74,6 +74,7 @@ class KWINEFFECTS_EXPORT QuickSceneEffect : public Effect { Q_OBJECT Q_PROPERTY(QuickSceneView *activeView READ activeView NOTIFY activeViewChanged) + Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) public: explicit QuickSceneEffect(QObject *parent = nullptr); @@ -112,6 +113,12 @@ public: */ Q_INVOKABLE void activateView(QuickSceneView *view); + /** + * The delegate provides a template defining the contents of each instantiated screen view. + */ + QQmlComponent *delegate() const; + void setDelegate(QQmlComponent *delegate); + /** * Returns the source URL. */ @@ -150,6 +157,7 @@ Q_SIGNALS: void itemDraggedOutOfScreen(QQuickItem *item, QList screens); void itemDroppedOutOfScreen(const QPointF &globalPos, QQuickItem *item, EffectScreen *screen); void activeViewChanged(KWin::QuickSceneView *view); + void delegateChanged(); protected: /** diff --git a/src/scripting/scriptedquicksceneeffect.cpp b/src/scripting/scriptedquicksceneeffect.cpp new file mode 100644 index 0000000000..c83a063e12 --- /dev/null +++ b/src/scripting/scriptedquicksceneeffect.cpp @@ -0,0 +1,126 @@ +/* + SPDX-FileCopyrightText: 2023 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "scripting/scriptedquicksceneeffect.h" +#include "main.h" + +#include +#include + +#include + +namespace KWin +{ + +ScriptedQuickSceneEffect::ScriptedQuickSceneEffect() +{ + m_visibleTimer.setSingleShot(true); + connect(&m_visibleTimer, &QTimer::timeout, this, [this]() { + setRunning(false); + }); +} + +ScriptedQuickSceneEffect::~ScriptedQuickSceneEffect() +{ +} + +int ScriptedQuickSceneEffect::requestedEffectChainPosition() const +{ + return m_requestedEffectChainPosition; +} + +void ScriptedQuickSceneEffect::setMetaData(const KPluginMetaData &metaData) +{ + m_requestedEffectChainPosition = metaData.value(QStringLiteral("X-KDE-Ordering"), 50); + + KConfigGroup cg = kwinApp()->config()->group(QStringLiteral("Effect-%1").arg(metaData.pluginId())); + const QString configFilePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kwin/effects/") + metaData.pluginId() + QLatin1String("/contents/config/main.xml")); + if (configFilePath.isNull()) { + m_configLoader = new KConfigLoader(cg, nullptr, this); + } else { + QFile xmlFile(configFilePath); + m_configLoader = new KConfigLoader(cg, &xmlFile, this); + m_configLoader->load(); + } + + m_configuration = new KConfigPropertyMap(m_configLoader, this); + connect(m_configLoader, &KConfigLoader::configChanged, this, &ScriptedQuickSceneEffect::configurationChanged); +} + +bool ScriptedQuickSceneEffect::isVisible() const +{ + return m_isVisible; +} + +void ScriptedQuickSceneEffect::setVisible(bool visible) +{ + if (m_isVisible == visible) { + return; + } + m_isVisible = visible; + + if (m_isVisible) { + m_visibleTimer.stop(); + setRunning(true); + } else { + // Delay setRunning(false) to avoid destroying views while still executing JS code. + m_visibleTimer.start(); + } + + Q_EMIT visibleChanged(); +} + +KConfigPropertyMap *ScriptedQuickSceneEffect::configuration() const +{ + return m_configuration; +} + +QQmlListProperty ScriptedQuickSceneEffect::data() +{ + return QQmlListProperty(this, nullptr, + data_append, + data_count, + data_at, + data_clear); +} + +void ScriptedQuickSceneEffect::data_append(QQmlListProperty *objects, QObject *object) +{ + if (!object) { + return; + } + + ScriptedQuickSceneEffect *effect = static_cast(objects->object); + if (!effect->m_children.contains(object)) { + object->setParent(effect); + effect->m_children.append(object); + } +} + +qsizetype ScriptedQuickSceneEffect::data_count(QQmlListProperty *objects) +{ + ScriptedQuickSceneEffect *effect = static_cast(objects->object); + return effect->m_children.count(); +} + +QObject *ScriptedQuickSceneEffect::data_at(QQmlListProperty *objects, qsizetype index) +{ + ScriptedQuickSceneEffect *effect = static_cast(objects->object); + return effect->m_children.value(index); +} + +void ScriptedQuickSceneEffect::data_clear(QQmlListProperty *objects) +{ + ScriptedQuickSceneEffect *effect = static_cast(objects->object); + while (!effect->m_children.isEmpty()) { + QObject *child = effect->m_children.takeLast(); + child->setParent(nullptr); + } +} + +} // namespace KWin + +#include "moc_scriptedquicksceneeffect.cpp" diff --git a/src/scripting/scriptedquicksceneeffect.h b/src/scripting/scriptedquicksceneeffect.h new file mode 100644 index 0000000000..413f5d9fdd --- /dev/null +++ b/src/scripting/scriptedquicksceneeffect.h @@ -0,0 +1,93 @@ +/* + SPDX-FileCopyrightText: 2023 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include "libkwineffects/kwinquickeffect.h" + +#include + +#include + +class KConfigLoader; +class KConfigPropertyMap; + +namespace KWin +{ + +/** + * The SceneEffect type provides a way to implement effects that replace the default scene with + * a custom one. + * + * Example usage: + * @code + * SceneEffect { + * id: root + * + * delegate: Rectangle { + * color: "blue" + * } + * + * ShortcutHandler { + * name: "Toggle Effect" + * text: i18n("Toggle Effect") + * sequence: "Meta+E" + * onActivated: root.visible = !root.visible; + * } + * } + * @endcode + */ +class ScriptedQuickSceneEffect : public QuickSceneEffect +{ + Q_OBJECT + Q_PROPERTY(QQmlListProperty data READ data) + Q_CLASSINFO("DefaultProperty", "data") + + /** + * The key-value store with the effect settings. + */ + Q_PROPERTY(KConfigPropertyMap *configuration READ configuration NOTIFY configurationChanged) + + /** + * Whether the effect is shown. Setting this property to @c true activates the effect; setting + * this property to @c false will deactivate the effect and the screen views will be unloaded at + * the next available time. + */ + Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged) + +public: + explicit ScriptedQuickSceneEffect(); + ~ScriptedQuickSceneEffect() override; + + void setMetaData(const KPluginMetaData &metaData); + + int requestedEffectChainPosition() const override; + + bool isVisible() const; + void setVisible(bool visible); + + QQmlListProperty data(); + KConfigPropertyMap *configuration() const; + + static void data_append(QQmlListProperty *objects, QObject *object); + static qsizetype data_count(QQmlListProperty *objects); + static QObject *data_at(QQmlListProperty *objects, qsizetype index); + static void data_clear(QQmlListProperty *objects); + +Q_SIGNALS: + void visibleChanged(); + void configurationChanged(); + +private: + KConfigLoader *m_configLoader = nullptr; + KConfigPropertyMap *m_configuration = nullptr; + QObjectList m_children; + QTimer m_visibleTimer; + bool m_isVisible = false; + int m_requestedEffectChainPosition = 0; +}; + +} // namespace KWin diff --git a/src/scripting/scripting.cpp b/src/scripting/scripting.cpp index 2cef5b8207..3f047f9bf4 100644 --- a/src/scripting/scripting.cpp +++ b/src/scripting/scripting.cpp @@ -16,6 +16,7 @@ #include "gesturehandler.h" #include "libkwineffects/kwinquickeffect.h" #include "screenedgehandler.h" +#include "scriptedquicksceneeffect.h" #include "scripting_logging.h" #include "scriptingutils.h" #include "shortcuthandler.h" @@ -34,6 +35,7 @@ #include "workspace.h" // KDE #include +#include #include #include #include @@ -652,12 +654,14 @@ void KWin::Scripting::init() qmlRegisterType("org.kde.kwin", 3, 0, "WindowFilterModel"); qmlRegisterType("org.kde.kwin", 3, 0, "VirtualDesktopModel"); qmlRegisterUncreatableType("org.kde.kwin", 3, 0, "SceneView", QStringLiteral("Can't instantiate an object of type SceneView")); + qmlRegisterType("org.kde.kwin", 3, 0, "SceneEffect"); qmlRegisterSingletonType("org.kde.kwin", 3, 0, "Workspace", [](QQmlEngine *qmlEngine, QJSEngine *jsEngine) { return new DeclarativeScriptWorkspaceWrapper(); }); qmlRegisterSingletonInstance("org.kde.kwin", 3, 0, "Options", options); + qmlRegisterAnonymousType("org.kde.kwin", 3); qmlRegisterAnonymousType("org.kde.kwin", 3); qmlRegisterAnonymousType("org.kde.kwin", 3); qmlRegisterAnonymousType("org.kde.kwin", 3);