Advance Wayland and KDE package bring-up

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,50 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "dbuscall.h"
#include "scriptingutils.h"
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusPendingCall>
namespace KWin
{
DBusCall::DBusCall(QObject *parent)
: QObject(parent)
{
}
DBusCall::~DBusCall()
{
}
void DBusCall::call()
{
QDBusMessage msg = QDBusMessage::createMethodCall(m_service, m_path, m_interface, m_method);
msg.setArguments(m_arguments);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(QDBusConnection::sessionBus().asyncCall(msg), this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, watcher]() {
watcher->deleteLater();
if (watcher->isError()) {
Q_EMIT failed();
return;
}
QVariantList reply = watcher->reply().arguments();
std::for_each(reply.begin(), reply.end(), [](QVariant &variant) {
variant = dbusToVariant(variant);
});
Q_EMIT finished(reply);
});
}
} // KWin
#include "moc_dbuscall.cpp"
@@ -0,0 +1,134 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QObject>
#include <QString>
#include <QVariant>
namespace KWin
{
/**
* @brief Qml export for providing a wrapper for sending a message over the DBus
* session bus.
*
* Allows to setup the connection arguments just like in QDBusMessage and supports
* adding arguments to the call. To invoke the message use the slot @ref call.
*
* If the call succeeds the signal @ref finished is emitted, if the call fails
* the signal @ref failed is emitted.
*
* Note: the DBusCall always uses the session bus and performs an async call.
*
* Example on how to use in Qml:
* @code
* DBusCall {
* id: dbus
* service: "org.kde.KWin"
* path: "/KWin"
* method: "nextDesktop"
* Component.onCompleted: dbus.call()
* }
* @endcode
*
* Example with arguments:
* @code
* DBusCall {
* id: dbus
* service: "org.kde.KWin"
* path: "/KWin"
* method: "setCurrentDesktop"
* arguments: [1]
* Component.onCompleted: dbus.call()
* }
* @endcode
*
* Example with a callback:
* @code
* DBusCall {
* id: dbus
* service: "org.kde.KWin"
* path: "/KWin"
* method: "currentDesktop"
* onFinished: console.log(returnValue[0])
* }
* @endcode
*/
class DBusCall : public QObject
{
Q_OBJECT
Q_PROPERTY(QString service READ service WRITE setService NOTIFY serviceChanged)
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
Q_PROPERTY(QString dbusInterface READ interface WRITE setInterface NOTIFY interfaceChanged)
Q_PROPERTY(QString method READ method WRITE setMethod NOTIFY methodChanged)
Q_PROPERTY(QVariantList arguments READ arguments WRITE setArguments NOTIFY argumentsChanged)
public:
explicit DBusCall(QObject *parent = nullptr);
~DBusCall() override;
const QString &service() const;
const QString &path() const;
const QString &interface() const;
const QString &method() const;
const QVariantList &arguments() const;
public Q_SLOTS:
void call();
void setService(const QString &service);
void setPath(const QString &path);
void setInterface(const QString &interface);
void setMethod(const QString &method);
void setArguments(const QVariantList &arguments);
Q_SIGNALS:
void finished(QVariantList returnValue);
void failed();
void serviceChanged();
void pathChanged();
void interfaceChanged();
void methodChanged();
void argumentsChanged();
private:
QString m_service;
QString m_path;
QString m_interface;
QString m_method;
QVariantList m_arguments;
};
#define GENERIC_WRAPPER(type, name, upperName) \
inline type DBusCall::name() const \
{ \
return m_##name; \
} \
inline void DBusCall::set##upperName(type name) \
{ \
if (m_##name == name) { \
return; \
} \
m_##name = name; \
Q_EMIT name##Changed(); \
}
#define WRAPPER(name, upperName) \
GENERIC_WRAPPER(const QString &, name, upperName)
WRAPPER(interface, Interface)
WRAPPER(method, Method)
WRAPPER(path, Path)
WRAPPER(service, Service)
GENERIC_WRAPPER(const QVariantList &, arguments, Arguments)
#undef WRAPPER
#undef GENERIC_WRAPPER
} // KWin
@@ -0,0 +1,126 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "desktopbackgrounditem.h"
#include "core/output.h"
#include "window.h"
#if KWIN_BUILD_ACTIVITIES
#include "activities.h"
#endif
#include "core/outputbackend.h"
#include "main.h"
#include "scripting_logging.h"
#include "virtualdesktops.h"
#include "workspace.h"
namespace KWin
{
DesktopBackgroundItem::DesktopBackgroundItem(QQuickItem *parent)
: WindowThumbnailItem(parent)
{
}
void DesktopBackgroundItem::componentComplete()
{
WindowThumbnailItem::componentComplete();
updateWindow();
}
QString DesktopBackgroundItem::outputName() const
{
return m_output ? m_output->name() : QString();
}
void DesktopBackgroundItem::setOutputName(const QString &name)
{
setOutput(kwinApp()->outputBackend()->findOutput(name));
}
Output *DesktopBackgroundItem::output() const
{
return m_output;
}
void DesktopBackgroundItem::setOutput(Output *output)
{
if (m_output != output) {
m_output = output;
updateWindow();
Q_EMIT outputChanged();
}
}
VirtualDesktop *DesktopBackgroundItem::desktop() const
{
return m_desktop;
}
void DesktopBackgroundItem::setDesktop(VirtualDesktop *desktop)
{
if (m_desktop != desktop) {
m_desktop = desktop;
updateWindow();
Q_EMIT desktopChanged();
}
}
QString DesktopBackgroundItem::activity() const
{
return m_activity;
}
void DesktopBackgroundItem::setActivity(const QString &activity)
{
if (m_activity != activity) {
m_activity = activity;
updateWindow();
Q_EMIT activityChanged();
}
}
void DesktopBackgroundItem::updateWindow()
{
if (!isComponentComplete()) {
return;
}
if (Q_UNLIKELY(!m_output)) {
qCWarning(KWIN_SCRIPTING) << "DesktopBackgroundItem.output is required";
return;
}
VirtualDesktop *desktop = m_desktop;
if (!desktop) {
desktop = VirtualDesktopManager::self()->currentDesktop();
}
QString activity = m_activity;
if (activity.isEmpty()) {
#if KWIN_BUILD_ACTIVITIES
activity = Workspace::self()->activities()->current();
#endif
}
Window *clientCandidate = nullptr;
const auto clients = workspace()->windows();
for (Window *client : clients) {
if (client->isDesktop() && client->isOnOutput(m_output) && client->isOnDesktop(desktop) && client->isOnActivity(activity)) {
// In the unlikely event there are multiple desktop windows (e.g. conky's floating panel is of type "desktop")
// choose the one which matches the ouptut size, if possible.
if (!clientCandidate || client->size() == m_output->geometry().size()) {
clientCandidate = client;
}
}
}
setClient(clientCandidate);
}
} // namespace KWin
#include "moc_desktopbackgrounditem.cpp"
@@ -0,0 +1,59 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "windowthumbnailitem.h"
namespace KWin
{
class Output;
class VirtualDesktop;
/**
* The DesktopBackgroundItem type is a convenience helper that represents the desktop
* background on the specified screen, virtual desktop, and activity.
*/
class DesktopBackgroundItem : public WindowThumbnailItem
{
Q_OBJECT
Q_PROPERTY(QString outputName READ outputName WRITE setOutputName NOTIFY outputChanged)
Q_PROPERTY(KWin::Output *output READ output WRITE setOutput NOTIFY outputChanged)
Q_PROPERTY(QString activity READ activity WRITE setActivity NOTIFY activityChanged)
Q_PROPERTY(KWin::VirtualDesktop *desktop READ desktop WRITE setDesktop NOTIFY desktopChanged)
public:
explicit DesktopBackgroundItem(QQuickItem *parent = nullptr);
void componentComplete() override;
QString outputName() const;
void setOutputName(const QString &name);
Output *output() const;
void setOutput(Output *output);
VirtualDesktop *desktop() const;
void setDesktop(VirtualDesktop *desktop);
QString activity() const;
void setActivity(const QString &activity);
Q_SIGNALS:
void outputChanged();
void desktopChanged();
void activityChanged();
private:
void updateWindow();
Output *m_output = nullptr;
VirtualDesktop *m_desktop = nullptr;
QString m_activity;
};
} // namespace KWin
@@ -0,0 +1,197 @@
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<doxygen xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="compound.xsd" version="1.7.6.1">
<!-- Documentation for global KWin effect methods. In doxygen XML format as this can be converted to MediaWiki -->
<!-- Use script and XSLT from kde:scratch/graesslin/kwin-scripting-api-generator to generate the documentation -->
<!-- This xml is not meant to be doxygen complient -->
<compounddef>
<compoundname>Global</compoundname>
<briefdescription>Methods and properties added to the global JavaScript object.</briefdescription>
<sectiondef kind="property">
<memberdef kind="property" writable="no">
<type>KWin::EffectsHandler</type>
<definition></definition>
<argsstring></argsstring>
<name>effects</name>
<read></read>
<detaileddescription>Global property to the core wrapper of KWin Effects</detaileddescription>
</memberdef>
<memberdef kind="property" writable="no">
<type>KWin::ScriptedEffect</type>
<definition></definition>
<argsstring></argsstring>
<name>effect</name>
<read></read>
<detaileddescription>Global property to the actual Effect</detaileddescription>
</memberdef>
<memberdef kind="property" writable="no">
<type>object</type>
<definition></definition>
<argsstring></argsstring>
<name>Effect</name>
<read></read>
<detaileddescription>Provides access to enums defined in KWin::AnimationEffect and KWin::ScriptedEffect</detaileddescription>
</memberdef>
<memberdef kind="property" writable="no">
<type>object</type>
<definition></definition>
<argsstring></argsstring>
<name>KWin</name>
<read></read>
<detaileddescription>Provides access to enums defined in KWin::WorkspaceWrapper</detaileddescription>
</memberdef>
<memberdef kind="property" writable="no">
<type>object</type>
<definition></definition>
<argsstring></argsstring>
<name>QEasingCurve</name>
<read></read>
<detaileddescription>Provides access to enums defined in QEasingCurve</detaileddescription>
</memberdef>
</sectiondef>
<sectiondef kind="public-func">
<memberdef kind="function">
<type>Q_SCRIPTABLE QList&lt;quint64&gt;</type>
<definition>QList&lt;quint64&gt; KWin::ScriptedEffect::animate</definition>
<argsstring>(settings)</argsstring>
<name>animate</name>
<read></read>
<detaileddescription>
Schedules one or many animations for one window. The animations are defined through the settings object providing
a more declarative way to specify the animations than the animate call on the effect object. The settings object
supports the following attributes:
&lt;syntaxhighlight lang="javascript"&gt;
{
window: EffectWindow, /* the window to animate, required */
duration: int, /* duration in msec, required */
curve: QEasingCurve.Type, /* global easing curve, optional */
type: Effect.Attribute, /* for first animation, optional */
from: FPx2, /* for first animation, optional */
to: FPx2, /* for first animation, optional */
delay: int, /* for first animation, optional */
shader: int, /* for first animation, optional */
animations: [ /* additional animations, optional */
{
curve: QEasingCurve.Type, /* overrides global */
type: Effect.Attribute,
from: FPx2,
to: FPx2,
delay: int,
shader: int
}
]
}
&lt;/syntaxhighlight&gt;
At least one animation or attribute setter (see below) needs to be specified either with the top-level properties or in the animations list.
</detaileddescription>
</memberdef>
<memberdef kind="function">
<type>Q_SCRIPTABLE QList&lt;quint64&gt;</type>
<definition>QList&lt;quint64&gt; KWin::ScriptedEffect::set</definition>
<argsstring>(settings)</argsstring>
<name>set</name>
<read></read>
<detaileddescription>
Like animate, just that the manipulation does not implicitly end with the animation. You have to explicitly cancel it.
Until then, the manipulated attribute will remain at animation target value.
</detaileddescription>
</memberdef>
<memberdef kind="function">
<type>Q_SCRIPTABLE bool</type>
<definition>bool KWin::ScriptedEffect::cancel</definition>
<argsstring>(QList&lt;quint64&gt;)</argsstring>
<name>cancel</name>
<read></read>
<detaileddescription>
Cancel one or more present animations caused and returned by KWin::ScriptedEffect::animate or KWin::ScriptedEffect::set.
For convenience you can pass a single quint64 as well.
</detaileddescription>
</memberdef>
<memberdef kind="function">
<type>Q_SCRIPTABLE void</type>
<definition>void KWin::ScriptedEffect::print</definition>
<argsstring>(QVariant ... values)</argsstring>
<name>print</name>
<read></read>
<detaileddescription>Prints all provided values to kDebug and as a D-Bus signal</detaileddescription>
</memberdef>
<memberdef kind="function">
<type>Q_SCRIPTABLE int</type>
<definition>int KWin::ScriptedEffect::animationTime</definition>
<argsstring>(int duration)</argsstring>
<name>animationTime</name>
<read></read>
<detaileddescription>Adjusts the passed in duration to the global animation time facator.</detaileddescription>
</memberdef>
<memberdef kind="function">
<type>Q_SCRIPTABLE int</type>
<definition>int KWin::ScriptedEffect::displayWidth</definition>
<argsstring>()</argsstring>
<name>displayWidth</name>
<read></read>
<detaileddescription>Width of the complete display (all screens).</detaileddescription>
</memberdef>
<memberdef kind="function">
<type>Q_SCRIPTABLE int</type>
<definition>int KWin::ScriptedEffect::displayHeight</definition>
<argsstring>()</argsstring>
<name>displayHeight</name>
<read></read>
<detaileddescription>Height of the complete display (all screens).</detaileddescription>
</memberdef>
<memberdef kind="function">
<type>Q_SCRIPTABLE bool</type>
<definition>bool KWin::ScriptedEffect::registerScreenEdge</definition>
<argsstring>(ElectricBorder border, QScriptValue callback)</argsstring>
<name>registerScreenEdge</name>
<read></read>
<detaileddescription>Registers the callback for the screen edge. When the mouse gets pushed against the given edge the callback will be invoked.</detaileddescription>
</memberdef>
<memberdef kind="function">
<type>Q_SCRIPTABLE bool</type>
<definition>bool KWin::ScriptedEffect::registerShortcut</definition>
<argsstring>(QString title, QString text, QString keySequence, QScriptValue callback)</argsstring>
<name>registerShortcut</name>
<read></read>
<detaileddescription>Registers keySequence as a global shortcut. When the shortcut is invoked the callback will be called. Title and text are used to name the shortcut and make it available to the global shortcut configuration module.</detaileddescription>
</memberdef>
<memberdef kind="function">
<type>Q_SCRIPTABLE uint</type>
<definition>uint KWin::ScriptedEffect::addFragmentShader</definition>
<argsstring>(ShaderTrait traits, QString fragmentShaderFile)</argsstring>
<name>addFragmentShader</name>
<read></read>
<detaileddescription>Creates a shader and returns an identifier which can be used in animate or set. The shader sources must be provided in the shaders sub-directory of the contents package directory. The fragment shader needs to have the file extension frag. Each shader should be provided in a GLSL 1.10 and GLSL 1.40 variant. The 1.40 variant needs to have a suffix _core. E.g. there should be a shader myCustomShader.frag and myCustomShader_core.frag. The vertex shader is generated from the ShaderTrait. The ShaderTrait enum can be used as flags in this method.</detaileddescription>
</memberdef>
<memberdef kind="function">
<type>Q_SCRIPTABLE uint</type>
<definition>void KWin::ScriptedEffect::setUniform</definition>
<argsstring>(uint shaderId, QString name, QJSValue value)</argsstring>
<name>setUniform</name>
<read></read>
<detaileddescription>Updates the uniform value of the uniform identified by @p name for the shader identified by @p shaderId. The @p value can be a floating point numeric value (integer uniform values are not supported), an array with either 2, 3 or 4 numeric values, a string to identify a color or a variant value to identify a color as returned by readConfig. This method can be used to update the state of the shader when the configuration of the effect changed.</detaileddescription>
</memberdef>
</sectiondef>
</compounddef>
<compounddef>
<compoundname>KWin::FPx2</compoundname>
<briefdescription>This class is used to describe the animation end points, that is from which FPx2 values to which FPx2 values an animation goes. This class contains two properties to describe two animation components individually (e.g. width and height). But it's also possible to just have one value (e.g. opacity). In this case the definition of an FPx2 can be replaced by a single value.</briefdescription>
<sectiondef kind="property">
<memberdef kind="property" writable="yes">
<type>qreal</type>
<definition></definition>
<argsstring></argsstring>
<name>value1</name>
<read></read>
<detaileddescription></detaileddescription>
</memberdef>
<memberdef kind="property" writable="yes">
<type>qreal</type>
<definition></definition>
<argsstring></argsstring>
<name>value2</name>
<read></read>
<detaileddescription></detaileddescription>
</memberdef>
</sectiondef>
</compounddef>
</doxygen>
@@ -0,0 +1,144 @@
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<doxygen xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="compound.xsd" version="1.7.6.1">
<!-- Documentation for global KWin scripting methods. In doxygen XML format as this can be converted to MediaWiki -->
<!-- Use script and XSLT from https://invent.kde.org/graesslin/kwin-scripting-api-generator to generate the documentation -->
<!-- This xml is not meant to be doxygen complient -->
<compounddef>
<compoundname>Global</compoundname>
<briefdescription>Methods and properties added to the global JavaScript object.</briefdescription>
<sectiondef kind="property">
<memberdef kind="property" writable="no">
<type>KWin::Options</type>
<definition></definition>
<argsstring></argsstring>
<name>options</name>
<read></read>
<detaileddescription>Global property to all configuration values of KWin core.</detaileddescription>
</memberdef>
<memberdef kind="property" writable="no">
<type>KWin::Workspace</type>
<definition></definition>
<argsstring></argsstring>
<name>workspace</name>
<read></read>
<detaileddescription>Global property to the core wrapper of KWin.</detaileddescription>
</memberdef>
<memberdef kind="property" writable="no">
<type>object</type>
<definition></definition>
<argsstring></argsstring>
<name>KWin</name>
<read></read>
<detaileddescription>Provides access to enums defined in KWin::WorkspaceWrapper</detaileddescription>
</memberdef>
</sectiondef>
<sectiondef kind="public-func">
<memberdef kind="function">
<type>Q_SCRIPTABLE void</type>
<definition>void KWin::Scripting::print</definition>
<argsstring>(QVariant ... values)</argsstring>
<name>print</name>
<read></read>
<detaileddescription>Prints all provided values to kDebug and as a D-Bus signal</detaileddescription>
</memberdef>
<memberdef kind="function">
<type>Q_SCRIPTABLE QVariant</type>
<definition>QVariant KWin::Scripting::readConfig</definition>
<argsstring>(QString key, QVariant defaultValue = QVariant())</argsstring>
<name>readConfig</name>
<read></read>
<detaileddescription>Reads the config value for key in the Script's configuration with the optional default value. If not providing a default value and no value stored in the configuration an undefined value is returned.</detaileddescription>
</memberdef>
<memberdef kind="function">
<type>Q_SCRIPTABLE bool</type>
<definition>bool KWin::Scripting::registerScreenEdge</definition>
<argsstring>(ElectricBorder border, QScriptValue callback)</argsstring>
<name>registerScreenEdge</name>
<read></read>
<detaileddescription>Registers the callback for the screen edge. When the mouse gets pushed against the given edge the callback will be invoked.
Scripts can also add "X-KWin-Border-Activate" to their metadata file to have the effect listed in the screen edges KCM. This will write an entry BorderConfig= in the script configuration object with a list of ScreenEdges the user has selected.
</detaileddescription>
</memberdef>
<memberdef kind="function">
<type>Q_SCRIPTABLE bool</type>
<definition>bool KWin::Scripting::unregisterScreenEdge</definition>
<argsstring>(ElectricBorder border)</argsstring>
<name>unregisterScreenEdge</name>
<read></read>
<detaileddescription>Unregisters the callback for the screen edge. This will disconnect all callbacks from this script to that edge.</detaileddescription>
</memberdef>
<memberdef kind="function">
<type>Q_SCRIPTABLE bool</type>
<definition>bool KWin::Scripting::registerShortcut</definition>
<argsstring>(QString title, QString text, QString keySequence, QScriptValue callback)</argsstring>
<name>registerShortcut</name>
<read></read>
<detaileddescription>Registers keySequence as a global shortcut. When the shortcut is invoked the callback will be called. Title and text are used to name the shortcut and make it available to the global shortcut configuration module.</detaileddescription>
</memberdef>
<memberdef kind="function">
<type>Q_SCRIPTABLE bool</type>
<definition>bool KWin::Scripting::assert</definition>
<argsstring>(bool value, QString message = QString())</argsstring>
<name>assert</name>
<read></read>
<detaileddescription>Aborts the execution of the script if value does not evaluate to true. If message is provided an error is thrown with the given message, if not provided an error with default message is thrown.</detaileddescription>
</memberdef>
<memberdef kind="function">
<type>Q_SCRIPTABLE bool</type>
<definition>bool KWin::Scripting::assertTrue</definition>
<argsstring>(bool value, QString message = QString())</argsstring>
<name>assertTrue</name>
<read></read>
<detaileddescription>Aborts the execution of the script if value does not evaluate to true. If message is provided an error is thrown with the given message, if not provided an error with default message is thrown.</detaileddescription>
</memberdef>
<memberdef kind="function">
<type>Q_SCRIPTABLE bool</type>
<definition>bool KWin::Scripting::assertFalse</definition>
<argsstring>(bool value, QString message = QString())</argsstring>
<name>assertFalse</name>
<read></read>
<detaileddescription>Aborts the execution of the script if value does not evaluate to false. If message is provided an error is thrown with the given message, if not provided an error with default message is thrown.</detaileddescription>
</memberdef>
<memberdef kind="function">
<type>Q_SCRIPTABLE bool</type>
<definition>bool KWin::Scripting::assertEquals</definition>
<argsstring>(QVariant expected, QVariant actual, QString message = QString())</argsstring>
<name>assertEquals</name>
<read></read>
<detaileddescription>Aborts the execution of the script if the actual value is not equal to the expected value. If message is provided an error is thrown with the given message, if not provided an error with default message is thrown.</detaileddescription>
</memberdef>
<memberdef kind="function">
<type>Q_SCRIPTABLE bool</type>
<definition>bool KWin::Scripting::assertNull</definition>
<argsstring>(QVariant value, QString message = QString())</argsstring>
<name>assertNull</name>
<read></read>
<detaileddescription>Aborts the execution of the script if value is not null. If message is provided an error is thrown with the given message, if not provided an error with default message is thrown.</detaileddescription>
</memberdef>
<memberdef kind="function">
<type>Q_SCRIPTABLE bool</type>
<definition>bool KWin::Scripting::assertNotNull</definition>
<argsstring>(QVariant value, QString message = QString())</argsstring>
<name>assertNotNull</name>
<read></read>
<detaileddescription>Aborts the execution of the script if value is null. If message is provided an error is thrown with the given message, if not provided an error with default message is thrown.</detaileddescription>
</memberdef>
<memberdef kind="function">
<type>Q_SCRIPTABLE void</type>
<definition>void KWin::Scripting::callDBus</definition>
<argsstring>(QString service, QString path, QString interface, QString method, QVariant arg..., QScriptValue callback = QScriptValue())</argsstring>
<name>callDBus</name>
<read></read>
<detaileddescription>Call a D-Bus method at (service, path, interface and method). A variable number of arguments can be added to the method call. The D-Bus call is always performed in an async way invoking the callback provided as the last (optional) argument. The reply values of the D-Bus method call are passed to the callback.</detaileddescription>
</memberdef>
<memberdef kind="function">
<type>Q_SCRIPTABLE void</type>
<definition>void KWin::Scripting::registerUserActionsMenu</definition>
<argsstring>(QScriptValue callback)</argsstring>
<name>registerUserActionsMenu</name>
<read></read>
<detaileddescription>Registers the passed in callback to be invoked whenever the User actions menu (Alt+F3 or right click on window decoration) is about to be shown. The callback is invoked with a reference to the Client for which the menu is shown. The callback can return either a single menu entry to be added to the menu or an own sub menu with multiple entries. The object for a menu entry should be {title: "My Menu entry", checkable: true, checked: false, triggered: function (action) { // callback with triggered QAction}}, for a menu it should be {title: "My menu", items: [{...}, {...}, ...] /*list with entries as described*/}</detaileddescription>
</memberdef>
</sectiondef>
</compounddef>
</doxygen>
@@ -0,0 +1,187 @@
/*
SPDX-FileCopyrightText: 2023 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "gesturehandler.h"
#include "gestures.h"
#include "globalshortcuts.h"
#include "input.h"
namespace KWin
{
SwipeGestureHandler::SwipeGestureHandler(QObject *parent)
: QObject(parent)
{
}
SwipeGestureHandler::~SwipeGestureHandler()
{
}
void SwipeGestureHandler::classBegin()
{
}
void SwipeGestureHandler::componentComplete()
{
m_gesture = std::make_unique<SwipeGesture>();
m_gesture->setDirection(SwipeDirection(m_direction));
m_gesture->setMinimumDelta(QPointF(200, 200));
m_gesture->setMaximumFingerCount(m_fingerCount);
m_gesture->setMinimumFingerCount(m_fingerCount);
connect(m_gesture.get(), &SwipeGesture::triggered, this, &SwipeGestureHandler::activated);
connect(m_gesture.get(), &SwipeGesture::cancelled, this, &SwipeGestureHandler::cancelled);
connect(m_gesture.get(), &SwipeGesture::progress, this, &SwipeGestureHandler::setProgress);
switch (m_deviceType) {
case Device::Touchpad:
input()->shortcuts()->registerTouchpadSwipe(m_gesture.get());
break;
case Device::Touchscreen:
input()->shortcuts()->registerTouchscreenSwipe(m_gesture.get());
break;
}
}
SwipeGestureHandler::Direction SwipeGestureHandler::direction() const
{
return m_direction;
}
void SwipeGestureHandler::setDirection(Direction direction)
{
if (m_direction != direction) {
m_direction = direction;
Q_EMIT directionChanged();
}
}
int SwipeGestureHandler::fingerCount() const
{
return m_fingerCount;
}
void SwipeGestureHandler::setFingerCount(int fingerCount)
{
if (m_fingerCount != fingerCount) {
m_fingerCount = fingerCount;
Q_EMIT fingerCountChanged();
}
}
qreal SwipeGestureHandler::progress() const
{
return m_progress;
}
void SwipeGestureHandler::setProgress(qreal progress)
{
if (m_progress != progress) {
m_progress = progress;
Q_EMIT progressChanged();
}
}
SwipeGestureHandler::Device SwipeGestureHandler::deviceType() const
{
return m_deviceType;
}
void SwipeGestureHandler::setDeviceType(Device device)
{
if (m_deviceType != device) {
m_deviceType = device;
Q_EMIT deviceTypeChanged();
}
}
PinchGestureHandler::PinchGestureHandler(QObject *parent)
: QObject(parent)
{
}
PinchGestureHandler::~PinchGestureHandler()
{
}
void PinchGestureHandler::classBegin()
{
}
void PinchGestureHandler::componentComplete()
{
m_gesture = std::make_unique<PinchGesture>();
m_gesture->setDirection(PinchDirection(m_direction));
m_gesture->setMaximumFingerCount(m_fingerCount);
m_gesture->setMinimumFingerCount(m_fingerCount);
connect(m_gesture.get(), &PinchGesture::triggered, this, &PinchGestureHandler::activated);
connect(m_gesture.get(), &PinchGesture::cancelled, this, &PinchGestureHandler::cancelled);
connect(m_gesture.get(), &PinchGesture::progress, this, &PinchGestureHandler::setProgress);
switch (m_deviceType) {
case Device::Touchpad:
input()->shortcuts()->registerTouchpadPinch(m_gesture.get());
break;
}
}
PinchGestureHandler::Direction PinchGestureHandler::direction() const
{
return m_direction;
}
void PinchGestureHandler::setDirection(Direction direction)
{
if (m_direction != direction) {
m_direction = direction;
Q_EMIT directionChanged();
}
}
int PinchGestureHandler::fingerCount() const
{
return m_fingerCount;
}
void PinchGestureHandler::setFingerCount(int fingerCount)
{
if (m_fingerCount != fingerCount) {
m_fingerCount = fingerCount;
Q_EMIT fingerCountChanged();
}
}
qreal PinchGestureHandler::progress() const
{
return m_progress;
}
void PinchGestureHandler::setProgress(qreal progress)
{
if (m_progress != progress) {
m_progress = progress;
Q_EMIT progressChanged();
}
}
PinchGestureHandler::Device PinchGestureHandler::deviceType() const
{
return m_deviceType;
}
void PinchGestureHandler::setDeviceType(Device device)
{
if (m_deviceType != device) {
m_deviceType = device;
Q_EMIT deviceTypeChanged();
}
}
} // namespace KWin
#include "moc_gesturehandler.cpp"
@@ -0,0 +1,158 @@
/*
SPDX-FileCopyrightText: 2023 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QQmlParserStatus>
class QAction;
namespace KWin
{
class PinchGesture;
class SwipeGesture;
/**
* The SwipeGestureHandler type provides a way to handle global swipe gestures.
*
* Example usage:
* @code
* SwipeGestureHandler {
* direction: SwipeGestureHandler.Direction.Up
* fingerCount: 3
* onActivated: console.log("activated")
* }
* @endcode
*/
class SwipeGestureHandler : public QObject, public QQmlParserStatus
{
Q_OBJECT
Q_PROPERTY(Direction direction READ direction WRITE setDirection NOTIFY directionChanged)
Q_PROPERTY(int fingerCount READ fingerCount WRITE setFingerCount NOTIFY fingerCountChanged)
Q_PROPERTY(qreal progress READ progress NOTIFY progressChanged)
Q_PROPERTY(Device deviceType READ deviceType WRITE setDeviceType NOTIFY deviceTypeChanged)
public:
explicit SwipeGestureHandler(QObject *parent = nullptr);
~SwipeGestureHandler() override;
// Matches SwipeDirection.
enum class Direction {
Invalid,
Down,
Left,
Up,
Right,
};
Q_ENUM(Direction)
enum class Device {
Touchpad,
Touchscreen,
};
Q_ENUM(Device)
void classBegin() override;
void componentComplete() override;
Direction direction() const;
void setDirection(Direction direction);
int fingerCount() const;
void setFingerCount(int fingerCount);
qreal progress() const;
void setProgress(qreal progress);
Device deviceType() const;
void setDeviceType(Device device);
Q_SIGNALS:
void activated();
void cancelled();
void progressChanged();
void directionChanged();
void fingerCountChanged();
void deviceTypeChanged();
private:
std::unique_ptr<SwipeGesture> m_gesture;
Direction m_direction = Direction::Invalid;
Device m_deviceType = Device::Touchpad;
qreal m_progress = 0;
int m_fingerCount = 3;
};
/**
* The PinchGestureHandler type provides a way to handle global pinch gestures.
*
* Example usage:
* @code
* PinchGestureHandler {
* direction: PinchGestureHandler.Direction.Contracting
* fingerCount: 3
* onActivated: console.log("activated")
* }
* @endcode
*/
class PinchGestureHandler : public QObject, public QQmlParserStatus
{
Q_OBJECT
Q_PROPERTY(Direction direction READ direction WRITE setDirection NOTIFY directionChanged)
Q_PROPERTY(int fingerCount READ fingerCount WRITE setFingerCount NOTIFY fingerCountChanged)
Q_PROPERTY(qreal progress READ progress NOTIFY progressChanged)
public:
explicit PinchGestureHandler(QObject *parent = nullptr);
~PinchGestureHandler() override;
// Matches PinchDirection.
enum class Direction {
Expanding,
Contracting,
};
Q_ENUM(Direction)
enum class Device {
Touchpad,
};
Q_ENUM(Device)
void classBegin() override;
void componentComplete() override;
Direction direction() const;
void setDirection(Direction direction);
int fingerCount() const;
void setFingerCount(int fingerCount);
qreal progress() const;
void setProgress(qreal progress);
Device deviceType() const;
void setDeviceType(Device device);
Q_SIGNALS:
void activated();
void cancelled();
void progressChanged();
void directionChanged();
void fingerCountChanged();
void deviceTypeChanged();
private:
std::unique_ptr<PinchGesture> m_gesture;
Direction m_direction = Direction::Contracting;
Device m_deviceType = Device::Touchpad;
qreal m_progress = 0;
int m_fingerCount = 3;
};
} // namespace KWin
@@ -0,0 +1,10 @@
<!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.Script">
<method name="stop">
</method>
<method name="run">
</method>
</interface>
</node>
@@ -0,0 +1,112 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "screenedgehandler.h"
#include "config-kwin.h"
#include "screenedge.h"
#include "workspace.h"
#include <QAction>
namespace KWin
{
ScreenEdgeHandler::ScreenEdgeHandler(QObject *parent)
: QObject(parent)
, m_enabled(true)
, m_edge(NoEdge)
, m_action(new QAction(this))
{
connect(m_action, &QAction::triggered, this, &ScreenEdgeHandler::activated);
}
ScreenEdgeHandler::~ScreenEdgeHandler()
{
}
void ScreenEdgeHandler::setEnabled(bool enabled)
{
if (m_enabled == enabled) {
return;
}
disableEdge();
m_enabled = enabled;
enableEdge();
Q_EMIT enabledChanged();
}
void ScreenEdgeHandler::setEdge(Edge edge)
{
if (m_edge == edge) {
return;
}
disableEdge();
m_edge = edge;
enableEdge();
Q_EMIT edgeChanged();
}
void ScreenEdgeHandler::enableEdge()
{
if (!m_enabled || m_edge == NoEdge) {
return;
}
switch (m_mode) {
case Mode::Pointer:
workspace()->screenEdges()->reserve(static_cast<ElectricBorder>(m_edge), this, "borderActivated");
break;
case Mode::Touch:
workspace()->screenEdges()->reserveTouch(static_cast<ElectricBorder>(m_edge), m_action);
break;
default:
Q_UNREACHABLE();
}
}
void ScreenEdgeHandler::disableEdge()
{
if (!m_enabled || m_edge == NoEdge) {
return;
}
switch (m_mode) {
case Mode::Pointer:
workspace()->screenEdges()->unreserve(static_cast<ElectricBorder>(m_edge), this);
break;
case Mode::Touch:
workspace()->screenEdges()->unreserveTouch(static_cast<ElectricBorder>(m_edge), m_action);
break;
default:
Q_UNREACHABLE();
}
}
bool ScreenEdgeHandler::borderActivated(ElectricBorder edge)
{
if (edge != static_cast<ElectricBorder>(m_edge) || !m_enabled) {
return false;
}
Q_EMIT activated();
return true;
}
void ScreenEdgeHandler::setMode(Mode mode)
{
if (m_mode == mode) {
return;
}
disableEdge();
m_mode = mode;
enableEdge();
Q_EMIT modeChanged();
}
} // namespace
#include "moc_screenedgehandler.cpp"
@@ -0,0 +1,114 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "effect/globals.h"
#include <QObject>
class QAction;
namespace KWin
{
/**
* @brief Qml export for reserving a Screen Edge.
*
* The edge is controlled by the @c enabled property and the @c edge
* property. If the edge is enabled and gets triggered the @ref activated()
* signal gets emitted.
*
* Example usage:
* @code
* ScreenEdgeHandler {
* edge: ScreenEdgeHandler.LeftEdge
* onActivated: doSomething()
* }
* @endcode
*/
class ScreenEdgeHandler : public QObject
{
Q_OBJECT
/**
* @brief Whether the edge is currently enabled, that is reserved. Default value is @c true.
*/
Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged)
/**
* @brief Which of the screen edges is to be reserved. Default value is @c NoEdge.
*/
Q_PROPERTY(Edge edge READ edge WRITE setEdge NOTIFY edgeChanged)
/**
* @brief The operation mode for this edge. Default value is @c Mode::Pointer
*/
Q_PROPERTY(Mode mode READ mode WRITE setMode NOTIFY modeChanged)
public:
enum Edge {
TopEdge,
TopRightEdge,
RightEdge,
BottomRightEdge,
BottomEdge,
BottomLeftEdge,
LeftEdge,
TopLeftEdge,
EDGE_COUNT,
NoEdge
};
Q_ENUM(Edge)
/**
* Enum describing the operation modes of the edge.
*/
enum class Mode {
Pointer,
Touch
};
Q_ENUM(Mode)
explicit ScreenEdgeHandler(QObject *parent = nullptr);
~ScreenEdgeHandler() override;
bool isEnabled() const;
Edge edge() const;
Mode mode() const
{
return m_mode;
}
public Q_SLOTS:
void setEnabled(bool enabled);
void setEdge(Edge edge);
void setMode(Mode mode);
Q_SIGNALS:
void enabledChanged();
void edgeChanged();
void modeChanged();
void activated();
private Q_SLOTS:
bool borderActivated(ElectricBorder edge);
private:
void enableEdge();
void disableEdge();
bool m_enabled;
Edge m_edge;
Mode m_mode = Mode::Pointer;
QAction *m_action;
};
inline bool ScreenEdgeHandler::isEnabled() const
{
return m_enabled;
}
inline ScreenEdgeHandler::Edge ScreenEdgeHandler::edge() const
{
return m_edge;
}
} // namespace KWin
@@ -0,0 +1,872 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2012 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2018 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "scriptedeffect.h"
#include "opengl/glshader.h"
#include "opengl/glshadermanager.h"
#include "scripting_logging.h"
#include "workspace_wrapper.h"
#include "core/output.h"
#include "effect/effecthandler.h"
#include "input.h"
#include "screenedge.h"
#include "workspace.h"
// KDE
#include <KConfigGroup>
#include <KGlobalAccel>
#include <KPluginMetaData>
#include <kconfigloader.h>
// Qt
#include <QAction>
#include <QFile>
#include <QJSEngine>
#include <QList>
#include <QStandardPaths>
#include <optional>
Q_DECLARE_METATYPE(KSharedConfigPtr)
namespace KWin
{
struct AnimationSettings
{
enum {
Type = 1 << 0,
Curve = 1 << 1,
Delay = 1 << 2,
Duration = 1 << 3,
FullScreen = 1 << 4,
KeepAlive = 1 << 5,
FrozenTime = 1 << 6
};
AnimationEffect::Attribute type;
QEasingCurve::Type curve;
QJSValue from;
QJSValue to;
int delay;
qint64 frozenTime;
uint duration;
uint set;
uint metaData;
bool fullScreenEffect;
bool keepAlive;
std::optional<uint> shader;
};
AnimationSettings animationSettingsFromObject(const QJSValue &object)
{
AnimationSettings settings;
settings.set = 0;
settings.metaData = 0;
settings.to = object.property(QStringLiteral("to"));
settings.from = object.property(QStringLiteral("from"));
const QJSValue duration = object.property(QStringLiteral("duration"));
if (duration.isNumber()) {
settings.duration = duration.toUInt();
settings.set |= AnimationSettings::Duration;
} else {
settings.duration = 0;
}
const QJSValue delay = object.property(QStringLiteral("delay"));
if (delay.isNumber()) {
settings.delay = delay.toInt();
settings.set |= AnimationSettings::Delay;
} else {
settings.delay = 0;
}
const QJSValue curve = object.property(QStringLiteral("curve"));
if (curve.isNumber()) {
settings.curve = static_cast<QEasingCurve::Type>(curve.toInt());
settings.set |= AnimationSettings::Curve;
} else {
settings.curve = QEasingCurve::Linear;
}
const QJSValue type = object.property(QStringLiteral("type"));
if (type.isNumber()) {
settings.type = static_cast<AnimationEffect::Attribute>(type.toInt());
settings.set |= AnimationSettings::Type;
} else {
settings.type = static_cast<AnimationEffect::Attribute>(-1);
}
const QJSValue isFullScreen = object.property(QStringLiteral("fullScreen"));
if (isFullScreen.isBool()) {
settings.fullScreenEffect = isFullScreen.toBool();
settings.set |= AnimationSettings::FullScreen;
} else {
settings.fullScreenEffect = false;
}
const QJSValue keepAlive = object.property(QStringLiteral("keepAlive"));
if (keepAlive.isBool()) {
settings.keepAlive = keepAlive.toBool();
settings.set |= AnimationSettings::KeepAlive;
} else {
settings.keepAlive = true;
}
const QJSValue frozenTime = object.property(QStringLiteral("frozenTime"));
if (frozenTime.isNumber()) {
settings.frozenTime = frozenTime.toInt();
settings.set |= AnimationSettings::FrozenTime;
} else {
settings.frozenTime = -1;
}
if (const auto shader = object.property(QStringLiteral("fragmentShader")); shader.isNumber()) {
settings.shader = shader.toUInt();
}
return settings;
}
static KWin::FPx2 fpx2FromScriptValue(const QJSValue &value)
{
if (value.isNull()) {
return FPx2();
}
if (value.isNumber()) {
return FPx2(value.toNumber());
}
if (value.isObject()) {
const QJSValue value1 = value.property(QStringLiteral("value1"));
const QJSValue value2 = value.property(QStringLiteral("value2"));
if (!value1.isNumber() || !value2.isNumber()) {
qCDebug(KWIN_SCRIPTING) << "Cannot cast scripted FPx2 to C++";
return FPx2();
}
return FPx2(value1.toNumber(), value2.toNumber());
}
return FPx2();
}
ScriptedEffect *ScriptedEffect::create(const KPluginMetaData &effect)
{
const QString name = effect.pluginId();
const QString scriptFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
QLatin1String("kwin/effects/") + name + QLatin1String("/contents/code/main.js"));
if (scriptFile.isEmpty()) {
qCDebug(KWIN_SCRIPTING) << "Could not locate effect script" << name;
return nullptr;
}
return ScriptedEffect::create(name, scriptFile, effect.value(QStringLiteral("X-KDE-Ordering"), 0), effect.value(QStringLiteral("X-KWin-Exclusive-Category")));
}
ScriptedEffect *ScriptedEffect::create(const QString &effectName, const QString &pathToScript, int chainPosition, const QString &exclusiveCategory)
{
ScriptedEffect *effect = new ScriptedEffect();
effect->m_exclusiveCategory = exclusiveCategory;
if (!effect->init(effectName, pathToScript)) {
delete effect;
return nullptr;
}
effect->m_chainPosition = chainPosition;
return effect;
}
bool ScriptedEffect::supported()
{
return effects->animationsSupported();
}
ScriptedEffect::ScriptedEffect()
: AnimationEffect()
, m_engine(new QJSEngine(this))
, m_scriptFile(QString())
, m_config(nullptr)
, m_chainPosition(0)
{
Q_ASSERT(effects);
connect(effects, &EffectsHandler::activeFullScreenEffectChanged, this, [this]() {
Effect *fullScreenEffect = effects->activeFullScreenEffect();
if (fullScreenEffect == m_activeFullScreenEffect) {
return;
}
if (m_activeFullScreenEffect == this || fullScreenEffect == this) {
Q_EMIT isActiveFullScreenEffectChanged();
}
m_activeFullScreenEffect = fullScreenEffect;
});
}
ScriptedEffect::~ScriptedEffect() = default;
bool ScriptedEffect::init(const QString &effectName, const QString &pathToScript)
{
qRegisterMetaType<QJSValueList>();
qRegisterMetaType<QList<KWin::EffectWindow *>>();
QFile scriptFile(pathToScript);
if (!scriptFile.open(QIODevice::ReadOnly)) {
qCDebug(KWIN_SCRIPTING) << "Could not open script file: " << pathToScript;
return false;
}
m_effectName = effectName;
m_scriptFile = pathToScript;
// does the effect contain an KConfigXT file?
const QString kconfigXTFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kwin/effects/") + m_effectName + QLatin1String("/contents/config/main.xml"));
if (!kconfigXTFile.isNull()) {
KConfigGroup cg = QCoreApplication::instance()->property("config").value<KSharedConfigPtr>()->group(QStringLiteral("Effect-%1").arg(m_effectName));
QFile xmlFile(kconfigXTFile);
m_config = new KConfigLoader(cg, &xmlFile, this);
m_config->load();
}
m_engine->installExtensions(QJSEngine::ConsoleExtension);
QJSValue globalObject = m_engine->globalObject();
QJSValue effectsObject = m_engine->newQObject(effects);
QJSEngine::setObjectOwnership(effects, QJSEngine::CppOwnership);
globalObject.setProperty(QStringLiteral("effects"), effectsObject);
QJSValue selfObject = m_engine->newQObject(this);
QJSEngine::setObjectOwnership(this, QJSEngine::CppOwnership);
globalObject.setProperty(QStringLiteral("effect"), selfObject);
globalObject.setProperty(QStringLiteral("Effect"),
m_engine->newQMetaObject(&ScriptedEffect::staticMetaObject));
globalObject.setProperty(QStringLiteral("KWin"),
m_engine->newQMetaObject(&QtScriptWorkspaceWrapper::staticMetaObject));
globalObject.setProperty(QStringLiteral("Globals"),
m_engine->newQMetaObject(&KWin::staticMetaObject));
globalObject.setProperty(QStringLiteral("QEasingCurve"),
m_engine->newQMetaObject(&QEasingCurve::staticMetaObject));
static const QStringList globalProperties{
QStringLiteral("animationTime"),
QStringLiteral("displayWidth"),
QStringLiteral("displayHeight"),
QStringLiteral("registerShortcut"),
QStringLiteral("registerScreenEdge"),
QStringLiteral("registerRealtimeScreenEdge"),
QStringLiteral("registerTouchScreenEdge"),
QStringLiteral("unregisterScreenEdge"),
QStringLiteral("unregisterTouchScreenEdge"),
QStringLiteral("animate"),
QStringLiteral("set"),
QStringLiteral("retarget"),
QStringLiteral("freezeInTime"),
QStringLiteral("redirect"),
QStringLiteral("complete"),
QStringLiteral("cancel"),
QStringLiteral("addShader"),
QStringLiteral("setUniform"),
};
for (const QString &propertyName : globalProperties) {
globalObject.setProperty(propertyName, selfObject.property(propertyName));
}
const QJSValue result = m_engine->evaluate(QString::fromUtf8(scriptFile.readAll()));
if (result.isError()) {
qCWarning(KWIN_SCRIPTING, "%s:%d: error: %s", qPrintable(scriptFile.fileName()),
result.property(QStringLiteral("lineNumber")).toInt(),
qPrintable(result.property(QStringLiteral("message")).toString()));
return false;
}
return true;
}
void ScriptedEffect::animationEnded(KWin::EffectWindow *w, Attribute a, uint meta)
{
AnimationEffect::animationEnded(w, a, meta);
Q_EMIT animationEnded(w, 0);
}
QString ScriptedEffect::pluginId() const
{
return m_effectName;
}
bool ScriptedEffect::isActiveFullScreenEffect() const
{
return effects->activeFullScreenEffect() == this;
}
QList<int> ScriptedEffect::touchEdgesForAction(const QString &action) const
{
QList<int> ret;
if (m_exclusiveCategory == QLatin1StringView("show-desktop") && action == QLatin1StringView("show-desktop")) {
for (const auto b : {ElectricTop, ElectricRight, ElectricBottom, ElectricLeft}) {
if (workspace()->screenEdges()->actionForTouchBorder(b) == ElectricActionShowDesktop) {
ret.append(b);
}
}
return ret;
} else {
if (!m_config) {
return ret;
}
return m_config->property(QStringLiteral("TouchBorderActivate") + action).value<QList<int>>();
}
}
QJSValue ScriptedEffect::animate_helper(const QJSValue &object, AnimationType animationType)
{
QJSValue windowProperty = object.property(QStringLiteral("window"));
if (!windowProperty.isObject()) {
m_engine->throwError(QStringLiteral("Window property missing in animation options"));
return QJSValue();
}
EffectWindow *window = qobject_cast<EffectWindow *>(windowProperty.toQObject());
if (!window) {
m_engine->throwError(QStringLiteral("Window property references invalid window"));
return QJSValue();
}
QList<AnimationSettings> settings{animationSettingsFromObject(object)}; // global
QJSValue animations = object.property(QStringLiteral("animations")); // array
if (!animations.isUndefined()) {
if (!animations.isArray()) {
m_engine->throwError(QStringLiteral("Animations provided but not an array"));
return QJSValue();
}
const int length = static_cast<int>(animations.property(QStringLiteral("length")).toInt());
for (int i = 0; i < length; ++i) {
QJSValue value = animations.property(QString::number(i));
if (value.isObject()) {
AnimationSettings s = animationSettingsFromObject(value);
const uint set = s.set | settings.at(0).set;
// Catch show stoppers (incompletable animation)
if (!(set & AnimationSettings::Type)) {
m_engine->throwError(QStringLiteral("Type property missing in animation options"));
return QJSValue();
}
if (!(set & AnimationSettings::Duration)) {
m_engine->throwError(QStringLiteral("Duration property missing in animation options"));
return QJSValue();
}
// Complete local animations from global settings
if (!(s.set & AnimationSettings::Duration)) {
s.duration = settings.at(0).duration;
}
if (!(s.set & AnimationSettings::Curve)) {
s.curve = settings.at(0).curve;
}
if (!(s.set & AnimationSettings::Delay)) {
s.delay = settings.at(0).delay;
}
if (!(s.set & AnimationSettings::FullScreen)) {
s.fullScreenEffect = settings.at(0).fullScreenEffect;
}
if (!(s.set & AnimationSettings::KeepAlive)) {
s.keepAlive = settings.at(0).keepAlive;
}
if (!s.shader.has_value()) {
s.shader = settings.at(0).shader;
}
s.metaData = 0;
typedef QMap<AnimationEffect::MetaType, QString> MetaTypeMap;
static MetaTypeMap metaTypes({{AnimationEffect::SourceAnchor, QStringLiteral("sourceAnchor")},
{AnimationEffect::TargetAnchor, QStringLiteral("targetAnchor")},
{AnimationEffect::RelativeSourceX, QStringLiteral("relativeSourceX")},
{AnimationEffect::RelativeSourceY, QStringLiteral("relativeSourceY")},
{AnimationEffect::RelativeTargetX, QStringLiteral("relativeTargetX")},
{AnimationEffect::RelativeTargetY, QStringLiteral("relativeTargetY")},
{AnimationEffect::Axis, QStringLiteral("axis")}});
for (auto it = metaTypes.constBegin(),
end = metaTypes.constEnd();
it != end; ++it) {
QJSValue metaVal = value.property(*it);
if (metaVal.isNumber()) {
AnimationEffect::setMetaData(it.key(), metaVal.toInt(), s.metaData);
}
}
if (s.type == ShaderUniform && s.shader) {
auto uniformProperty = value.property(QStringLiteral("uniform")).toString();
auto shader = findShader(s.shader.value());
if (!shader) {
m_engine->throwError(QStringLiteral("Shader for given shaderId not found"));
return {};
}
if (!effects->makeOpenGLContextCurrent()) {
m_engine->throwError(QStringLiteral("Failed to make OpenGL context current"));
return {};
}
ShaderBinder binder{shader};
s.metaData = shader->uniformLocation(uniformProperty.toUtf8().constData());
}
settings << s;
}
}
}
if (settings.count() == 1) {
const uint set = settings.at(0).set;
if (!(set & AnimationSettings::Type)) {
m_engine->throwError(QStringLiteral("Type property missing in animation options"));
return QJSValue();
}
if (!(set & AnimationSettings::Duration)) {
m_engine->throwError(QStringLiteral("Duration property missing in animation options"));
return QJSValue();
}
} else if (!(settings.at(0).set & AnimationSettings::Type)) { // invalid global
settings.removeAt(0); // -> get rid of it, only used to complete the others
}
if (settings.isEmpty()) {
m_engine->throwError(QStringLiteral("No animations provided"));
return QJSValue();
}
QJSValue array = m_engine->newArray(settings.length());
for (int i = 0; i < settings.count(); i++) {
const AnimationSettings &setting = settings[i];
int animationId;
if (animationType == AnimationType::Set) {
animationId = set(window,
setting.type,
setting.duration,
setting.to,
setting.from,
setting.metaData,
setting.curve,
setting.delay,
setting.fullScreenEffect,
setting.keepAlive,
setting.shader ? setting.shader.value() : 0u);
if (setting.frozenTime >= 0) {
freezeInTime(animationId, setting.frozenTime);
}
} else {
animationId = animate(window,
setting.type,
setting.duration,
setting.to,
setting.from,
setting.metaData,
setting.curve,
setting.delay,
setting.fullScreenEffect,
setting.keepAlive,
setting.shader ? setting.shader.value() : 0u);
if (setting.frozenTime >= 0) {
freezeInTime(animationId, setting.frozenTime);
}
}
array.setProperty(i, animationId);
}
return array;
}
quint64 ScriptedEffect::animate(KWin::EffectWindow *window, KWin::AnimationEffect::Attribute attribute,
int ms, const QJSValue &to, const QJSValue &from, uint metaData, int curve,
int delay, bool fullScreen, bool keepAlive, uint shaderId)
{
QEasingCurve qec;
if (curve < QEasingCurve::Custom) {
qec.setType(static_cast<QEasingCurve::Type>(curve));
} else if (curve == GaussianCurve) {
qec.setCustomType(qecGaussian);
}
return AnimationEffect::animate(window, attribute, metaData, ms, fpx2FromScriptValue(to), qec,
delay, fpx2FromScriptValue(from), fullScreen, keepAlive, findShader(shaderId));
}
QJSValue ScriptedEffect::animate(const QJSValue &object)
{
return animate_helper(object, AnimationType::Animate);
}
quint64 ScriptedEffect::set(KWin::EffectWindow *window, KWin::AnimationEffect::Attribute attribute,
int ms, const QJSValue &to, const QJSValue &from, uint metaData, int curve,
int delay, bool fullScreen, bool keepAlive, uint shaderId)
{
QEasingCurve qec;
if (curve < QEasingCurve::Custom) {
qec.setType(static_cast<QEasingCurve::Type>(curve));
} else if (curve == GaussianCurve) {
qec.setCustomType(qecGaussian);
}
return AnimationEffect::set(window, attribute, metaData, ms, fpx2FromScriptValue(to), qec,
delay, fpx2FromScriptValue(from), fullScreen, keepAlive, findShader(shaderId));
}
QJSValue ScriptedEffect::set(const QJSValue &object)
{
return animate_helper(object, AnimationType::Set);
}
bool ScriptedEffect::retarget(quint64 animationId, const QJSValue &newTarget, int newRemainingTime)
{
return AnimationEffect::retarget(animationId, fpx2FromScriptValue(newTarget), newRemainingTime);
}
bool ScriptedEffect::retarget(const QList<quint64> &animationIds, const QJSValue &newTarget, int newRemainingTime)
{
return std::all_of(animationIds.begin(), animationIds.end(), [&](quint64 animationId) {
return retarget(animationId, newTarget, newRemainingTime);
});
}
bool ScriptedEffect::freezeInTime(quint64 animationId, qint64 frozenTime)
{
return AnimationEffect::freezeInTime(animationId, frozenTime);
}
bool ScriptedEffect::freezeInTime(const QList<quint64> &animationIds, qint64 frozenTime)
{
return std::all_of(animationIds.begin(), animationIds.end(), [&](quint64 animationId) {
return AnimationEffect::freezeInTime(animationId, frozenTime);
});
}
bool ScriptedEffect::redirect(quint64 animationId, Direction direction, TerminationFlags terminationFlags)
{
return AnimationEffect::redirect(animationId, direction, terminationFlags);
}
bool ScriptedEffect::redirect(const QList<quint64> &animationIds, Direction direction, TerminationFlags terminationFlags)
{
return std::all_of(animationIds.begin(), animationIds.end(), [&](quint64 animationId) {
return redirect(animationId, direction, terminationFlags);
});
}
bool ScriptedEffect::complete(quint64 animationId)
{
return AnimationEffect::complete(animationId);
}
bool ScriptedEffect::complete(const QList<quint64> &animationIds)
{
return std::all_of(animationIds.begin(), animationIds.end(), [&](quint64 animationId) {
return complete(animationId);
});
}
bool ScriptedEffect::cancel(quint64 animationId)
{
return AnimationEffect::cancel(animationId);
}
bool ScriptedEffect::cancel(const QList<quint64> &animationIds)
{
bool ret = false;
for (const quint64 &animationId : animationIds) {
ret |= cancel(animationId);
}
return ret;
}
bool ScriptedEffect::isGrabbed(EffectWindow *w, ScriptedEffect::DataRole grabRole)
{
void *e = w->data(static_cast<KWin::DataRole>(grabRole)).value<void *>();
if (e) {
return e != this;
} else {
return false;
}
}
bool ScriptedEffect::grab(EffectWindow *w, DataRole grabRole, bool force)
{
void *grabber = w->data(grabRole).value<void *>();
if (grabber == this) {
return true;
}
if (grabber != nullptr && grabber != this && !force) {
return false;
}
w->setData(grabRole, QVariant::fromValue(static_cast<void *>(this)));
return true;
}
bool ScriptedEffect::ungrab(EffectWindow *w, DataRole grabRole)
{
void *grabber = w->data(grabRole).value<void *>();
if (grabber == nullptr) {
return true;
}
if (grabber != this) {
return false;
}
w->setData(grabRole, QVariant());
return true;
}
void ScriptedEffect::reconfigure(ReconfigureFlags flags)
{
AnimationEffect::reconfigure(flags);
if (m_config) {
m_config->read();
}
Q_EMIT configChanged();
}
void ScriptedEffect::registerShortcut(const QString &objectName, const QString &text,
const QString &keySequence, const QJSValue &callback)
{
if (!callback.isCallable()) {
m_engine->throwError(QStringLiteral("Shortcut handler must be callable"));
return;
}
QAction *action = new QAction(this);
action->setObjectName(objectName);
action->setText(text);
const QKeySequence shortcut = QKeySequence(keySequence);
KGlobalAccel::self()->setShortcut(action, QList<QKeySequence>() << shortcut);
connect(action, &QAction::triggered, this, [this, action, callback]() {
QJSValue actionObject = m_engine->newQObject(action);
QJSEngine::setObjectOwnership(action, QJSEngine::CppOwnership);
QJSValue(callback).call(QJSValueList{actionObject});
});
}
bool ScriptedEffect::borderActivated(ElectricBorder edge)
{
auto it = screenEdgeCallbacks().constFind(edge);
if (it != screenEdgeCallbacks().constEnd()) {
for (const QJSValue &callback : it.value()) {
QJSValue(callback).call();
}
}
return true;
}
QJSValue ScriptedEffect::readConfig(const QString &key, const QJSValue &defaultValue)
{
if (!m_config) {
return defaultValue;
}
return m_engine->toScriptValue(m_config->property(key));
}
int ScriptedEffect::displayWidth() const
{
return workspace()->geometry().width();
}
int ScriptedEffect::displayHeight() const
{
return workspace()->geometry().height();
}
int ScriptedEffect::animationTime(int defaultTime) const
{
return Effect::animationTime(std::chrono::milliseconds(defaultTime));
}
bool ScriptedEffect::registerScreenEdge(int edge, const QJSValue &callback)
{
if (!callback.isCallable()) {
m_engine->throwError(QStringLiteral("Screen edge handler must be callable"));
return false;
}
auto it = screenEdgeCallbacks().find(edge);
if (it == screenEdgeCallbacks().end()) {
// not yet registered
workspace()->screenEdges()->reserve(static_cast<KWin::ElectricBorder>(edge), this, "borderActivated");
screenEdgeCallbacks().insert(edge, QJSValueList{callback});
} else {
it->append(callback);
}
return true;
}
bool ScriptedEffect::registerRealtimeScreenEdge(int edge, const QJSValue &callback)
{
if (!callback.isCallable()) {
m_engine->throwError(QStringLiteral("Screen edge handler must be callable"));
return false;
}
auto it = realtimeScreenEdgeCallbacks().find(edge);
if (it == realtimeScreenEdgeCallbacks().end()) {
// not yet registered
realtimeScreenEdgeCallbacks().insert(edge, QJSValueList{callback});
auto *triggerAction = new QAction(this);
connect(triggerAction, &QAction::triggered, this, [this, edge]() {
auto it = realtimeScreenEdgeCallbacks().constFind(edge);
if (it != realtimeScreenEdgeCallbacks().constEnd()) {
for (const QJSValue &callback : it.value()) {
QJSValue(callback).call({edge});
}
}
});
effects->registerRealtimeTouchBorder(static_cast<KWin::ElectricBorder>(edge), triggerAction, [this](ElectricBorder border, const QPointF &deltaProgress, Output *screen) {
auto it = realtimeScreenEdgeCallbacks().constFind(border);
if (it != realtimeScreenEdgeCallbacks().constEnd()) {
for (const QJSValue &callback : it.value()) {
QJSValue delta = m_engine->newObject();
delta.setProperty("width", deltaProgress.x());
delta.setProperty("height", deltaProgress.y());
QJSValue(callback).call({border, QJSValue(delta), m_engine->newQObject(screen)});
}
}
});
} else {
it->append(callback);
}
return true;
}
bool ScriptedEffect::unregisterScreenEdge(int edge)
{
auto it = screenEdgeCallbacks().find(edge);
if (it == screenEdgeCallbacks().end()) {
// not previously registered
return false;
}
workspace()->screenEdges()->unreserve(static_cast<KWin::ElectricBorder>(edge), this);
screenEdgeCallbacks().erase(it);
return true;
}
bool ScriptedEffect::registerTouchScreenEdge(int edge, const QJSValue &callback)
{
if (m_touchScreenEdgeCallbacks.constFind(edge) != m_touchScreenEdgeCallbacks.constEnd()) {
return false;
}
if (!callback.isCallable()) {
m_engine->throwError(QStringLiteral("Touch screen edge handler must be callable"));
return false;
}
QAction *action = new QAction(this);
connect(action, &QAction::triggered, this, [callback]() {
QJSValue(callback).call();
});
workspace()->screenEdges()->reserveTouch(KWin::ElectricBorder(edge), action);
m_touchScreenEdgeCallbacks.insert(edge, action);
return true;
}
bool ScriptedEffect::unregisterTouchScreenEdge(int edge)
{
auto it = m_touchScreenEdgeCallbacks.find(edge);
if (it == m_touchScreenEdgeCallbacks.end()) {
return false;
}
delete it.value();
m_touchScreenEdgeCallbacks.erase(it);
return true;
}
QJSEngine *ScriptedEffect::engine() const
{
return m_engine;
}
uint ScriptedEffect::addFragmentShader(ShaderTrait traits, const QString &fragmentShaderFile)
{
if (!effects->makeOpenGLContextCurrent()) {
m_engine->throwError(QStringLiteral("Failed to make OpenGL context current"));
return 0;
}
const QString shaderDir{QLatin1String("kwin/effects/") + m_effectName + QLatin1String("/contents/shaders/")};
const QString fragment = fragmentShaderFile.isEmpty() ? QString{} : QStandardPaths::locate(QStandardPaths::GenericDataLocation, shaderDir + fragmentShaderFile);
auto shader = ShaderManager::instance()->generateShaderFromFile(static_cast<KWin::ShaderTraits>(int(traits)), {}, fragment);
if (!shader->isValid()) {
m_engine->throwError(QStringLiteral("Shader failed to load"));
// 0 is never a valid shader identifier, it's ensured the first shader gets id 1
return 0;
}
const uint shaderId{m_nextShaderId};
m_nextShaderId++;
m_shaders[shaderId] = std::move(shader);
return shaderId;
}
GLShader *ScriptedEffect::findShader(uint shaderId) const
{
if (auto it = m_shaders.find(shaderId); it != m_shaders.end()) {
return it->second.get();
}
return nullptr;
}
void ScriptedEffect::setUniform(uint shaderId, const QString &name, const QJSValue &value)
{
auto shader = findShader(shaderId);
if (!shader) {
m_engine->throwError(QStringLiteral("Shader for given shaderId not found"));
return;
}
if (!effects->makeOpenGLContextCurrent()) {
m_engine->throwError(QStringLiteral("Failed to make OpenGL context current"));
return;
}
auto setColorUniform = [this, shader, name](const QColor &color) {
if (!color.isValid()) {
return;
}
if (!shader->setUniform(name.toUtf8().constData(), color)) {
m_engine->throwError(QStringLiteral("Failed to set uniform ") + name);
}
};
ShaderBinder binder{shader};
if (value.isString()) {
setColorUniform(value.toString());
} else if (value.isNumber()) {
if (!shader->setUniform(name.toUtf8().constData(), float(value.toNumber()))) {
m_engine->throwError(QStringLiteral("Failed to set uniform ") + name);
}
} else if (value.isArray()) {
const auto length = value.property(QStringLiteral("length")).toInt();
if (length == 2) {
if (!shader->setUniform(name.toUtf8().constData(), QVector2D{float(value.property(0).toNumber()), float(value.property(1).toNumber())})) {
m_engine->throwError(QStringLiteral("Failed to set uniform ") + name);
}
} else if (length == 3) {
if (!shader->setUniform(name.toUtf8().constData(), QVector3D{float(value.property(0).toNumber()), float(value.property(1).toNumber()), float(value.property(2).toNumber())})) {
m_engine->throwError(QStringLiteral("Failed to set uniform ") + name);
}
} else if (length == 4) {
if (!shader->setUniform(name.toUtf8().constData(), QVector4D{float(value.property(0).toNumber()), float(value.property(1).toNumber()), float(value.property(2).toNumber()), float(value.property(3).toNumber())})) {
m_engine->throwError(QStringLiteral("Failed to set uniform ") + name);
}
} else {
m_engine->throwError(QStringLiteral("Invalid number of elements in array"));
}
} else if (value.isVariant()) {
const auto variant = value.toVariant();
setColorUniform(variant.value<QColor>());
} else {
m_engine->throwError(QStringLiteral("Invalid value provided for uniform"));
}
}
} // namespace
#include "moc_scriptedeffect.cpp"
@@ -0,0 +1,224 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2012 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2018 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "effect/animationeffect.h"
#include <QJSEngine>
#include <QJSValue>
class KConfigLoader;
class KPluginMetaData;
class QAction;
namespace KWin
{
class KWIN_EXPORT ScriptedEffect : public KWin::AnimationEffect
{
Q_OBJECT
Q_ENUMS(DataRole)
Q_ENUMS(Qt::Axis)
Q_ENUMS(Anchor)
Q_ENUMS(MetaType)
Q_ENUMS(EasingCurve)
Q_ENUMS(SessionState)
Q_ENUMS(ElectricBorder)
Q_ENUMS(ShaderTrait)
/**
* The plugin ID of the effect
*/
Q_PROPERTY(QString pluginId READ pluginId CONSTANT)
/**
* True if we are the active fullscreen effect
*/
Q_PROPERTY(bool isActiveFullScreenEffect READ isActiveFullScreenEffect NOTIFY isActiveFullScreenEffectChanged)
public:
// copied from effecthandler.h
enum DataRole {
// Grab roles are used to force all other animations to ignore the window.
// The value of the data is set to the Effect's `this` value.
WindowAddedGrabRole = 1,
WindowClosedGrabRole,
WindowMinimizedGrabRole,
WindowUnminimizedGrabRole,
WindowForceBlurRole, ///< For fullscreen effects to enforce blurring of windows,
WindowForceBackgroundContrastRole, ///< For fullscreen effects to enforce the background contrast,
};
enum EasingCurve {
GaussianCurve = 128
};
// copied from kwinglutils.h
enum class ShaderTrait {
MapTexture = (1 << 0),
UniformColor = (1 << 1),
Modulate = (1 << 2),
AdjustSaturation = (1 << 3),
};
const QString &scriptFile() const
{
return m_scriptFile;
}
void reconfigure(ReconfigureFlags flags) override;
int requestedEffectChainPosition() const override
{
return m_chainPosition;
}
QString activeConfig() const;
void setActiveConfig(const QString &name);
static ScriptedEffect *create(const QString &effectName, const QString &pathToScript, int chainPosition, const QString &exclusiveCategory);
static ScriptedEffect *create(const KPluginMetaData &effect);
static bool supported();
~ScriptedEffect() override;
/**
* Whether another effect has grabbed the @p w with the given @p grabRole.
* @param w The window to check
* @param grabRole The grab role to check
* @returns @c true if another window has grabbed the effect, @c false otherwise
*/
Q_SCRIPTABLE bool isGrabbed(KWin::EffectWindow *w, DataRole grabRole);
/**
* Grabs the window with the specified role.
*
* @param w The window.
* @param grabRole The grab role.
* @param force By default, if the window is already grabbed by another effect,
* then that window won't be grabbed by effect that called this method. If you
* would like to grab a window even if it's grabbed by another effect, then
* pass @c true.
* @returns @c true if the window was grabbed successfully, otherwise @c false.
*/
Q_SCRIPTABLE bool grab(KWin::EffectWindow *w, DataRole grabRole, bool force = false);
/**
* Ungrabs the window with the specified role.
*
* @param w The window.
* @param grabRole The grab role.
* @returns @c true if the window was ungrabbed successfully, otherwise @c false.
*/
Q_SCRIPTABLE bool ungrab(KWin::EffectWindow *w, DataRole grabRole);
/**
* Reads the value from the configuration data for the given key.
* @param key The key to search for
* @param defaultValue The value to return if the key is not found
* @returns The config value if present
*/
Q_SCRIPTABLE QJSValue readConfig(const QString &key, const QJSValue &defaultValue = QJSValue());
Q_SCRIPTABLE int displayWidth() const;
Q_SCRIPTABLE int displayHeight() const;
Q_SCRIPTABLE int animationTime(int defaultTime) const;
Q_SCRIPTABLE void registerShortcut(const QString &objectName, const QString &text,
const QString &keySequence, const QJSValue &callback);
Q_SCRIPTABLE bool registerScreenEdge(int edge, const QJSValue &callback);
Q_SCRIPTABLE bool registerRealtimeScreenEdge(int edge, const QJSValue &callback);
Q_SCRIPTABLE bool unregisterScreenEdge(int edge);
Q_SCRIPTABLE bool registerTouchScreenEdge(int edge, const QJSValue &callback);
Q_SCRIPTABLE bool unregisterTouchScreenEdge(int edge);
Q_SCRIPTABLE quint64 animate(KWin::EffectWindow *window, Attribute attribute, int ms,
const QJSValue &to, const QJSValue &from = QJSValue(),
uint metaData = 0, int curve = QEasingCurve::Linear, int delay = 0,
bool fullScreen = false, bool keepAlive = true, uint shaderId = 0);
Q_SCRIPTABLE QJSValue animate(const QJSValue &object);
Q_SCRIPTABLE quint64 set(KWin::EffectWindow *window, Attribute attribute, int ms,
const QJSValue &to, const QJSValue &from = QJSValue(),
uint metaData = 0, int curve = QEasingCurve::Linear, int delay = 0,
bool fullScreen = false, bool keepAlive = true, uint shaderId = 0);
Q_SCRIPTABLE QJSValue set(const QJSValue &object);
Q_SCRIPTABLE bool retarget(quint64 animationId, const QJSValue &newTarget,
int newRemainingTime = -1);
Q_SCRIPTABLE bool retarget(const QList<quint64> &animationIds, const QJSValue &newTarget,
int newRemainingTime = -1);
Q_SCRIPTABLE bool freezeInTime(quint64 animationId, qint64 frozenTime);
Q_SCRIPTABLE bool freezeInTime(const QList<quint64> &animationIds, qint64 frozenTime);
Q_SCRIPTABLE bool redirect(quint64 animationId, Direction direction,
TerminationFlags terminationFlags = TerminateAtSource);
Q_SCRIPTABLE bool redirect(const QList<quint64> &animationIds, Direction direction,
TerminationFlags terminationFlags = TerminateAtSource);
Q_SCRIPTABLE bool complete(quint64 animationId);
Q_SCRIPTABLE bool complete(const QList<quint64> &animationIds);
Q_SCRIPTABLE bool cancel(quint64 animationId);
Q_SCRIPTABLE bool cancel(const QList<quint64> &animationIds);
Q_SCRIPTABLE QList<int> touchEdgesForAction(const QString &action) const;
Q_SCRIPTABLE uint addFragmentShader(ShaderTrait traits, const QString &fragmentShaderFile = {});
Q_SCRIPTABLE void setUniform(uint shaderId, const QString &name, const QJSValue &value);
QHash<int, QJSValueList> &screenEdgeCallbacks()
{
return m_screenEdgeCallbacks;
}
QHash<int, QJSValueList> &realtimeScreenEdgeCallbacks()
{
return m_realtimeScreenEdgeCallbacks;
}
QString pluginId() const;
bool isActiveFullScreenEffect() const;
public Q_SLOTS:
bool borderActivated(ElectricBorder border) override;
Q_SIGNALS:
/**
* Signal emitted whenever the effect's config changed.
*/
void configChanged();
void animationEnded(KWin::EffectWindow *w, quint64 animationId);
void isActiveFullScreenEffectChanged();
protected:
ScriptedEffect();
QJSEngine *engine() const;
bool init(const QString &effectName, const QString &pathToScript);
void animationEnded(KWin::EffectWindow *w, Attribute a, uint meta) override;
private:
enum class AnimationType {
Animate,
Set
};
QJSValue animate_helper(const QJSValue &object, AnimationType animationType);
GLShader *findShader(uint shaderId) const;
QJSEngine *m_engine;
QString m_effectName;
QString m_scriptFile;
QString m_exclusiveCategory;
QHash<int, QJSValueList> m_screenEdgeCallbacks;
QHash<int, QJSValueList> m_realtimeScreenEdgeCallbacks;
KConfigLoader *m_config;
int m_chainPosition;
QHash<int, QAction *> m_touchScreenEdgeCallbacks;
Effect *m_activeFullScreenEffect = nullptr;
std::map<uint, std::unique_ptr<GLShader>> m_shaders;
uint m_nextShaderId{1u};
};
}
@@ -0,0 +1,132 @@
/*
SPDX-FileCopyrightText: 2023 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "scripting/scriptedquicksceneeffect.h"
#include "main.h"
#include <KConfigGroup>
#include <KConfigLoader>
#include <QFile>
namespace KWin
{
ScriptedQuickSceneEffect::ScriptedQuickSceneEffect()
{
m_visibleTimer.setSingleShot(true);
connect(&m_visibleTimer, &QTimer::timeout, this, [this]() {
setRunning(false);
});
}
ScriptedQuickSceneEffect::~ScriptedQuickSceneEffect()
{
}
void ScriptedQuickSceneEffect::reconfigure(ReconfigureFlags flags)
{
m_configLoader->load();
Q_EMIT m_configLoader->configChanged();
}
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<QObject> ScriptedQuickSceneEffect::data()
{
return QQmlListProperty<QObject>(this, nullptr,
data_append,
data_count,
data_at,
data_clear);
}
void ScriptedQuickSceneEffect::data_append(QQmlListProperty<QObject> *objects, QObject *object)
{
if (!object) {
return;
}
ScriptedQuickSceneEffect *effect = static_cast<ScriptedQuickSceneEffect *>(objects->object);
if (!effect->m_children.contains(object)) {
object->setParent(effect);
effect->m_children.append(object);
}
}
qsizetype ScriptedQuickSceneEffect::data_count(QQmlListProperty<QObject> *objects)
{
ScriptedQuickSceneEffect *effect = static_cast<ScriptedQuickSceneEffect *>(objects->object);
return effect->m_children.count();
}
QObject *ScriptedQuickSceneEffect::data_at(QQmlListProperty<QObject> *objects, qsizetype index)
{
ScriptedQuickSceneEffect *effect = static_cast<ScriptedQuickSceneEffect *>(objects->object);
return effect->m_children.value(index);
}
void ScriptedQuickSceneEffect::data_clear(QQmlListProperty<QObject> *objects)
{
ScriptedQuickSceneEffect *effect = static_cast<ScriptedQuickSceneEffect *>(objects->object);
while (!effect->m_children.isEmpty()) {
QObject *child = effect->m_children.takeLast();
child->setParent(nullptr);
}
}
} // namespace KWin
#include "moc_scriptedquicksceneeffect.cpp"
@@ -0,0 +1,94 @@
/*
SPDX-FileCopyrightText: 2023 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "effect/quickeffect.h"
#include <KConfigPropertyMap>
#include <QTimer>
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<QObject> 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);
void reconfigure(ReconfigureFlags flags) override;
int requestedEffectChainPosition() const override;
bool isVisible() const;
void setVisible(bool visible);
QQmlListProperty<QObject> data();
KConfigPropertyMap *configuration() const;
static void data_append(QQmlListProperty<QObject> *objects, QObject *object);
static qsizetype data_count(QQmlListProperty<QObject> *objects);
static QObject *data_at(QQmlListProperty<QObject> *objects, qsizetype index);
static void data_clear(QQmlListProperty<QObject> *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
@@ -0,0 +1,861 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2010 Rohan Prabhu <rohan@rohanprabhu.com>
SPDX-FileCopyrightText: 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "scripting.h"
// own
#include "dbuscall.h"
#if KWIN_BUILD_QTQUICK
#include "desktopbackgrounditem.h"
#include "effect/quickeffect.h"
#endif
#include "gesturehandler.h"
#include "screenedgehandler.h"
#if KWIN_BUILD_QTQUICK
#include "scriptedquicksceneeffect.h"
#endif
#include "scripting_logging.h"
#include "scriptingutils.h"
#include "shortcuthandler.h"
#include "virtualdesktopmodel.h"
#include "windowmodel.h"
#if KWIN_BUILD_QTQUICK
#include "windowthumbnailitem.h"
#endif
#include "workspace_wrapper.h"
#include "core/output.h"
#include "input.h"
#include "options.h"
#include "screenedge.h"
#include "tiles/tilemanager.h"
#include "virtualdesktops.h"
#include "window.h"
#include "workspace.h"
// KDE
#include <KConfigGroup>
#include <KConfigPropertyMap>
#include <KGlobalAccel>
#include <KLocalizedContext>
#include <KLocalizedQmlContext>
#include <KPackage/PackageLoader>
// Qt
#include <QDBusConnection>
#include <QDBusPendingCallWatcher>
#include <QDebug>
#include <QFutureWatcher>
#include <QMenu>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQmlExpression>
#include <QSettings>
#include <QStandardPaths>
#include <QtConcurrentRun>
#include "scriptadaptor.h"
static QRect scriptValueToRect(const QJSValue &value)
{
return QRect(value.property(QStringLiteral("x")).toInt(),
value.property(QStringLiteral("y")).toInt(),
value.property(QStringLiteral("width")).toInt(),
value.property(QStringLiteral("height")).toInt());
}
static QRectF scriptValueToRectF(const QJSValue &value)
{
return QRectF(value.property(QStringLiteral("x")).toNumber(),
value.property(QStringLiteral("y")).toNumber(),
value.property(QStringLiteral("width")).toNumber(),
value.property(QStringLiteral("height")).toNumber());
}
static QPoint scriptValueToPoint(const QJSValue &value)
{
return QPoint(value.property(QStringLiteral("x")).toInt(),
value.property(QStringLiteral("y")).toInt());
}
static QPointF scriptValueToPointF(const QJSValue &value)
{
return QPointF(value.property(QStringLiteral("x")).toNumber(),
value.property(QStringLiteral("y")).toNumber());
}
static QSize scriptValueToSize(const QJSValue &value)
{
return QSize(value.property(QStringLiteral("width")).toInt(),
value.property(QStringLiteral("height")).toInt());
}
static QSizeF scriptValueToSizeF(const QJSValue &value)
{
return QSizeF(value.property(QStringLiteral("width")).toNumber(),
value.property(QStringLiteral("height")).toNumber());
}
KWin::AbstractScript::AbstractScript(int id, QString scriptName, QString pluginName, QObject *parent)
: QObject(parent)
, m_scriptId(id)
, m_fileName(scriptName)
, m_pluginName(pluginName)
, m_running(false)
{
if (m_pluginName.isNull()) {
m_pluginName = scriptName;
}
new ScriptAdaptor(this);
QDBusConnection::sessionBus().registerObject(QStringLiteral("/Scripting/Script") + QString::number(scriptId()), this, QDBusConnection::ExportAdaptors);
}
KWin::AbstractScript::~AbstractScript()
{
}
KConfigGroup KWin::AbstractScript::config() const
{
return kwinApp()->config()->group(QLatin1String("Script-") + m_pluginName);
}
void KWin::AbstractScript::stop()
{
deleteLater();
}
KWin::ScriptTimer::ScriptTimer(QObject *parent)
: QTimer(parent)
{
}
KWin::Script::Script(int id, QString scriptName, QString pluginName, QObject *parent)
: AbstractScript(id, scriptName, pluginName, parent)
, m_engine(new QJSEngine(this))
, m_starting(false)
{
// TODO: Remove in kwin 6. We have these converters only for compatibility reasons.
if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QRect>()) {
QMetaType::registerConverter<QJSValue, QRect>(scriptValueToRect);
}
if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QRectF>()) {
QMetaType::registerConverter<QJSValue, QRectF>(scriptValueToRectF);
}
if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QPoint>()) {
QMetaType::registerConverter<QJSValue, QPoint>(scriptValueToPoint);
}
if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QPointF>()) {
QMetaType::registerConverter<QJSValue, QPointF>(scriptValueToPointF);
}
if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QSize>()) {
QMetaType::registerConverter<QJSValue, QSize>(scriptValueToSize);
}
if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QSizeF>()) {
QMetaType::registerConverter<QJSValue, QSizeF>(scriptValueToSizeF);
}
}
KWin::Script::~Script()
{
}
void KWin::Script::run()
{
if (running() || m_starting) {
return;
}
if (calledFromDBus()) {
m_invocationContext = message();
setDelayedReply(true);
}
m_starting = true;
QFutureWatcher<QByteArray> *watcher = new QFutureWatcher<QByteArray>(this);
connect(watcher, &QFutureWatcherBase::finished, this, &Script::slotScriptLoadedFromFile);
watcher->setFuture(QtConcurrent::run(&KWin::Script::loadScriptFromFile, this, fileName()));
}
QByteArray KWin::Script::loadScriptFromFile(const QString &fileName)
{
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
return QByteArray();
}
QByteArray result(file.readAll());
return result;
}
void KWin::Script::slotScriptLoadedFromFile()
{
QFutureWatcher<QByteArray> *watcher = dynamic_cast<QFutureWatcher<QByteArray> *>(sender());
if (!watcher) {
// not invoked from a QFutureWatcher
return;
}
if (watcher->result().isNull()) {
// do not load empty script
deleteLater();
watcher->deleteLater();
if (m_invocationContext.type() == QDBusMessage::MethodCallMessage) {
auto reply = m_invocationContext.createErrorReply("org.kde.kwin.Scripting.FileError", QString("Could not open %1").arg(fileName()));
QDBusConnection::sessionBus().send(reply);
m_invocationContext = QDBusMessage();
}
return;
}
// Install console functions (e.g. console.assert(), console.log(), etc).
m_engine->installExtensions(QJSEngine::ConsoleExtension);
// Make the timer visible to QJSEngine.
QJSValue timerMetaObject = m_engine->newQMetaObject(&ScriptTimer::staticMetaObject);
m_engine->globalObject().setProperty("QTimer", timerMetaObject);
// Expose enums.
m_engine->globalObject().setProperty(QStringLiteral("KWin"), m_engine->newQMetaObject(&QtScriptWorkspaceWrapper::staticMetaObject));
// Make the options object visible to QJSEngine.
QJSValue optionsObject = m_engine->newQObject(options);
QJSEngine::setObjectOwnership(options, QJSEngine::CppOwnership);
m_engine->globalObject().setProperty(QStringLiteral("options"), optionsObject);
// Make the workspace visible to QJSEngine.
QJSValue workspaceObject = m_engine->newQObject(Scripting::self()->workspaceWrapper());
QJSEngine::setObjectOwnership(Scripting::self()->workspaceWrapper(), QJSEngine::CppOwnership);
m_engine->globalObject().setProperty(QStringLiteral("workspace"), workspaceObject);
QJSValue self = m_engine->newQObject(this);
QJSEngine::setObjectOwnership(this, QJSEngine::CppOwnership);
static const QStringList globalProperties{
QStringLiteral("readConfig"),
QStringLiteral("callDBus"),
QStringLiteral("registerShortcut"),
QStringLiteral("registerScreenEdge"),
QStringLiteral("unregisterScreenEdge"),
QStringLiteral("registerTouchScreenEdge"),
QStringLiteral("unregisterTouchScreenEdge"),
QStringLiteral("registerUserActionsMenu"),
};
for (const QString &propertyName : globalProperties) {
m_engine->globalObject().setProperty(propertyName, self.property(propertyName));
}
// Inject assertion functions. It would be better to create a module with all
// this assert functions or just deprecate them in favor of console.assert().
QJSValue result = m_engine->evaluate(QStringLiteral(R"(
function assert(condition, message) {
console.assert(condition, message || 'Assertion failed');
}
function assertTrue(condition, message) {
console.assert(condition, message || 'Assertion failed');
}
function assertFalse(condition, message) {
console.assert(!condition, message || 'Assertion failed');
}
function assertNull(value, message) {
console.assert(value === null, message || 'Assertion failed');
}
function assertNotNull(value, message) {
console.assert(value !== null, message || 'Assertion failed');
}
function assertEquals(expected, actual, message) {
console.assert(expected === actual, message || 'Assertion failed');
}
)"));
Q_ASSERT(!result.isError());
result = m_engine->evaluate(QString::fromUtf8(watcher->result()), fileName());
if (result.isError()) {
qCWarning(KWIN_SCRIPTING, "%s:%d: error: %s", qPrintable(fileName()),
result.property(QStringLiteral("lineNumber")).toInt(),
qPrintable(result.property(QStringLiteral("message")).toString()));
deleteLater();
}
if (m_invocationContext.type() == QDBusMessage::MethodCallMessage) {
auto reply = m_invocationContext.createReply();
QDBusConnection::sessionBus().send(reply);
m_invocationContext = QDBusMessage();
}
watcher->deleteLater();
setRunning(true);
m_starting = false;
}
QVariant KWin::Script::readConfig(const QString &key, const QVariant &defaultValue)
{
return config().readEntry(key, defaultValue);
}
void KWin::Script::callDBus(const QString &service, const QString &path, const QString &interface,
const QString &method, const QJSValue &arg1, const QJSValue &arg2,
const QJSValue &arg3, const QJSValue &arg4, const QJSValue &arg5,
const QJSValue &arg6, const QJSValue &arg7, const QJSValue &arg8,
const QJSValue &arg9)
{
QJSValueList jsArguments;
jsArguments.reserve(9);
if (!arg1.isUndefined()) {
jsArguments << arg1;
}
if (!arg2.isUndefined()) {
jsArguments << arg2;
}
if (!arg3.isUndefined()) {
jsArguments << arg3;
}
if (!arg4.isUndefined()) {
jsArguments << arg4;
}
if (!arg5.isUndefined()) {
jsArguments << arg5;
}
if (!arg6.isUndefined()) {
jsArguments << arg6;
}
if (!arg7.isUndefined()) {
jsArguments << arg7;
}
if (!arg8.isUndefined()) {
jsArguments << arg8;
}
if (!arg9.isUndefined()) {
jsArguments << arg9;
}
QJSValue callback;
if (!jsArguments.isEmpty() && jsArguments.last().isCallable()) {
callback = jsArguments.takeLast();
}
QVariantList dbusArguments;
dbusArguments.reserve(jsArguments.count());
for (const QJSValue &jsArgument : std::as_const(jsArguments)) {
dbusArguments << jsArgument.toVariant();
}
QDBusMessage message = QDBusMessage::createMethodCall(service, path, interface, method);
message.setArguments(dbusArguments);
const QDBusPendingCall call = QDBusConnection::sessionBus().asyncCall(message);
if (callback.isUndefined()) {
return;
}
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, callback](QDBusPendingCallWatcher *self) {
self->deleteLater();
if (self->isError()) {
qCWarning(KWIN_SCRIPTING) << "Received D-Bus message is error:" << self->error().message();
return;
}
QJSValueList arguments;
const QVariantList reply = self->reply().arguments();
for (const QVariant &variant : reply) {
arguments << m_engine->toScriptValue(dbusToVariant(variant));
}
QJSValue(callback).call(arguments);
});
}
bool KWin::Script::registerShortcut(const QString &objectName, const QString &text, const QString &keySequence, const QJSValue &callback)
{
if (!callback.isCallable()) {
m_engine->throwError(QStringLiteral("Shortcut handler must be callable"));
return false;
}
QAction *action = new QAction(this);
action->setObjectName(objectName);
action->setText(text);
const QKeySequence shortcut = keySequence;
KGlobalAccel::self()->setShortcut(action, {shortcut});
connect(action, &QAction::triggered, this, [this, action, callback]() {
QJSValue(callback).call({m_engine->toScriptValue(action)});
});
return true;
}
bool KWin::Script::registerScreenEdge(int edge, const QJSValue &callback)
{
if (!callback.isCallable()) {
m_engine->throwError(QStringLiteral("Screen edge handler must be callable"));
return false;
}
QJSValueList &callbacks = m_screenEdgeCallbacks[edge];
if (callbacks.isEmpty()) {
workspace()->screenEdges()->reserve(static_cast<KWin::ElectricBorder>(edge), this, "slotBorderActivated");
}
callbacks << callback;
return true;
}
bool KWin::Script::unregisterScreenEdge(int edge)
{
auto it = m_screenEdgeCallbacks.find(edge);
if (it == m_screenEdgeCallbacks.end()) {
return false;
}
workspace()->screenEdges()->unreserve(static_cast<KWin::ElectricBorder>(edge), this);
m_screenEdgeCallbacks.erase(it);
return true;
}
bool KWin::Script::registerTouchScreenEdge(int edge, const QJSValue &callback)
{
if (!callback.isCallable()) {
m_engine->throwError(QStringLiteral("Touch screen edge handler must be callable"));
return false;
}
if (m_touchScreenEdgeCallbacks.contains(edge)) {
return false;
}
QAction *action = new QAction(this);
workspace()->screenEdges()->reserveTouch(KWin::ElectricBorder(edge), action);
m_touchScreenEdgeCallbacks.insert(edge, action);
connect(action, &QAction::triggered, this, [callback]() {
QJSValue(callback).call();
});
return true;
}
bool KWin::Script::unregisterTouchScreenEdge(int edge)
{
auto it = m_touchScreenEdgeCallbacks.find(edge);
if (it == m_touchScreenEdgeCallbacks.end()) {
return false;
}
delete it.value();
m_touchScreenEdgeCallbacks.erase(it);
return true;
}
void KWin::Script::registerUserActionsMenu(const QJSValue &callback)
{
if (!callback.isCallable()) {
m_engine->throwError(QStringLiteral("User action handler must be callable"));
return;
}
m_userActionsMenuCallbacks.append(callback);
}
QList<QAction *> KWin::Script::actionsForUserActionMenu(KWin::Window *client, QMenu *parent)
{
QList<QAction *> actions;
actions.reserve(m_userActionsMenuCallbacks.count());
for (QJSValue callback : std::as_const(m_userActionsMenuCallbacks)) {
const QJSValue result = callback.call({m_engine->toScriptValue(client)});
if (result.isError()) {
continue;
}
if (!result.isObject()) {
continue;
}
if (QAction *action = scriptValueToAction(result, parent)) {
actions << action;
}
}
return actions;
}
bool KWin::Script::slotBorderActivated(ElectricBorder border)
{
const QJSValueList callbacks = m_screenEdgeCallbacks.value(border);
if (callbacks.isEmpty()) {
return false;
}
std::for_each(callbacks.begin(), callbacks.end(), [](QJSValue callback) {
callback.call();
});
return true;
}
QAction *KWin::Script::scriptValueToAction(const QJSValue &value, QMenu *parent)
{
const QString title = value.property(QStringLiteral("text")).toString();
if (title.isEmpty()) {
return nullptr;
}
// Either a menu or a menu item.
const QJSValue itemsValue = value.property(QStringLiteral("items"));
if (!itemsValue.isUndefined()) {
return createMenu(title, itemsValue, parent);
}
return createAction(title, value, parent);
}
QAction *KWin::Script::createAction(const QString &title, const QJSValue &item, QMenu *parent)
{
const QJSValue callback = item.property(QStringLiteral("triggered"));
if (!callback.isCallable()) {
return nullptr;
}
const bool checkable = item.property(QStringLiteral("checkable")).toBool();
const bool checked = item.property(QStringLiteral("checked")).toBool();
QAction *action = new QAction(title, parent);
action->setCheckable(checkable);
action->setChecked(checked);
connect(action, &QAction::triggered, this, [this, action, callback]() {
QJSValue(callback).call({m_engine->toScriptValue(action)});
});
return action;
}
QAction *KWin::Script::createMenu(const QString &title, const QJSValue &items, QMenu *parent)
{
if (!items.isArray()) {
return nullptr;
}
const int length = items.property(QStringLiteral("length")).toInt();
if (!length) {
return nullptr;
}
QMenu *menu = new QMenu(title, parent);
for (int i = 0; i < length; ++i) {
const QJSValue value = items.property(QString::number(i));
if (!value.isObject()) {
continue;
}
if (QAction *action = scriptValueToAction(value, menu)) {
menu->addAction(action);
}
}
return menu->menuAction();
}
KWin::DeclarativeScript::DeclarativeScript(int id, QString scriptName, QString pluginName, QObject *parent)
: AbstractScript(id, scriptName, pluginName, parent)
, m_context(new QQmlContext(Scripting::self()->declarativeScriptSharedContext(), this))
, m_component(new QQmlComponent(Scripting::self()->qmlEngine(), this))
{
m_context->setContextProperty(QStringLiteral("KWin"), new JSEngineGlobalMethodsWrapper(this));
}
KWin::DeclarativeScript::~DeclarativeScript()
{
}
void KWin::DeclarativeScript::run()
{
if (running()) {
return;
}
m_component->loadUrl(QUrl::fromLocalFile(fileName()));
if (m_component->isLoading()) {
connect(m_component, &QQmlComponent::statusChanged, this, &DeclarativeScript::createComponent);
} else {
createComponent();
}
}
void KWin::DeclarativeScript::createComponent()
{
if (m_component->isError()) {
qCWarning(KWIN_SCRIPTING) << "Component failed to load: " << m_component->errors();
} else {
if (QObject *object = m_component->create(m_context)) {
object->setParent(this);
}
}
setRunning(true);
}
KWin::JSEngineGlobalMethodsWrapper::JSEngineGlobalMethodsWrapper(KWin::DeclarativeScript *parent)
: QObject(parent)
, m_script(parent)
{
}
KWin::JSEngineGlobalMethodsWrapper::~JSEngineGlobalMethodsWrapper()
{
}
QVariant KWin::JSEngineGlobalMethodsWrapper::readConfig(const QString &key, QVariant defaultValue)
{
return m_script->config().readEntry(key, defaultValue);
}
KWin::Scripting *KWin::Scripting::s_self = nullptr;
KWin::Scripting *KWin::Scripting::create(QObject *parent)
{
Q_ASSERT(!s_self);
s_self = new Scripting(parent);
return s_self;
}
KWin::Scripting::Scripting(QObject *parent)
: QObject(parent)
, m_scriptsLock(new QRecursiveMutex)
, m_qmlEngine(new QQmlEngine(this))
, m_declarativeScriptSharedContext(new QQmlContext(m_qmlEngine, this))
, m_workspaceWrapper(new QtScriptWorkspaceWrapper(this))
{
m_qmlEngine->setProperty("_kirigamiTheme", QStringLiteral("KirigamiPlasmaStyle"));
m_qmlEngine->rootContext()->setContextObject(new KLocalizedQmlContext(m_qmlEngine));
init();
QDBusConnection::sessionBus().registerObject(QStringLiteral("/Scripting"), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportScriptableInvokables);
connect(Workspace::self(), &Workspace::configChanged, this, &Scripting::start);
connect(Workspace::self(), &Workspace::workspaceInitialized, this, &Scripting::start);
}
void KWin::Scripting::init()
{
qRegisterMetaType<QList<KWin::Output *>>();
qRegisterMetaType<QList<KWin::Window *>>();
qRegisterMetaType<QList<KWin::VirtualDesktop *>>();
qmlRegisterType<DBusCall>("org.kde.kwin", 3, 0, "DBusCall");
qmlRegisterType<ScreenEdgeHandler>("org.kde.kwin", 3, 0, "ScreenEdgeHandler");
qmlRegisterType<ShortcutHandler>("org.kde.kwin", 3, 0, "ShortcutHandler");
qmlRegisterType<SwipeGestureHandler>("org.kde.kwin", 3, 0, "SwipeGestureHandler");
qmlRegisterType<PinchGestureHandler>("org.kde.kwin", 3, 0, "PinchGestureHandler");
qmlRegisterType<WindowModel>("org.kde.kwin", 3, 0, "WindowModel");
qmlRegisterType<WindowFilterModel>("org.kde.kwin", 3, 0, "WindowFilterModel");
qmlRegisterType<VirtualDesktopModel>("org.kde.kwin", 3, 0, "VirtualDesktopModel");
#if KWIN_BUILD_QTQUICK
qmlRegisterType<DesktopBackgroundItem>("org.kde.kwin", 3, 0, "DesktopBackground");
qmlRegisterType<WindowThumbnailItem>("org.kde.kwin", 3, 0, "WindowThumbnail");
qmlRegisterUncreatableType<KWin::QuickSceneView>("org.kde.kwin", 3, 0, "SceneView", QStringLiteral("Can't instantiate an object of type SceneView"));
qmlRegisterType<ScriptedQuickSceneEffect>("org.kde.kwin", 3, 0, "SceneEffect");
#endif
qmlRegisterSingletonType<DeclarativeScriptWorkspaceWrapper>("org.kde.kwin", 3, 0, "Workspace", [](QQmlEngine *qmlEngine, QJSEngine *jsEngine) {
return new DeclarativeScriptWorkspaceWrapper();
});
qmlRegisterSingletonInstance("org.kde.kwin", 3, 0, "Options", options);
qmlRegisterAnonymousType<KConfigPropertyMap>("org.kde.kwin", 3);
qmlRegisterAnonymousType<KWin::Output>("org.kde.kwin", 3);
qmlRegisterAnonymousType<KWin::Window>("org.kde.kwin", 3);
qmlRegisterAnonymousType<KWin::VirtualDesktop>("org.kde.kwin", 3);
qmlRegisterAnonymousType<QAbstractItemModel>("org.kde.kwin", 3);
qmlRegisterAnonymousType<KWin::TileManager>("org.kde.kwin", 3);
// TODO: call the qml types as the C++ types?
qmlRegisterUncreatableType<KWin::CustomTile>("org.kde.kwin", 3, 0, "CustomTile", QStringLiteral("Cannot create objects of type Tile"));
qmlRegisterUncreatableType<KWin::Tile>("org.kde.kwin", 3, 0, "Tile", QStringLiteral("Cannot create objects of type AbstractTile"));
}
void KWin::Scripting::start()
{
#if 0
// TODO make this threaded again once KConfigGroup is sufficiently thread safe, bug #305361 and friends
// perform querying for the services in a thread
QFutureWatcher<LoadScriptList> *watcher = new QFutureWatcher<LoadScriptList>(this);
connect(watcher, &QFutureWatcher<LoadScriptList>::finished, this, &Scripting::slotScriptsQueried);
watcher->setFuture(QtConcurrent::run(this, &KWin::Scripting::queryScriptsToLoad, pluginStates, offers));
#else
LoadScriptList scriptsToLoad = queryScriptsToLoad();
for (LoadScriptList::const_iterator it = scriptsToLoad.constBegin();
it != scriptsToLoad.constEnd();
++it) {
if (it->first) {
loadScript(it->second.first, it->second.second);
} else {
loadDeclarativeScript(it->second.first, it->second.second);
}
}
runScripts();
#endif
}
LoadScriptList KWin::Scripting::queryScriptsToLoad()
{
KSharedConfig::Ptr _config = kwinApp()->config();
static bool s_started = false;
if (s_started) {
_config->reparseConfiguration();
} else {
s_started = true;
}
QMap<QString, QString> pluginStates = KConfigGroup(_config, QStringLiteral("Plugins")).entryMap();
const QString scriptFolder = QStringLiteral("kwin/scripts/");
const auto offers = KPackage::PackageLoader::self()->listPackages(QStringLiteral("KWin/Script"), scriptFolder);
LoadScriptList scriptsToLoad;
for (const KPluginMetaData &service : offers) {
const QString value = pluginStates.value(service.pluginId() + QLatin1String("Enabled"), QString());
const bool enabled = value.isNull() ? service.isEnabledByDefault() : QVariant(value).toBool();
const bool javaScript = service.value(QStringLiteral("X-Plasma-API")) == QLatin1String("javascript");
const bool declarativeScript = service.value(QStringLiteral("X-Plasma-API")) == QLatin1String("declarativescript");
if (!javaScript && !declarativeScript) {
continue;
}
if (!enabled) {
if (isScriptLoaded(service.pluginId())) {
// unload the script
unloadScript(service.pluginId());
}
continue;
}
const QString pluginName = service.pluginId();
// The file we want to load depends on the specified API. We could check if one or the other file exists, but that is more error prone and causes IO overhead
const QString relScriptPath = scriptFolder + pluginName + QLatin1String("/contents/") + (javaScript ? QLatin1String("code/main.js") : QLatin1String("ui/main.qml"));
const QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, relScriptPath);
if (file.isEmpty()) {
qCDebug(KWIN_SCRIPTING) << "Could not find script file for " << pluginName;
continue;
}
scriptsToLoad << qMakePair(javaScript, qMakePair(file, pluginName));
}
return scriptsToLoad;
}
void KWin::Scripting::slotScriptsQueried()
{
QFutureWatcher<LoadScriptList> *watcher = dynamic_cast<QFutureWatcher<LoadScriptList> *>(sender());
if (!watcher) {
// slot invoked not from a FutureWatcher
return;
}
LoadScriptList scriptsToLoad = watcher->result();
for (LoadScriptList::const_iterator it = scriptsToLoad.constBegin();
it != scriptsToLoad.constEnd();
++it) {
if (it->first) {
loadScript(it->second.first, it->second.second);
} else {
loadDeclarativeScript(it->second.first, it->second.second);
}
}
runScripts();
watcher->deleteLater();
}
bool KWin::Scripting::isScriptLoaded(const QString &pluginName) const
{
return findScript(pluginName) != nullptr;
}
KWin::AbstractScript *KWin::Scripting::findScript(const QString &pluginName) const
{
QMutexLocker locker(m_scriptsLock.get());
for (AbstractScript *script : std::as_const(scripts)) {
if (script->pluginName() == pluginName) {
return script;
}
}
return nullptr;
}
bool KWin::Scripting::unloadScript(const QString &pluginName)
{
QMutexLocker locker(m_scriptsLock.get());
for (AbstractScript *script : std::as_const(scripts)) {
if (script->pluginName() == pluginName) {
script->deleteLater();
return true;
}
}
return false;
}
void KWin::Scripting::runScripts()
{
QMutexLocker locker(m_scriptsLock.get());
for (int i = 0; i < scripts.size(); i++) {
scripts.at(i)->run();
}
}
void KWin::Scripting::scriptDestroyed(QObject *object)
{
QMutexLocker locker(m_scriptsLock.get());
scripts.removeAll(static_cast<KWin::Script *>(object));
}
int KWin::Scripting::loadScript(const QString &filePath, const QString &pluginName)
{
QMutexLocker locker(m_scriptsLock.get());
if (isScriptLoaded(pluginName)) {
return -1;
}
const int id = scripts.size();
KWin::Script *script = new KWin::Script(id, filePath, pluginName, this);
connect(script, &QObject::destroyed, this, &Scripting::scriptDestroyed);
scripts.append(script);
return id;
}
int KWin::Scripting::loadDeclarativeScript(const QString &filePath, const QString &pluginName)
{
QMutexLocker locker(m_scriptsLock.get());
if (isScriptLoaded(pluginName)) {
return -1;
}
const int id = scripts.size();
KWin::DeclarativeScript *script = new KWin::DeclarativeScript(id, filePath, pluginName, this);
connect(script, &QObject::destroyed, this, &Scripting::scriptDestroyed);
scripts.append(script);
return id;
}
KWin::Scripting::~Scripting()
{
QDBusConnection::sessionBus().unregisterObject(QStringLiteral("/Scripting"));
s_self = nullptr;
}
QList<QAction *> KWin::Scripting::actionsForUserActionMenu(KWin::Window *c, QMenu *parent)
{
QList<QAction *> actions;
for (AbstractScript *s : std::as_const(scripts)) {
// TODO: Allow declarative scripts to add their own user actions.
if (Script *script = qobject_cast<Script *>(s)) {
actions << script->actionsForUserActionMenu(c, parent);
}
}
return actions;
}
#include "moc_scripting.cpp"
@@ -0,0 +1,398 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2010 Rohan Prabhu <rohan@rohanprabhu.com>
SPDX-FileCopyrightText: 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "effect/globals.h"
#include <QHash>
#include <QJSEngine>
#include <QJSValue>
#include <QStringList>
#include <QTimer>
#include <QDBusContext>
#include <QDBusMessage>
class QQmlComponent;
class QQmlContext;
class QQmlEngine;
class QAction;
class QMenu;
class QRecursiveMutex;
class QQuickWindow;
class KConfigGroup;
/// @c true == javascript, @c false == qml
typedef QList<QPair<bool, QPair<QString, QString>>> LoadScriptList;
namespace KWin
{
class Window;
class QtScriptWorkspaceWrapper;
class KWIN_EXPORT AbstractScript : public QObject
{
Q_OBJECT
public:
AbstractScript(int id, QString scriptName, QString pluginName, QObject *parent = nullptr);
~AbstractScript() override;
int scriptId() const
{
return m_scriptId;
}
QString fileName() const
{
return m_fileName;
}
const QString &pluginName()
{
return m_pluginName;
}
bool running() const
{
return m_running;
}
KConfigGroup config() const;
public Q_SLOTS:
void stop();
virtual void run() = 0;
Q_SIGNALS:
void runningChanged(bool);
protected:
void setRunning(bool running)
{
if (m_running == running) {
return;
}
m_running = running;
Q_EMIT runningChanged(m_running);
}
private:
int m_scriptId;
QString m_fileName;
QString m_pluginName;
bool m_running;
};
/**
* In order to be able to construct QTimer objects in javascript, the constructor
* must be declared with Q_INVOKABLE.
*/
class ScriptTimer : public QTimer
{
Q_OBJECT
public:
Q_INVOKABLE ScriptTimer(QObject *parent = nullptr);
};
class Script : public AbstractScript, QDBusContext
{
Q_OBJECT
public:
Script(int id, QString scriptName, QString pluginName, QObject *parent = nullptr);
virtual ~Script();
Q_INVOKABLE QVariant readConfig(const QString &key, const QVariant &defaultValue = QVariant());
Q_INVOKABLE void callDBus(const QString &service, const QString &path,
const QString &interface, const QString &method,
const QJSValue &arg1 = QJSValue(),
const QJSValue &arg2 = QJSValue(),
const QJSValue &arg3 = QJSValue(),
const QJSValue &arg4 = QJSValue(),
const QJSValue &arg5 = QJSValue(),
const QJSValue &arg6 = QJSValue(),
const QJSValue &arg7 = QJSValue(),
const QJSValue &arg8 = QJSValue(),
const QJSValue &arg9 = QJSValue());
Q_INVOKABLE bool registerShortcut(const QString &objectName, const QString &text,
const QString &keySequence, const QJSValue &callback);
Q_INVOKABLE bool registerScreenEdge(int edge, const QJSValue &callback);
Q_INVOKABLE bool unregisterScreenEdge(int edge);
Q_INVOKABLE bool registerTouchScreenEdge(int edge, const QJSValue &callback);
Q_INVOKABLE bool unregisterTouchScreenEdge(int edge);
/**
* @brief Registers the given @p callback to be invoked whenever the UserActionsMenu is about
* to be showed. In the callback the script can create a further sub menu or menu entry to be
* added to the UserActionsMenu.
*
* @param callback Script method to execute when the UserActionsMenu is about to be shown.
* @return void
* @see actionsForUserActionMenu
*/
Q_INVOKABLE void registerUserActionsMenu(const QJSValue &callback);
/**
* @brief Creates actions for the UserActionsMenu by invoking the registered callbacks.
*
* This method invokes all the callbacks previously registered with registerUseractionsMenuCallback.
* The Client @p c is passed in as an argument to the invoked method.
*
* The invoked method is supposed to return a JavaScript object containing either the menu or
* menu entry to be added. In case the callback returns a null or undefined or any other invalid
* value, it is not considered for adding to the menu.
*
* The JavaScript object structure for a menu entry looks like the following:
* @code
* {
* title: "My Menu Entry",
* checkable: true,
* checked: false,
* triggered: function (action) {
* // callback when the menu entry is triggered with the QAction as argument
* }
* }
* @endcode
*
* To construct a complete Menu the JavaScript object looks like the following:
* @code
* {
* title: "My Menu Title",
* items: [{...}, {...}, ...] // list of menu entries as described above
* }
* @endcode
*
* The returned JavaScript object is introspected and for a menu entry a QAction is created,
* while for a menu a QMenu is created and QActions for the individual entries. Of course it
* is allowed to have nested structures.
*
* All created objects are (grand) children to the passed in @p parent menu, so that they get
* deleted whenever the menu is destroyed.
*
* @param c The Client for which the menu is invoked, passed to the callback
* @param parent The Parent for the created Menus or Actions
* @return QList< QAction* > List of QActions obtained from asking the registered callbacks
* @see registerUseractionsMenuCallback
*/
QList<QAction *> actionsForUserActionMenu(Window *client, QMenu *parent);
public Q_SLOTS:
void run() override;
private Q_SLOTS:
/**
* Callback for when loadScriptFromFile has finished.
*/
void slotScriptLoadedFromFile();
/**
* Called when any reserve screen edge is triggered.
*/
bool slotBorderActivated(ElectricBorder border);
private:
/**
* Read the script from file into a byte array.
* If file cannot be read an empty byte array is returned.
*/
QByteArray loadScriptFromFile(const QString &fileName);
/**
* @brief Parses the @p value to either a QMenu or QAction.
*
* @param value The ScriptValue describing either a menu or action
* @param parent The parent to use for the created menu or action
* @return QAction* The parsed action or menu action, if parsing fails returns @c null.
*/
QAction *scriptValueToAction(const QJSValue &value, QMenu *parent);
/**
* @brief Creates a new QAction from the provided data and registers it for invoking the
* @p callback when the action is triggered.
*
* The created action is added to the map of actions and callbacks shared with the global
* shortcuts.
*
* @param title The title of the action
* @param checkable Whether the action is checkable
* @param checked Whether the checkable action is checked
* @param callback The callback to invoke when the action is triggered
* @param parent The parent to be used for the new created action
* @return QAction* The created action
*/
QAction *createAction(const QString &title, const QJSValue &item, QMenu *parent);
/**
* @brief Parses the @p items and creates a QMenu from it.
*
* @param title The title of the Menu.
* @param items JavaScript Array containing Menu items.
* @param parent The parent to use for the new created menu
* @return QAction* The menu action for the new Menu
*/
QAction *createMenu(const QString &title, const QJSValue &items, QMenu *parent);
QJSEngine *m_engine;
QDBusMessage m_invocationContext;
bool m_starting;
QHash<int, QJSValueList> m_screenEdgeCallbacks;
QHash<int, QAction *> m_touchScreenEdgeCallbacks;
QJSValueList m_userActionsMenuCallbacks;
};
class DeclarativeScript : public AbstractScript
{
Q_OBJECT
public:
explicit DeclarativeScript(int id, QString scriptName, QString pluginName, QObject *parent = nullptr);
~DeclarativeScript() override;
public Q_SLOTS:
Q_SCRIPTABLE void run() override;
private Q_SLOTS:
void createComponent();
private:
QQmlContext *m_context;
QQmlComponent *m_component;
};
class JSEngineGlobalMethodsWrapper : public QObject
{
Q_OBJECT
public:
//------------------------------------------------------------------
// enums copy&pasted from kwinglobals.h for exporting
enum ClientAreaOption {
///< geometry where a window will be initially placed after being mapped
PlacementArea,
///< window movement snapping area? ignore struts
MovementArea,
///< geometry to which a window will be maximized
MaximizeArea,
///< like MaximizeArea, but ignore struts - used e.g. for topmenu
MaximizeFullArea,
///< area for fullscreen windows
FullScreenArea,
///< whole workarea (all screens together)
WorkArea,
///< whole area (all screens together), ignore struts
FullArea,
///< one whole screen, ignore struts
ScreenArea
};
Q_ENUM(ClientAreaOption)
explicit JSEngineGlobalMethodsWrapper(DeclarativeScript *parent);
~JSEngineGlobalMethodsWrapper() override;
public Q_SLOTS:
QVariant readConfig(const QString &key, QVariant defaultValue = QVariant());
private:
DeclarativeScript *m_script;
};
/**
* The heart of KWin::Scripting. Infinite power lies beyond
*/
class KWIN_EXPORT Scripting : public QObject
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.kwin.Scripting")
private:
explicit Scripting(QObject *parent);
QStringList scriptList;
QList<KWin::AbstractScript *> scripts;
/**
* Lock to protect the scripts member variable.
*/
std::unique_ptr<QRecursiveMutex> m_scriptsLock;
// Preferably call ONLY at load time
void runScripts();
public:
~Scripting() override;
Q_SCRIPTABLE Q_INVOKABLE int loadScript(const QString &filePath, const QString &pluginName = QString());
Q_SCRIPTABLE Q_INVOKABLE int loadDeclarativeScript(const QString &filePath, const QString &pluginName = QString());
Q_SCRIPTABLE Q_INVOKABLE bool isScriptLoaded(const QString &pluginName) const;
Q_SCRIPTABLE Q_INVOKABLE bool unloadScript(const QString &pluginName);
/**
* @brief Invokes all registered callbacks to add actions to the UserActionsMenu.
*
* @param c The Client for which the UserActionsMenu is about to be shown
* @param parent The parent menu to which to add created child menus and items
* @return QList< QAction* > List of all actions aggregated from all scripts.
*/
QList<QAction *> actionsForUserActionMenu(Window *c, QMenu *parent);
QQmlEngine *qmlEngine() const;
QQmlEngine *qmlEngine();
QQmlContext *declarativeScriptSharedContext() const;
QQmlContext *declarativeScriptSharedContext();
QtScriptWorkspaceWrapper *workspaceWrapper() const;
AbstractScript *findScript(const QString &pluginName) const;
static Scripting *self();
static Scripting *create(QObject *parent);
public Q_SLOTS:
void scriptDestroyed(QObject *object);
Q_SCRIPTABLE void start();
private Q_SLOTS:
void slotScriptsQueried();
private:
void init();
LoadScriptList queryScriptsToLoad();
static Scripting *s_self;
QQmlEngine *m_qmlEngine;
QQmlContext *m_declarativeScriptSharedContext;
QtScriptWorkspaceWrapper *m_workspaceWrapper;
};
inline QQmlEngine *Scripting::qmlEngine() const
{
return m_qmlEngine;
}
inline QQmlEngine *Scripting::qmlEngine()
{
return m_qmlEngine;
}
inline QQmlContext *Scripting::declarativeScriptSharedContext() const
{
return m_declarativeScriptSharedContext;
}
inline QQmlContext *Scripting::declarativeScriptSharedContext()
{
return m_declarativeScriptSharedContext;
}
inline QtScriptWorkspaceWrapper *Scripting::workspaceWrapper() const
{
return m_workspaceWrapper;
}
inline Scripting *Scripting::self()
{
return s_self;
}
}
@@ -0,0 +1,10 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "scripting_logging.h"
Q_LOGGING_CATEGORY(KWIN_SCRIPTING, "kwin_scripting", QtWarningMsg)
@@ -0,0 +1,12 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QDebug>
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(KWIN_SCRIPTING)
@@ -0,0 +1,76 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2012 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "scriptingutils.h"
#include "scripting_logging.h"
#include <QDBusArgument>
#include <QDBusObjectPath>
#include <QDBusSignature>
namespace KWin
{
QVariant dbusToVariant(const QVariant &variant)
{
if (variant.canConvert<QDBusArgument>()) {
const QDBusArgument argument = variant.value<QDBusArgument>();
switch (argument.currentType()) {
case QDBusArgument::BasicType:
return dbusToVariant(argument.asVariant());
case QDBusArgument::VariantType:
return dbusToVariant(argument.asVariant().value<QDBusVariant>().variant());
case QDBusArgument::ArrayType: {
QVariantList array;
argument.beginArray();
while (!argument.atEnd()) {
const QVariant value = argument.asVariant();
array.append(dbusToVariant(value));
}
argument.endArray();
return array;
}
case QDBusArgument::StructureType: {
QVariantList structure;
argument.beginStructure();
while (!argument.atEnd()) {
const QVariant value = argument.asVariant();
structure.append(dbusToVariant(value));
}
argument.endStructure();
return structure;
}
case QDBusArgument::MapType: {
QVariantMap map;
argument.beginMap();
while (!argument.atEnd()) {
argument.beginMapEntry();
const QVariant key = argument.asVariant();
const QVariant value = argument.asVariant();
argument.endMapEntry();
map.insert(key.toString(), dbusToVariant(value));
}
argument.endMap();
return map;
}
default:
qCWarning(KWIN_SCRIPTING) << "Couldn't unwrap QDBusArgument of type" << argument.currentType();
return variant;
}
} else if (variant.canConvert<QDBusObjectPath>()) {
return variant.value<QDBusObjectPath>().path();
} else if (variant.canConvert<QDBusSignature>()) {
return variant.value<QDBusSignature>().signature();
} else if (variant.canConvert<QDBusVariant>()) {
return dbusToVariant(variant.value<QDBusVariant>().variant());
}
return variant;
}
}
@@ -0,0 +1,19 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2012 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QVariant>
namespace KWin
{
QVariant dbusToVariant(const QVariant &variant);
} // namespace KWin
@@ -0,0 +1,97 @@
/*
SPDX-FileCopyrightText: 2023 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "shortcuthandler.h"
#include "utils/common.h"
#include <KGlobalAccel>
#include <QAction>
namespace KWin
{
ShortcutHandler::ShortcutHandler(QObject *parent)
: QObject(parent)
{
}
void ShortcutHandler::classBegin()
{
}
void ShortcutHandler::componentComplete()
{
if (m_name.isEmpty()) {
qCWarning(KWIN_CORE) << "ShortcutHandler.name is required";
return;
}
if (m_text.isEmpty()) {
qCWarning(KWIN_CORE) << "ShortcutHandler.text is required";
return;
}
QAction *action = new QAction(this);
connect(action, &QAction::triggered, this, &ShortcutHandler::activated);
action->setObjectName(m_name);
action->setText(m_text);
KGlobalAccel::self()->setShortcut(action, {m_keySequence});
}
QString ShortcutHandler::name() const
{
return m_name;
}
void ShortcutHandler::setName(const QString &name)
{
if (m_action) {
qCWarning(KWIN_CORE) << "ShortcutHandler.name cannot be changed";
return;
}
if (m_name != name) {
m_name = name;
Q_EMIT nameChanged();
}
}
QString ShortcutHandler::text() const
{
return m_text;
}
void ShortcutHandler::setText(const QString &text)
{
if (m_text != text) {
m_text = text;
if (m_action) {
m_action->setText(text);
}
Q_EMIT textChanged();
}
}
QVariant ShortcutHandler::sequence() const
{
return m_userSequence;
}
void ShortcutHandler::setSequence(const QVariant &sequence)
{
if (m_action) {
qCWarning(KWIN_CORE) << "ShortcutHandler.sequence cannot be changed";
return;
}
if (m_userSequence != sequence) {
m_userSequence = sequence;
m_keySequence = QKeySequence::fromString(sequence.toString());
Q_EMIT sequenceChanged();
}
}
} // namespace KWin
#include "moc_shortcuthandler.cpp"
@@ -0,0 +1,87 @@
/*
SPDX-FileCopyrightText: 2023 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QKeySequence>
#include <QObject>
#include <QQmlParserStatus>
#include <QVariant>
class QAction;
namespace KWin
{
/**
* The ShortcutHandler type provides a way to register global key shorcuts.
*
* Example usage:
* @code
* ShortcutHandler {
* name: "Activate Something"
* text: i18n("Activate Something")
* sequence: "Meta+Ctrl+S"
* onActivated: doSomething()
* }
* @endcode
*/
class ShortcutHandler : public QObject, public QQmlParserStatus
{
Q_OBJECT
/**
* This property specifies the unique shortcut identifier, not localized.
*
* The shortcut name cannot be changed once the ShortcutHandler is constructed.
*/
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
/**
* This property specifies human readable name of the shortcut.
*/
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
/**
* This property holds the key sequence for this shortcut. The key sequence
* can be specified using a string containing a sequence of keys that are needed
* to be pressed to activate the shortcut, e.g. `Meta+K`.
*
* The key sequence is optional. If omitted, the user can assign a key sequence
* to this shortcut in system settings.
*
* The key sequence cannot be changed once the ShortcutHandler is constructed.
*/
Q_PROPERTY(QVariant sequence READ sequence WRITE setSequence NOTIFY sequenceChanged)
public:
explicit ShortcutHandler(QObject *parent = nullptr);
void classBegin() override;
void componentComplete() override;
QString name() const;
void setName(const QString &name);
QString text() const;
void setText(const QString &text);
QVariant sequence() const;
void setSequence(const QVariant &sequence);
Q_SIGNALS:
void nameChanged();
void textChanged();
void sequenceChanged();
void activated();
private:
QString m_name;
QString m_text;
QVariant m_userSequence;
QKeySequence m_keySequence;
QAction *m_action = nullptr;
};
} // namespace KWin
@@ -0,0 +1,159 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2022 Marco Martin <mart@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "tilemodel.h"
#include "tiles/tilemanager.h"
#include "workspace.h"
#include <KConfigGroup>
#include <KLocalizedString>
#include <KSharedConfig>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QTimer>
namespace KWin
{
TileModel::TileModel(TileManager *parent)
: QAbstractItemModel(parent)
, m_tileManager(parent)
{
}
TileModel::~TileModel()
{
}
QHash<int, QByteArray> TileModel::roleNames() const
{
return {
{TileRole, QByteArrayLiteral("tile")},
};
}
QVariant TileModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
if (role == TileRole) {
return QVariant::fromValue(static_cast<CustomTile *>(index.internalPointer()));
}
return QVariant();
}
Qt::ItemFlags TileModel::flags(const QModelIndex &index) const
{
if (!index.isValid()) {
return Qt::NoItemFlags;
}
return QAbstractItemModel::flags(index);
}
QModelIndex TileModel::index(int row, int column, const QModelIndex &parent) const
{
if (column > 0 || !hasIndex(row, column, parent)) {
return QModelIndex();
}
CustomTile *parentItem;
if (!parent.isValid()) {
parentItem = m_tileManager->rootTile();
} else {
parentItem = static_cast<CustomTile *>(parent.internalPointer());
}
CustomTile *childItem = static_cast<CustomTile *>(parentItem->childTile(row));
if (childItem) {
return createIndex(row, column, childItem);
}
return QModelIndex();
}
QModelIndex TileModel::parent(const QModelIndex &index) const
{
if (!index.isValid()) {
return QModelIndex();
}
CustomTile *childItem = static_cast<CustomTile *>(index.internalPointer());
CustomTile *parentItem = static_cast<CustomTile *>(childItem->parentTile());
if (!parentItem || parentItem == m_tileManager->rootTile()) {
return QModelIndex();
}
return createIndex(parentItem->row(), 0, parentItem);
}
int TileModel::rowCount(const QModelIndex &parent) const
{
Tile *parentItem;
if (parent.column() > 0) {
return 0;
}
if (!parent.isValid()) {
parentItem = m_tileManager->rootTile();
} else {
parentItem = static_cast<CustomTile *>(parent.internalPointer());
}
return parentItem->childCount();
}
int TileModel::columnCount(const QModelIndex &parent) const
{
return 1;
}
void TileModel::beginInsertTile(CustomTile *tile, int position)
{
Q_ASSERT(position >= 0);
CustomTile *parentTile = static_cast<CustomTile *>(tile->parentTile());
Q_ASSERT(parentTile);
auto index = parentTile == m_tileManager->rootTile() ? QModelIndex() : createIndex(parentTile->row(), 0, parentTile);
const int pos = std::clamp(position, 0, parentTile->childCount());
beginInsertRows(index, pos, pos);
}
void TileModel::endInsertTile()
{
endInsertRows();
}
void TileModel::beginRemoveTile(CustomTile *tile)
{
const auto parentTile = static_cast<CustomTile *>(tile->parentTile());
if (!parentTile) {
qCWarning(KWIN_CORE) << "Can't remove the root tile";
return;
}
QModelIndex parentIndex = parentTile == m_tileManager->rootTile() ? QModelIndex() : createIndex(parentTile->row(), 0, parentTile);
beginRemoveRows(parentIndex, tile->row(), tile->row());
}
void TileModel::endRemoveTile()
{
endRemoveRows();
}
} // namespace KWin
#include "moc_tilemodel.cpp"
@@ -0,0 +1,68 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2022 Marco Martin <mart@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "tiles/customtile.h"
#include "tiles/tile.h"
#include <kwin_export.h>
#include <QAbstractItemModel>
#include <QObject>
#include <QRectF>
#include <QJsonValue>
class QTimer;
namespace KWin
{
class Output;
class CustomTile;
class TileManager;
/**
* Custom tiling zones management per output.
*/
class KWIN_EXPORT TileModel : public QAbstractItemModel
{
Q_OBJECT
public:
enum Roles {
TileRole = Qt::UserRole + 1
};
explicit TileModel(TileManager *parent = nullptr);
~TileModel() override;
// QAbstractItemModel overrides
QHash<int, QByteArray> roleNames() const override;
QVariant data(const QModelIndex &index, int role) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QModelIndex index(int row, int column,
const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
private:
void beginInsertTile(CustomTile *tile, int position);
void endInsertTile();
void beginRemoveTile(CustomTile *tile);
void endRemoveTile();
// Not an uinique_pointer as the model is child of the manager so would cause a cyclic delete
TileManager *m_tileManager;
friend class CustomTile;
Q_DISABLE_COPY(TileModel)
};
} // namespace KWin
@@ -0,0 +1,93 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "virtualdesktopmodel.h"
#include "virtualdesktops.h"
namespace KWin
{
VirtualDesktopModel::VirtualDesktopModel(QObject *parent)
: QAbstractListModel(parent)
{
VirtualDesktopManager *manager = VirtualDesktopManager::self();
connect(manager, &VirtualDesktopManager::desktopAdded,
this, &VirtualDesktopModel::handleVirtualDesktopAdded);
connect(manager, &VirtualDesktopManager::desktopRemoved,
this, &VirtualDesktopModel::handleVirtualDesktopRemoved);
m_virtualDesktops = manager->desktops();
}
VirtualDesktop *VirtualDesktopModel::create(uint position, const QString &name)
{
return VirtualDesktopManager::self()->createVirtualDesktop(position, name);
}
void VirtualDesktopModel::remove(uint position)
{
if (position < m_virtualDesktops.count()) {
VirtualDesktopManager::self()->removeVirtualDesktop(m_virtualDesktops[position]);
}
}
void VirtualDesktopModel::handleVirtualDesktopAdded(VirtualDesktop *desktop)
{
const int position = desktop->x11DesktopNumber() - 1;
beginInsertRows(QModelIndex(), position, position);
m_virtualDesktops.insert(position, desktop);
endInsertRows();
}
void VirtualDesktopModel::handleVirtualDesktopRemoved(VirtualDesktop *desktop)
{
const int index = m_virtualDesktops.indexOf(desktop);
Q_ASSERT(index != -1);
beginRemoveRows(QModelIndex(), index, index);
m_virtualDesktops.removeAt(index);
endRemoveRows();
}
QHash<int, QByteArray> VirtualDesktopModel::roleNames() const
{
QHash<int, QByteArray> roleNames = QAbstractListModel::roleNames();
roleNames.insert(DesktopRole, QByteArrayLiteral("desktop"));
return roleNames;
}
VirtualDesktop *VirtualDesktopModel::desktopFromIndex(const QModelIndex &index) const
{
if (!index.isValid() || index.row() < 0 || index.row() >= m_virtualDesktops.count()) {
return nullptr;
}
return m_virtualDesktops[index.row()];
}
QVariant VirtualDesktopModel::data(const QModelIndex &index, int role) const
{
VirtualDesktop *desktop = desktopFromIndex(index);
if (!desktop) {
return QVariant();
}
switch (role) {
case Qt::DisplayRole:
case DesktopRole:
return QVariant::fromValue(desktop);
default:
return QVariant();
}
}
int VirtualDesktopModel::rowCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : m_virtualDesktops.count();
}
} // namespace KWin
#include "moc_virtualdesktopmodel.cpp"
@@ -0,0 +1,46 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QAbstractListModel>
namespace KWin
{
class VirtualDesktop;
/**
* The VirtualDesktopModel class provides a data model for the virtual desktops.
*/
class VirtualDesktopModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Role {
DesktopRole = Qt::UserRole + 1,
};
explicit VirtualDesktopModel(QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
public Q_SLOTS:
KWin::VirtualDesktop *create(uint position, const QString &name = QString());
void remove(uint position);
private:
KWin::VirtualDesktop *desktopFromIndex(const QModelIndex &index) const;
void handleVirtualDesktopAdded(KWin::VirtualDesktop *desktop);
void handleVirtualDesktopRemoved(KWin::VirtualDesktop *desktop);
QList<KWin::VirtualDesktop *> m_virtualDesktops;
};
} // namespace KWin
@@ -0,0 +1,332 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "windowmodel.h"
#include "core/output.h"
#include "core/outputbackend.h"
#include "virtualdesktops.h"
#include "window.h"
#include "workspace.h"
namespace KWin
{
WindowModel::WindowModel(QObject *parent)
: QAbstractListModel(parent)
{
connect(workspace(), &Workspace::windowAdded, this, &WindowModel::handleWindowAdded);
connect(workspace(), &Workspace::windowRemoved, this, &WindowModel::handleWindowRemoved);
m_windows = workspace()->windows();
for (Window *window : std::as_const(m_windows)) {
setupWindowConnections(window);
}
}
void WindowModel::markRoleChanged(Window *window, int role)
{
const QModelIndex row = index(m_windows.indexOf(window), 0);
Q_EMIT dataChanged(row, row, {role});
}
void WindowModel::setupWindowConnections(Window *window)
{
connect(window, &Window::desktopsChanged, this, [this, window]() {
markRoleChanged(window, DesktopRole);
});
connect(window, &Window::outputChanged, this, [this, window]() {
markRoleChanged(window, OutputRole);
});
connect(window, &Window::activitiesChanged, this, [this, window]() {
markRoleChanged(window, ActivityRole);
});
}
void WindowModel::handleWindowAdded(Window *window)
{
beginInsertRows(QModelIndex(), m_windows.count(), m_windows.count());
m_windows.append(window);
endInsertRows();
setupWindowConnections(window);
}
void WindowModel::handleWindowRemoved(Window *window)
{
const int index = m_windows.indexOf(window);
Q_ASSERT(index != -1);
beginRemoveRows(QModelIndex(), index, index);
m_windows.removeAt(index);
endRemoveRows();
}
QHash<int, QByteArray> WindowModel::roleNames() const
{
return {
{Qt::DisplayRole, QByteArrayLiteral("display")},
{WindowRole, QByteArrayLiteral("window")},
{OutputRole, QByteArrayLiteral("output")},
{DesktopRole, QByteArrayLiteral("desktop")},
{ActivityRole, QByteArrayLiteral("activity")},
};
}
QVariant WindowModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() < 0 || index.row() >= m_windows.count()) {
return QVariant();
}
Window *window = m_windows[index.row()];
switch (role) {
case Qt::DisplayRole:
case WindowRole:
return QVariant::fromValue(window);
case OutputRole:
return QVariant::fromValue(window->output());
case DesktopRole:
return QVariant::fromValue(window->desktops());
case ActivityRole:
return window->activities();
default:
return QVariant();
}
}
int WindowModel::rowCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : m_windows.count();
}
WindowFilterModel::WindowFilterModel(QObject *parent)
: QSortFilterProxyModel(parent)
{
}
WindowModel *WindowFilterModel::windowModel() const
{
return m_windowModel;
}
void WindowFilterModel::setWindowModel(WindowModel *windowModel)
{
if (windowModel == m_windowModel) {
return;
}
m_windowModel = windowModel;
setSourceModel(m_windowModel);
Q_EMIT windowModelChanged();
}
QString WindowFilterModel::activity() const
{
return m_activity.value_or(QString());
}
void WindowFilterModel::setActivity(const QString &activity)
{
if (m_activity != activity) {
m_activity = activity;
Q_EMIT activityChanged();
invalidateFilter();
}
}
void WindowFilterModel::resetActivity()
{
if (m_activity.has_value()) {
m_activity.reset();
Q_EMIT activityChanged();
invalidateFilter();
}
}
VirtualDesktop *WindowFilterModel::desktop() const
{
return m_desktop;
}
void WindowFilterModel::setDesktop(VirtualDesktop *desktop)
{
if (m_desktop != desktop) {
m_desktop = desktop;
Q_EMIT desktopChanged();
invalidateFilter();
}
}
void WindowFilterModel::resetDesktop()
{
setDesktop(nullptr);
}
QString WindowFilterModel::filter() const
{
return m_filter;
}
void WindowFilterModel::setFilter(const QString &filter)
{
if (filter == m_filter) {
return;
}
m_filter = filter;
Q_EMIT filterChanged();
invalidateFilter();
}
QString WindowFilterModel::screenName() const
{
return m_output ? m_output->name() : QString();
}
void WindowFilterModel::setScreenName(const QString &screen)
{
Output *output = kwinApp()->outputBackend()->findOutput(screen);
if (m_output != output) {
m_output = output;
Q_EMIT screenNameChanged();
invalidateFilter();
}
}
void WindowFilterModel::resetScreenName()
{
if (m_output) {
m_output = nullptr;
Q_EMIT screenNameChanged();
invalidateFilter();
}
}
WindowFilterModel::WindowTypes WindowFilterModel::windowType() const
{
return m_windowType.value_or(WindowTypes());
}
void WindowFilterModel::setWindowType(WindowTypes windowType)
{
if (m_windowType != windowType) {
m_windowType = windowType;
Q_EMIT windowTypeChanged();
invalidateFilter();
}
}
void WindowFilterModel::resetWindowType()
{
if (m_windowType.has_value()) {
m_windowType.reset();
Q_EMIT windowTypeChanged();
invalidateFilter();
}
}
void WindowFilterModel::setMinimizedWindows(bool show)
{
if (m_showMinimizedWindows == show) {
return;
}
m_showMinimizedWindows = show;
invalidateFilter();
Q_EMIT minimizedWindowsChanged();
}
bool WindowFilterModel::minimizedWindows() const
{
return m_showMinimizedWindows;
}
bool WindowFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
if (!m_windowModel) {
return false;
}
const QModelIndex index = m_windowModel->index(sourceRow, 0, sourceParent);
if (!index.isValid()) {
return false;
}
const QVariant data = index.data();
if (!data.isValid()) {
// an invalid QVariant is valid data
return true;
}
Window *window = qvariant_cast<Window *>(data);
if (!window || !window->isClient()) {
return false;
}
if (m_activity.has_value()) {
if (!window->isOnActivity(*m_activity)) {
return false;
}
}
if (m_desktop) {
if (!window->isOnDesktop(m_desktop)) {
return false;
}
}
if (m_output) {
if (window->output() != m_output) {
return false;
}
}
if (m_windowType.has_value()) {
if (!(windowTypeMask(window) & *m_windowType)) {
return false;
}
}
if (!m_filter.isEmpty()) {
if (window->caption().contains(m_filter, Qt::CaseInsensitive)) {
return true;
}
if (window->windowRole().contains(m_filter, Qt::CaseInsensitive)) {
return true;
}
if (window->resourceName().contains(m_filter, Qt::CaseInsensitive)) {
return true;
}
if (window->resourceClass().contains(m_filter, Qt::CaseInsensitive)) {
return true;
}
return false;
}
if (!m_showMinimizedWindows) {
return !window->isMinimized();
}
return true;
}
WindowFilterModel::WindowTypes WindowFilterModel::windowTypeMask(Window *window) const
{
WindowTypes mask;
if (window->isNormalWindow()) {
mask |= WindowType::Normal;
} else if (window->isDialog()) {
mask |= WindowType::Dialog;
} else if (window->isDock()) {
mask |= WindowType::Dock;
} else if (window->isDesktop()) {
mask |= WindowType::Desktop;
} else if (window->isNotification()) {
mask |= WindowType::Notification;
} else if (window->isCriticalNotification()) {
mask |= WindowType::CriticalNotification;
}
return mask;
}
} // namespace KWin
#include "moc_windowmodel.cpp"
@@ -0,0 +1,124 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "virtualdesktops.h"
#include <QAbstractListModel>
#include <QPointer>
#include <QSortFilterProxyModel>
#include <optional>
namespace KWin
{
class Window;
class Output;
class WindowModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles {
WindowRole = Qt::UserRole + 1,
OutputRole,
DesktopRole,
ActivityRole
};
explicit WindowModel(QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
private:
void markRoleChanged(Window *window, int role);
void handleWindowAdded(Window *window);
void handleWindowRemoved(Window *window);
void setupWindowConnections(Window *window);
QList<Window *> m_windows;
};
class WindowFilterModel : public QSortFilterProxyModel
{
Q_OBJECT
Q_PROPERTY(WindowModel *windowModel READ windowModel WRITE setWindowModel NOTIFY windowModelChanged)
Q_PROPERTY(QString activity READ activity WRITE setActivity RESET resetActivity NOTIFY activityChanged)
Q_PROPERTY(KWin::VirtualDesktop *desktop READ desktop WRITE setDesktop RESET resetDesktop NOTIFY desktopChanged)
Q_PROPERTY(QString filter READ filter WRITE setFilter NOTIFY filterChanged)
Q_PROPERTY(QString screenName READ screenName WRITE setScreenName RESET resetScreenName NOTIFY screenNameChanged)
Q_PROPERTY(WindowTypes windowType READ windowType WRITE setWindowType RESET resetWindowType NOTIFY windowTypeChanged)
Q_PROPERTY(bool minimizedWindows READ minimizedWindows WRITE setMinimizedWindows NOTIFY minimizedWindowsChanged)
public:
enum WindowType {
Normal = 0x1,
Dialog = 0x2,
Dock = 0x4,
Desktop = 0x8,
Notification = 0x10,
CriticalNotification = 0x20,
};
Q_DECLARE_FLAGS(WindowTypes, WindowType)
Q_FLAG(WindowTypes)
explicit WindowFilterModel(QObject *parent = nullptr);
WindowModel *windowModel() const;
void setWindowModel(WindowModel *windowModel);
QString activity() const;
void setActivity(const QString &activity);
void resetActivity();
VirtualDesktop *desktop() const;
void setDesktop(VirtualDesktop *desktop);
void resetDesktop();
QString filter() const;
void setFilter(const QString &filter);
QString screenName() const;
void setScreenName(const QString &screenName);
void resetScreenName();
WindowTypes windowType() const;
void setWindowType(WindowTypes windowType);
void resetWindowType();
void setMinimizedWindows(bool show);
bool minimizedWindows() const;
protected:
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
Q_SIGNALS:
void activityChanged();
void desktopChanged();
void screenNameChanged();
void windowModelChanged();
void filterChanged();
void windowTypeChanged();
void minimizedWindowsChanged();
private:
WindowTypes windowTypeMask(Window *window) const;
WindowModel *m_windowModel = nullptr;
std::optional<QString> m_activity;
QPointer<Output> m_output;
QPointer<VirtualDesktop> m_desktop;
QString m_filter;
std::optional<WindowTypes> m_windowType;
bool m_showMinimizedWindows = true;
};
} // namespace KWin
@@ -0,0 +1,439 @@
/*
SPDX-FileCopyrightText: 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org>
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "windowthumbnailitem.h"
#include "compositor.h"
#include "core/renderbackend.h"
#include "core/rendertarget.h"
#include "core/renderviewport.h"
#include "effect/effect.h"
#include "opengl/glframebuffer.h"
#include "scene/itemrenderer.h"
#include "scene/windowitem.h"
#include "scene/workspacescene.h"
#include "scripting_logging.h"
#include "window.h"
#include "workspace.h"
#include "opengl/gltexture.h"
#include <QOpenGLContext>
#include <QQuickWindow>
#include <QRunnable>
#include <QSGImageNode>
#include <QSGTextureProvider>
namespace KWin
{
static bool useGlThumbnails()
{
static bool qtQuickIsSoftware = QStringList({QStringLiteral("software"), QStringLiteral("softwarecontext")}).contains(QQuickWindow::sceneGraphBackend());
return Compositor::self()->backend() && Compositor::self()->backend()->compositingType() == OpenGLCompositing && !qtQuickIsSoftware;
}
WindowThumbnailSource::WindowThumbnailSource(QQuickWindow *view, Window *handle)
: m_view(view)
, m_handle(handle)
{
connect(handle, &Window::frameGeometryChanged, this, [this]() {
m_dirty = true;
Q_EMIT changed();
});
connect(handle, &Window::damaged, this, [this]() {
m_dirty = true;
Q_EMIT changed();
});
connect(Compositor::self()->scene(), &WorkspaceScene::preFrameRender, this, &WindowThumbnailSource::update);
m_handle->refOffscreenRendering();
}
WindowThumbnailSource::~WindowThumbnailSource()
{
if (m_handle) {
m_handle->unrefOffscreenRendering();
}
if (!m_offscreenTexture) {
return;
}
if (!QOpenGLContext::currentContext()) {
Compositor::self()->scene()->makeOpenGLContextCurrent();
}
m_offscreenTarget.reset();
m_offscreenTexture.reset();
if (m_acquireFence) {
glDeleteSync(m_acquireFence);
m_acquireFence = 0;
}
}
std::shared_ptr<WindowThumbnailSource> WindowThumbnailSource::getOrCreate(QQuickWindow *window, Window *handle)
{
using WindowThumbnailSourceKey = std::pair<QQuickWindow *, Window *>;
const WindowThumbnailSourceKey key{window, handle};
static std::map<WindowThumbnailSourceKey, std::weak_ptr<WindowThumbnailSource>> sources;
auto &source = sources[key];
if (!source.expired()) {
return source.lock();
}
auto s = std::make_shared<WindowThumbnailSource>(window, handle);
source = s;
QObject::connect(handle, &Window::destroyed, [key]() {
sources.erase(key);
});
QObject::connect(window, &QQuickWindow::destroyed, [key]() {
sources.erase(key);
});
return s;
}
WindowThumbnailSource::Frame WindowThumbnailSource::acquire()
{
return Frame{
.texture = m_offscreenTexture,
.fence = std::exchange(m_acquireFence, nullptr),
};
}
void WindowThumbnailSource::update()
{
if (m_acquireFence || !m_dirty || !m_handle) {
return;
}
Q_ASSERT(m_view);
const QRectF geometry = m_handle->visibleGeometry();
const qreal devicePixelRatio = m_view->devicePixelRatio();
const QSize textureSize = geometry.toAlignedRect().size() * devicePixelRatio;
if (!m_offscreenTexture || m_offscreenTexture->size() != textureSize) {
m_offscreenTexture = GLTexture::allocate(GL_RGBA8, textureSize);
if (!m_offscreenTexture) {
return;
}
m_offscreenTexture->setContentTransform(OutputTransform::FlipY);
m_offscreenTexture->setFilter(GL_LINEAR);
m_offscreenTexture->setWrapMode(GL_CLAMP_TO_EDGE);
m_offscreenTarget = std::make_unique<GLFramebuffer>(m_offscreenTexture.get());
}
RenderTarget offscreenRenderTarget(m_offscreenTarget.get());
RenderViewport offscreenViewport(geometry, devicePixelRatio, offscreenRenderTarget);
GLFramebuffer::pushFramebuffer(m_offscreenTarget.get());
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
// The thumbnail must be rendered using kwin's opengl context as VAOs are not
// shared across contexts. Unfortunately, this also introduces a latency of 1
// frame, which is not ideal, but it is acceptable for things such as thumbnails.
const int mask = Scene::PAINT_WINDOW_TRANSFORMED;
Compositor::self()->scene()->renderer()->renderItem(offscreenRenderTarget, offscreenViewport, m_handle->windowItem(), mask, infiniteRegion(), WindowPaintData{});
GLFramebuffer::popFramebuffer();
// The fence is needed to avoid the case where qtquick renderer starts using
// the texture while all rendering commands to it haven't completed yet.
m_dirty = false;
m_acquireFence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
Q_EMIT changed();
}
class ThumbnailTextureProvider : public QSGTextureProvider
{
public:
explicit ThumbnailTextureProvider(QQuickWindow *window);
QSGTexture *texture() const override;
void setTexture(const std::shared_ptr<GLTexture> &nativeTexture);
void setTexture(QSGTexture *texture);
private:
QQuickWindow *m_window;
std::shared_ptr<GLTexture> m_nativeTexture;
std::unique_ptr<QSGTexture> m_texture;
};
ThumbnailTextureProvider::ThumbnailTextureProvider(QQuickWindow *window)
: m_window(window)
{
}
QSGTexture *ThumbnailTextureProvider::texture() const
{
return m_texture.get();
}
void ThumbnailTextureProvider::setTexture(const std::shared_ptr<GLTexture> &nativeTexture)
{
if (m_nativeTexture != nativeTexture) {
const GLuint textureId = nativeTexture->texture();
m_nativeTexture = nativeTexture;
m_texture.reset(QNativeInterface::QSGOpenGLTexture::fromNative(textureId, m_window,
nativeTexture->size(),
QQuickWindow::TextureHasAlphaChannel));
m_texture->setFiltering(QSGTexture::Linear);
m_texture->setHorizontalWrapMode(QSGTexture::ClampToEdge);
m_texture->setVerticalWrapMode(QSGTexture::ClampToEdge);
}
// The textureChanged signal must be emitted also if only texture data changes.
Q_EMIT textureChanged();
}
void ThumbnailTextureProvider::setTexture(QSGTexture *texture)
{
m_nativeTexture = nullptr;
m_texture.reset(texture);
Q_EMIT textureChanged();
}
class ThumbnailTextureProviderCleanupJob : public QRunnable
{
public:
explicit ThumbnailTextureProviderCleanupJob(ThumbnailTextureProvider *provider)
: m_provider(provider)
{
}
void run() override
{
m_provider.reset();
}
private:
std::unique_ptr<ThumbnailTextureProvider> m_provider;
};
WindowThumbnailItem::WindowThumbnailItem(QQuickItem *parent)
: QQuickItem(parent)
{
setFlag(ItemHasContents);
connect(Compositor::self(), &Compositor::aboutToToggleCompositing,
this, &WindowThumbnailItem::resetSource);
connect(Compositor::self(), &Compositor::compositingToggled,
this, &WindowThumbnailItem::updateSource);
}
WindowThumbnailItem::~WindowThumbnailItem()
{
if (m_provider) {
if (window()) {
window()->scheduleRenderJob(new ThumbnailTextureProviderCleanupJob(m_provider),
QQuickWindow::AfterSynchronizingStage);
} else {
qCCritical(KWIN_SCRIPTING) << "Can't destroy thumbnail texture provider because window is null";
}
}
}
void WindowThumbnailItem::releaseResources()
{
if (m_provider) {
window()->scheduleRenderJob(new ThumbnailTextureProviderCleanupJob(m_provider),
QQuickWindow::AfterSynchronizingStage);
m_provider = nullptr;
}
}
void WindowThumbnailItem::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
{
if (change == QQuickItem::ItemSceneChange) {
updateSource();
}
QQuickItem::itemChange(change, value);
}
bool WindowThumbnailItem::isTextureProvider() const
{
return true;
}
QSGTextureProvider *WindowThumbnailItem::textureProvider() const
{
if (QQuickItem::isTextureProvider()) {
return QQuickItem::textureProvider();
}
if (!m_provider) {
m_provider = new ThumbnailTextureProvider(window());
}
return m_provider;
}
void WindowThumbnailItem::resetSource()
{
m_source.reset();
}
void WindowThumbnailItem::updateSource()
{
if (useGlThumbnails() && window() && m_client) {
m_source = WindowThumbnailSource::getOrCreate(window(), m_client);
connect(m_source.get(), &WindowThumbnailSource::changed, this, &WindowThumbnailItem::update);
} else {
m_source.reset();
}
}
QSGNode *WindowThumbnailItem::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
{
if (Compositor::compositing()) {
if (!m_source) {
return oldNode;
}
auto [texture, acquireFence] = m_source->acquire();
if (!texture) {
return oldNode;
}
// Wait for rendering commands to the offscreen texture complete if there are any.
if (acquireFence) {
glWaitSync(acquireFence, 0, GL_TIMEOUT_IGNORED);
glDeleteSync(acquireFence);
}
if (!m_provider) {
m_provider = new ThumbnailTextureProvider(window());
}
m_provider->setTexture(texture);
} else {
if (!m_provider) {
m_provider = new ThumbnailTextureProvider(window());
}
const QImage placeholderImage = fallbackImage();
m_provider->setTexture(window()->createTextureFromImage(placeholderImage));
}
QSGImageNode *node = static_cast<QSGImageNode *>(oldNode);
if (!node) {
node = window()->createImageNode();
node->setFiltering(QSGTexture::Linear);
}
node->setTexture(m_provider->texture());
node->setTextureCoordinatesTransform(QSGImageNode::NoTransform);
node->setRect(paintedRect());
return node;
}
QUuid WindowThumbnailItem::wId() const
{
return m_wId;
}
void WindowThumbnailItem::setWId(const QUuid &wId)
{
if (m_wId == wId) {
return;
}
m_wId = wId;
if (!m_wId.isNull()) {
setClient(workspace()->findWindow(wId));
} else if (m_client) {
m_client = nullptr;
updateSource();
updateImplicitSize();
Q_EMIT clientChanged();
}
Q_EMIT wIdChanged();
}
Window *WindowThumbnailItem::client() const
{
return m_client;
}
void WindowThumbnailItem::setClient(Window *client)
{
if (m_client == client) {
return;
}
if (m_client) {
disconnect(m_client, &Window::frameGeometryChanged,
this, &WindowThumbnailItem::updateImplicitSize);
}
m_client = client;
if (m_client) {
connect(m_client, &Window::frameGeometryChanged,
this, &WindowThumbnailItem::updateImplicitSize);
setWId(m_client->internalId());
} else {
setWId(QUuid());
}
updateSource();
updateImplicitSize();
Q_EMIT clientChanged();
}
void WindowThumbnailItem::updateImplicitSize()
{
QSize frameSize;
if (m_client) {
frameSize = m_client->frameGeometry().toAlignedRect().size();
}
setImplicitSize(frameSize.width(), frameSize.height());
}
QImage WindowThumbnailItem::fallbackImage() const
{
if (m_client) {
return m_client->icon().pixmap(window(), boundingRect().size().toSize()).toImage();
}
return QImage();
}
static QRectF centeredSize(const QRectF &boundingRect, const QSizeF &size)
{
const QSizeF scaled = size.scaled(boundingRect.size(), Qt::KeepAspectRatio);
const qreal x = boundingRect.x() + (boundingRect.width() - scaled.width()) / 2;
const qreal y = boundingRect.y() + (boundingRect.height() - scaled.height()) / 2;
return QRectF(QPointF(x, y), scaled);
}
QRectF WindowThumbnailItem::paintedRect() const
{
if (!m_client) {
return QRectF();
}
if (!Compositor::compositing()) {
const QSizeF iconSize = m_client->icon().actualSize(window(), boundingRect().size().toSize());
return centeredSize(boundingRect(), iconSize);
}
const QRectF visibleGeometry = m_client->visibleGeometry();
const QRectF frameGeometry = m_client->frameGeometry();
const QSizeF scaled = QSizeF(frameGeometry.size()).scaled(boundingRect().size(), Qt::KeepAspectRatio);
const qreal xScale = scaled.width() / frameGeometry.width();
const qreal yScale = scaled.height() / frameGeometry.height();
QRectF paintedRect(boundingRect().x() + (boundingRect().width() - scaled.width()) / 2,
boundingRect().y() + (boundingRect().height() - scaled.height()) / 2,
visibleGeometry.width() * xScale,
visibleGeometry.height() * yScale);
paintedRect.moveLeft(paintedRect.x() + (visibleGeometry.x() - frameGeometry.x()) * xScale);
paintedRect.moveTop(paintedRect.y() + (visibleGeometry.y() - frameGeometry.y()) * yScale);
return paintedRect;
}
} // namespace KWin
#include "moc_windowthumbnailitem.cpp"
@@ -0,0 +1,98 @@
/*
SPDX-FileCopyrightText: 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QQuickItem>
#include <QUuid>
#include <epoxy/gl.h>
namespace KWin
{
class Window;
class GLFramebuffer;
class GLTexture;
class ThumbnailTextureProvider;
class WindowThumbnailSource;
class WindowThumbnailSource : public QObject
{
Q_OBJECT
public:
WindowThumbnailSource(QQuickWindow *view, Window *handle);
~WindowThumbnailSource() override;
static std::shared_ptr<WindowThumbnailSource> getOrCreate(QQuickWindow *window, Window *handle);
struct Frame
{
std::shared_ptr<GLTexture> texture;
GLsync fence;
};
Frame acquire();
Q_SIGNALS:
void changed();
private:
void update();
QPointer<QQuickWindow> m_view;
QPointer<Window> m_handle;
std::shared_ptr<GLTexture> m_offscreenTexture;
std::unique_ptr<GLFramebuffer> m_offscreenTarget;
GLsync m_acquireFence = 0;
bool m_dirty = true;
};
class WindowThumbnailItem : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(QUuid wId READ wId WRITE setWId NOTIFY wIdChanged)
Q_PROPERTY(KWin::Window *client READ client WRITE setClient NOTIFY clientChanged)
public:
explicit WindowThumbnailItem(QQuickItem *parent = nullptr);
~WindowThumbnailItem() override;
QUuid wId() const;
void setWId(const QUuid &wId);
Window *client() const;
void setClient(Window *client);
QSGTextureProvider *textureProvider() const override;
bool isTextureProvider() const override;
QSGNode *updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) override;
protected:
void releaseResources() override;
void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) override;
Q_SIGNALS:
void wIdChanged();
void clientChanged();
private:
QImage fallbackImage() const;
QRectF paintedRect() const;
void updateImplicitSize();
void updateSource();
void resetSource();
QUuid m_wId;
QPointer<Window> m_client;
mutable ThumbnailTextureProvider *m_provider = nullptr;
std::shared_ptr<WindowThumbnailSource> m_source;
};
} // namespace KWin
@@ -0,0 +1,462 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2010 Rohan Prabhu <rohan@rohanprabhu.com>
SPDX-FileCopyrightText: 2011, 2012 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "workspace_wrapper.h"
#include "core/output.h"
#include "core/outputbackend.h"
#include "cursor.h"
#include "effect/effecthandler.h"
#include "outline.h"
#include "scripting_logging.h"
#include "tiles/tilemanager.h"
#include "virtualdesktops.h"
#include "window.h"
#include "workspace.h"
#if KWIN_BUILD_ACTIVITIES
#include "activities.h"
#endif
#if KWIN_BUILD_X11
#include "x11window.h"
#endif
#include <QJSEngine>
namespace KWin
{
WorkspaceWrapper::WorkspaceWrapper(QObject *parent)
: QObject(parent)
{
KWin::Workspace *ws = KWin::Workspace::self();
KWin::VirtualDesktopManager *vds = KWin::VirtualDesktopManager::self();
connect(ws, &Workspace::windowAdded, this, &WorkspaceWrapper::windowAdded);
connect(ws, &Workspace::windowRemoved, this, &WorkspaceWrapper::windowRemoved);
connect(ws, &Workspace::windowActivated, this, &WorkspaceWrapper::windowActivated);
connect(vds, &VirtualDesktopManager::desktopAdded, this, &WorkspaceWrapper::desktopsChanged);
connect(vds, &VirtualDesktopManager::desktopRemoved, this, &WorkspaceWrapper::desktopsChanged);
connect(vds, &VirtualDesktopManager::layoutChanged, this, &WorkspaceWrapper::desktopLayoutChanged);
connect(vds, &VirtualDesktopManager::currentChanged, this, &WorkspaceWrapper::currentDesktopChanged);
#if KWIN_BUILD_ACTIVITIES
if (KWin::Activities *activities = ws->activities()) {
connect(activities, &Activities::currentChanged, this, &WorkspaceWrapper::currentActivityChanged);
connect(activities, &Activities::added, this, &WorkspaceWrapper::activitiesChanged);
connect(activities, &Activities::added, this, &WorkspaceWrapper::activityAdded);
connect(activities, &Activities::removed, this, &WorkspaceWrapper::activitiesChanged);
connect(activities, &Activities::removed, this, &WorkspaceWrapper::activityRemoved);
}
#endif
connect(ws, &Workspace::geometryChanged, this, &WorkspaceWrapper::virtualScreenSizeChanged);
connect(ws, &Workspace::geometryChanged, this, &WorkspaceWrapper::virtualScreenGeometryChanged);
connect(ws, &Workspace::outputsChanged, this, &WorkspaceWrapper::screensChanged);
connect(ws, &Workspace::outputOrderChanged, this, &WorkspaceWrapper::screenOrderChanged);
connect(Cursors::self()->mouse(), &Cursor::posChanged, this, &WorkspaceWrapper::cursorPosChanged);
}
VirtualDesktop *WorkspaceWrapper::currentDesktop() const
{
return VirtualDesktopManager::self()->currentDesktop();
}
QList<VirtualDesktop *> WorkspaceWrapper::desktops() const
{
return VirtualDesktopManager::self()->desktops();
}
void WorkspaceWrapper::setCurrentDesktop(VirtualDesktop *desktop)
{
VirtualDesktopManager::self()->setCurrent(desktop);
}
Window *WorkspaceWrapper::activeWindow() const
{
return workspace()->activeWindow();
}
QString WorkspaceWrapper::currentActivity() const
{
#if KWIN_BUILD_ACTIVITIES
if (!Workspace::self()->activities()) {
return QString();
}
return Workspace::self()->activities()->current();
#else
return QString();
#endif
}
void WorkspaceWrapper::setCurrentActivity(const QString &activity)
{
#if KWIN_BUILD_ACTIVITIES
if (Workspace::self()->activities()) {
Workspace::self()->activities()->setCurrent(activity, nullptr);
}
#endif
}
QStringList WorkspaceWrapper::activityList() const
{
#if KWIN_BUILD_ACTIVITIES
if (!Workspace::self()->activities()) {
return QStringList();
}
return Workspace::self()->activities()->all();
#else
return QStringList();
#endif
}
QPoint WorkspaceWrapper::cursorPos() const
{
return Cursors::self()->mouse()->pos().toPoint();
}
#define SLOTWRAPPER(name) \
void WorkspaceWrapper::name() \
{ \
Workspace::self()->name(); \
}
SLOTWRAPPER(slotToggleShowDesktop)
SLOTWRAPPER(slotWindowMaximize)
SLOTWRAPPER(slotWindowMaximizeVertical)
SLOTWRAPPER(slotWindowMaximizeHorizontal)
SLOTWRAPPER(slotWindowMinimize)
SLOTWRAPPER(slotWindowShade)
SLOTWRAPPER(slotWindowRaise)
SLOTWRAPPER(slotWindowLower)
SLOTWRAPPER(slotWindowRaiseOrLower)
SLOTWRAPPER(slotActivateAttentionWindow)
SLOTWRAPPER(slotWindowMoveLeft)
SLOTWRAPPER(slotWindowMoveRight)
SLOTWRAPPER(slotWindowMoveUp)
SLOTWRAPPER(slotWindowMoveDown)
SLOTWRAPPER(slotWindowExpandHorizontal)
SLOTWRAPPER(slotWindowExpandVertical)
SLOTWRAPPER(slotWindowShrinkHorizontal)
SLOTWRAPPER(slotWindowShrinkVertical)
SLOTWRAPPER(slotIncreaseWindowOpacity)
SLOTWRAPPER(slotLowerWindowOpacity)
SLOTWRAPPER(slotWindowOperations)
SLOTWRAPPER(slotWindowClose)
SLOTWRAPPER(slotWindowMove)
SLOTWRAPPER(slotWindowResize)
SLOTWRAPPER(slotWindowAbove)
SLOTWRAPPER(slotWindowBelow)
SLOTWRAPPER(slotWindowOnAllDesktops)
SLOTWRAPPER(slotWindowFullScreen)
SLOTWRAPPER(slotWindowNoBorder)
SLOTWRAPPER(slotWindowToNextDesktop)
SLOTWRAPPER(slotWindowToPreviousDesktop)
SLOTWRAPPER(slotWindowToDesktopRight)
SLOTWRAPPER(slotWindowToDesktopLeft)
SLOTWRAPPER(slotWindowToDesktopUp)
SLOTWRAPPER(slotWindowToDesktopDown)
SLOTWRAPPER(slotWindowToPrevScreen)
SLOTWRAPPER(slotWindowToNextScreen)
SLOTWRAPPER(slotWindowToLeftScreen)
SLOTWRAPPER(slotWindowToRightScreen)
SLOTWRAPPER(slotWindowToAboveScreen)
SLOTWRAPPER(slotWindowToBelowScreen)
SLOTWRAPPER(slotSwitchToPrevScreen)
SLOTWRAPPER(slotSwitchToNextScreen)
SLOTWRAPPER(slotSwitchToLeftScreen)
SLOTWRAPPER(slotSwitchToRightScreen)
SLOTWRAPPER(slotSwitchToAboveScreen)
SLOTWRAPPER(slotSwitchToBelowScreen)
#undef SLOTWRAPPER
#define SLOTWRAPPER(name, modes) \
void WorkspaceWrapper::name() \
{ \
Workspace::self()->quickTileWindow(modes); \
}
SLOTWRAPPER(slotWindowQuickTileLeft, QuickTileFlag::Left)
SLOTWRAPPER(slotWindowQuickTileRight, QuickTileFlag::Right)
SLOTWRAPPER(slotWindowQuickTileTop, QuickTileFlag::Top)
SLOTWRAPPER(slotWindowQuickTileBottom, QuickTileFlag::Bottom)
SLOTWRAPPER(slotWindowQuickTileTopLeft, QuickTileFlag::Top | QuickTileFlag::Left)
SLOTWRAPPER(slotWindowQuickTileTopRight, QuickTileFlag::Top | QuickTileFlag::Right)
SLOTWRAPPER(slotWindowQuickTileBottomLeft, QuickTileFlag::Bottom | QuickTileFlag::Left)
SLOTWRAPPER(slotWindowQuickTileBottomRight, QuickTileFlag::Bottom | QuickTileFlag::Right)
#undef SLOTWRAPPER
#define SLOTWRAPPER(name, direction) \
void WorkspaceWrapper::name() \
{ \
Workspace::self()->switchWindow(Workspace::direction); \
}
SLOTWRAPPER(slotSwitchWindowUp, DirectionNorth)
SLOTWRAPPER(slotSwitchWindowDown, DirectionSouth)
SLOTWRAPPER(slotSwitchWindowRight, DirectionEast)
SLOTWRAPPER(slotSwitchWindowLeft, DirectionWest)
#undef SLOTWRAPPER
#define SLOTWRAPPER(name, direction) \
void WorkspaceWrapper::name() \
{ \
VirtualDesktopManager::self()->moveTo(VirtualDesktopManager::Direction::direction, options->isRollOverDesktops()); \
}
SLOTWRAPPER(slotSwitchDesktopNext, Next)
SLOTWRAPPER(slotSwitchDesktopPrevious, Previous)
SLOTWRAPPER(slotSwitchDesktopRight, Right)
SLOTWRAPPER(slotSwitchDesktopLeft, Left)
SLOTWRAPPER(slotSwitchDesktopUp, Up)
SLOTWRAPPER(slotSwitchDesktopDown, Down)
#undef SLOTWRAPPER
void WorkspaceWrapper::setActiveWindow(KWin::Window *window)
{
KWin::Workspace::self()->activateWindow(window);
}
QSize WorkspaceWrapper::workspaceSize() const
{
return QSize(workspaceWidth(), workspaceHeight());
}
QRectF WorkspaceWrapper::clientArea(ClientAreaOption option, const KWin::Window *c) const
{
if (!c) {
return QRectF();
}
return Workspace::self()->clientArea(static_cast<clientAreaOption>(option), c);
}
QRectF WorkspaceWrapper::clientArea(ClientAreaOption option, KWin::Window *c) const
{
if (!c) {
return QRectF();
}
return Workspace::self()->clientArea(static_cast<clientAreaOption>(option), c);
}
QRectF WorkspaceWrapper::clientArea(ClientAreaOption option, Output *output, VirtualDesktop *desktop) const
{
if (!output || !desktop) {
qCWarning(KWIN_SCRIPTING) << "clientArea needs valid output:" << output << "and desktop:" << desktop << "arguments";
return QRect();
}
return workspace()->clientArea(static_cast<clientAreaOption>(option), output, desktop);
}
void WorkspaceWrapper::createDesktop(int position, const QString &name) const
{
VirtualDesktopManager::self()->createVirtualDesktop(position, name);
}
void WorkspaceWrapper::removeDesktop(VirtualDesktop *desktop) const
{
VirtualDesktopManager::self()->removeVirtualDesktop(desktop->id());
}
QString WorkspaceWrapper::supportInformation() const
{
return Workspace::self()->supportInformation();
}
void WorkspaceWrapper::showOutline(const QRect &geometry)
{
workspace()->outline()->show(geometry);
}
void WorkspaceWrapper::showOutline(int x, int y, int width, int height)
{
workspace()->outline()->show(QRect(x, y, width, height));
}
void WorkspaceWrapper::hideOutline()
{
workspace()->outline()->hide();
}
QList<KWin::Window *> WorkspaceWrapper::stackingOrder() const
{
return workspace()->stackingOrder();
}
void WorkspaceWrapper::raiseWindow(KWin::Window *window)
{
if (window) {
KWin::Workspace::self()->raiseWindow(window);
}
}
#if KWIN_BUILD_X11
Window *WorkspaceWrapper::getClient(qulonglong windowId)
{
auto window = Workspace::self()->findClient(Predicate::WindowMatch, windowId);
QJSEngine::setObjectOwnership(window, QJSEngine::CppOwnership);
return window;
}
#endif
QList<KWin::Window *> WorkspaceWrapper::windowAt(const QPointF &pos, int count) const
{
QList<KWin::Window *> result;
int found = 0;
const QList<Window *> &stacking = workspace()->stackingOrder();
if (stacking.isEmpty()) {
return result;
}
auto it = stacking.end();
do {
if (found == count) {
return result;
}
--it;
Window *window = (*it);
if (window->isDeleted()) {
continue;
}
if (!window->isOnCurrentActivity() || !window->isOnCurrentDesktop() || window->isMinimized() || window->isHidden() || window->isHiddenByShowDesktop()) {
continue;
}
if (window->hitTest(pos)) {
result.append(window);
found++;
}
} while (it != stacking.begin());
return result;
}
bool WorkspaceWrapper::isEffectActive(const QString &pluginId) const
{
if (!effects) {
return false;
}
return effects->isEffectActive(pluginId);
}
QSize WorkspaceWrapper::desktopGridSize() const
{
return VirtualDesktopManager::self()->grid().size();
}
int WorkspaceWrapper::desktopGridWidth() const
{
return desktopGridSize().width();
}
int WorkspaceWrapper::desktopGridHeight() const
{
return desktopGridSize().height();
}
int WorkspaceWrapper::workspaceHeight() const
{
return desktopGridHeight() * workspace()->geometry().height();
}
int WorkspaceWrapper::workspaceWidth() const
{
return desktopGridWidth() * workspace()->geometry().width();
}
Output *WorkspaceWrapper::activeScreen() const
{
return workspace()->activeOutput();
}
QList<Output *> WorkspaceWrapper::screens() const
{
return workspace()->outputs();
}
QList<Output *> WorkspaceWrapper::screenOrder() const
{
return workspace()->outputOrder();
}
Output *WorkspaceWrapper::screenAt(const QPointF &pos) const
{
auto output = workspace()->outputAt(pos);
QJSEngine::setObjectOwnership(output, QJSEngine::CppOwnership);
return output;
}
QRect WorkspaceWrapper::virtualScreenGeometry() const
{
return workspace()->geometry();
}
QSize WorkspaceWrapper::virtualScreenSize() const
{
return workspace()->geometry().size();
}
void WorkspaceWrapper::sendClientToScreen(Window *client, Output *output)
{
client->sendToOutput(output);
}
KWin::TileManager *WorkspaceWrapper::tilingForScreen(const QString &screenName) const
{
Output *output = kwinApp()->outputBackend()->findOutput(screenName);
if (output) {
auto tileManager = workspace()->tileManager(output);
QJSEngine::setObjectOwnership(tileManager, QJSEngine::CppOwnership);
return tileManager;
}
return nullptr;
}
KWin::TileManager *WorkspaceWrapper::tilingForScreen(Output *output) const
{
auto tileManager = workspace()->tileManager(output);
QJSEngine::setObjectOwnership(tileManager, QJSEngine::CppOwnership);
return tileManager;
}
QtScriptWorkspaceWrapper::QtScriptWorkspaceWrapper(QObject *parent)
: WorkspaceWrapper(parent)
{
}
QList<KWin::Window *> QtScriptWorkspaceWrapper::windowList() const
{
return workspace()->windows();
}
QQmlListProperty<KWin::Window> DeclarativeScriptWorkspaceWrapper::windows()
{
return QQmlListProperty<KWin::Window>(this, nullptr, &DeclarativeScriptWorkspaceWrapper::countWindowList, &DeclarativeScriptWorkspaceWrapper::atWindowList);
}
qsizetype DeclarativeScriptWorkspaceWrapper::countWindowList(QQmlListProperty<KWin::Window> *windows)
{
return workspace()->windows().size();
}
KWin::Window *DeclarativeScriptWorkspaceWrapper::atWindowList(QQmlListProperty<KWin::Window> *windows, qsizetype index)
{
return workspace()->windows().at(index);
}
DeclarativeScriptWorkspaceWrapper::DeclarativeScriptWorkspaceWrapper(QObject *parent)
: WorkspaceWrapper(parent)
{
}
} // KWin
#include "moc_workspace_wrapper.cpp"
@@ -0,0 +1,422 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2010 Rohan Prabhu <rohan@rohanprabhu.com>
SPDX-FileCopyrightText: 2012 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "effect/globals.h"
#include <QObject>
#include <QQmlListProperty>
#include <QRect>
#include <QSize>
#include <QStringList>
namespace KWin
{
// forward declarations
class TileManager;
class Window;
class Output;
class VirtualDesktop;
class WorkspaceWrapper : public QObject
{
Q_OBJECT
Q_PROPERTY(QList<KWin::VirtualDesktop *> desktops READ desktops NOTIFY desktopsChanged)
Q_PROPERTY(KWin::VirtualDesktop *currentDesktop READ currentDesktop WRITE setCurrentDesktop NOTIFY currentDesktopChanged)
Q_PROPERTY(KWin::Window *activeWindow READ activeWindow WRITE setActiveWindow NOTIFY windowActivated)
// TODO: write and notify?
Q_PROPERTY(QSize desktopGridSize READ desktopGridSize NOTIFY desktopLayoutChanged)
Q_PROPERTY(int desktopGridWidth READ desktopGridWidth NOTIFY desktopLayoutChanged)
Q_PROPERTY(int desktopGridHeight READ desktopGridHeight NOTIFY desktopLayoutChanged)
Q_PROPERTY(int workspaceWidth READ workspaceWidth)
Q_PROPERTY(int workspaceHeight READ workspaceHeight)
Q_PROPERTY(QSize workspaceSize READ workspaceSize)
Q_PROPERTY(KWin::Output *activeScreen READ activeScreen)
Q_PROPERTY(QList<KWin::Output *> screens READ screens NOTIFY screensChanged)
Q_PROPERTY(QList<KWin::Output *> screenOrder READ screenOrder NOTIFY screenOrderChanged)
Q_PROPERTY(QString currentActivity READ currentActivity WRITE setCurrentActivity NOTIFY currentActivityChanged)
Q_PROPERTY(QStringList activities READ activityList NOTIFY activitiesChanged)
/**
* The bounding size of all screens combined. Overlapping areas
* are not counted multiple times.
* @see virtualScreenGeometry
*/
Q_PROPERTY(QSize virtualScreenSize READ virtualScreenSize NOTIFY virtualScreenSizeChanged)
/**
* The bounding geometry of all screens combined. Always starts at (0,0) and has
* virtualScreenSize as it's size.
* @see virtualScreenSize
*/
Q_PROPERTY(QRect virtualScreenGeometry READ virtualScreenGeometry NOTIFY virtualScreenGeometryChanged)
/**
* List of Clients currently managed by KWin, orderd by
* their visibility (later ones cover earlier ones).
*/
Q_PROPERTY(QList<KWin::Window *> stackingOrder READ stackingOrder)
/**
* The current position of the cursor.
*/
Q_PROPERTY(QPoint cursorPos READ cursorPos NOTIFY cursorPosChanged)
private:
Q_DISABLE_COPY(WorkspaceWrapper)
Q_SIGNALS:
void windowAdded(KWin::Window *window);
void windowRemoved(KWin::Window *window);
void windowActivated(KWin::Window *window);
/**
* This signal is emitted when a virtual desktop is added or removed.
*/
void desktopsChanged();
/**
* Signal emitted whenever the layout of virtual desktops changed.
* That is desktopGrid(Size/Width/Height) will have new values.
* @since 4.11
*/
void desktopLayoutChanged();
/**
* Emitted when the output list changes, e.g. an output is connected or removed.
*/
void screensChanged();
/**
* Emitted when the output order list changes, e.g. the primary output changes.
*/
void screenOrderChanged();
/**
* Signal emitted whenever the current activity changed.
* @param id id of the new activity
*/
void currentActivityChanged(const QString &id);
/**
* Signal emitted whenever the list of activities changed.
* @param id id of the new activity
*/
void activitiesChanged(const QString &id);
/**
* This signal is emitted when a new activity is added
* @param id id of the new activity
*/
void activityAdded(const QString &id);
/**
* This signal is emitted when the activity
* is removed
* @param id id of the removed activity
*/
void activityRemoved(const QString &id);
/**
* Emitted whenever the virtualScreenSize changes.
* @see virtualScreenSize()
* @since 5.0
*/
void virtualScreenSizeChanged();
/**
* Emitted whenever the virtualScreenGeometry changes.
* @see virtualScreenGeometry()
* @since 5.0
*/
void virtualScreenGeometryChanged();
/**
* This signal is emitted when the current virtual desktop changes.
*/
void currentDesktopChanged(KWin::VirtualDesktop *previous);
/**
* This signal is emitted when the cursor position changes.
* @see cursorPos()
*/
void cursorPosChanged();
public:
//------------------------------------------------------------------
// enums copy&pasted from kwinglobals.h because qtscript is evil
enum ClientAreaOption {
///< geometry where a window will be initially placed after being mapped
PlacementArea,
///< window movement snapping area? ignore struts
MovementArea,
///< geometry to which a window will be maximized
MaximizeArea,
///< like MaximizeArea, but ignore struts - used e.g. for topmenu
MaximizeFullArea,
///< area for fullscreen windows
FullScreenArea,
///< whole workarea (all screens together)
WorkArea,
///< whole area (all screens together), ignore struts
FullArea,
///< one whole screen, ignore struts
ScreenArea
};
Q_ENUM(ClientAreaOption)
enum ElectricBorder {
ElectricTop,
ElectricTopRight,
ElectricRight,
ElectricBottomRight,
ElectricBottom,
ElectricBottomLeft,
ElectricLeft,
ElectricTopLeft,
ELECTRIC_COUNT,
ElectricNone
};
Q_ENUM(ElectricBorder)
protected:
explicit WorkspaceWrapper(QObject *parent = nullptr);
public:
Window *activeWindow() const;
void setActiveWindow(Window *window);
QString currentActivity() const;
void setCurrentActivity(const QString &activity);
QSize desktopGridSize() const;
int desktopGridWidth() const;
int desktopGridHeight() const;
int workspaceWidth() const;
int workspaceHeight() const;
QSize workspaceSize() const;
KWin::Output *activeScreen() const;
QList<KWin::Output *> screens() const;
QList<KWin::Output *> screenOrder() const;
QStringList activityList() const;
QSize virtualScreenSize() const;
QRect virtualScreenGeometry() const;
QPoint cursorPos() const;
QList<VirtualDesktop *> desktops() const;
VirtualDesktop *currentDesktop() const;
void setCurrentDesktop(VirtualDesktop *desktop);
Q_INVOKABLE KWin::Output *screenAt(const QPointF &pos) const;
Q_INVOKABLE KWin::TileManager *tilingForScreen(const QString &screenName) const;
Q_INVOKABLE KWin::TileManager *tilingForScreen(KWin::Output *output) const;
/**
* Returns the geometry a Client can use with the specified option.
* This method should be preferred over other methods providing screen sizes as the
* various options take constraints such as struts set on panels into account.
* This method is also multi screen aware, but there are also options to get full areas.
* @param option The type of area which should be considered
* @param screen The screen for which the area should be considered
* @param desktop The desktop for which the area should be considered, in general there should not be a difference
* @returns The specified screen geometry
*/
Q_SCRIPTABLE QRectF clientArea(ClientAreaOption option, KWin::Output *output, KWin::VirtualDesktop *desktop) const;
/**
* Overloaded method for convenience.
* @param client The Client for which the area should be retrieved
* @returns The specified screen geometry
*/
Q_SCRIPTABLE QRectF clientArea(ClientAreaOption option, KWin::Window *client) const;
Q_SCRIPTABLE QRectF clientArea(ClientAreaOption option, const KWin::Window *client) const;
/**
* Create a new virtual desktop at the requested position.
* @param position The position of the desktop. It should be in range [0, count].
* @param name The name for the new desktop, if empty the default name will be used.
*/
Q_SCRIPTABLE void createDesktop(int position, const QString &name) const;
/**
* Removes the specified virtual desktop.
*/
Q_SCRIPTABLE void removeDesktop(KWin::VirtualDesktop *desktop) const;
/**
* Provides support information about the currently running KWin instance.
*/
Q_SCRIPTABLE QString supportInformation() const;
/**
* List of Clients currently managed by KWin, orderd by
* their visibility (later ones cover earlier ones).
*/
QList<KWin::Window *> stackingOrder() const;
/**
* Raises a Window above all others on the screen.
* @param window The Window to raise
*/
Q_INVOKABLE void raiseWindow(KWin::Window *window);
#if KWIN_BUILD_X11
/**
* Finds the Client with the given @p windowId.
* @param windowId The window Id of the Client
* @return The found Client or @c null
*/
Q_SCRIPTABLE KWin::Window *getClient(qulonglong windowId);
#endif
/**
* Finds up to count windows at a particular location,
* prioritizing the topmost one first. A negative count
* returns all matching clients.
* @param pos The location to look for
* @param count The number of clients to return
* @return A list of Client objects
*/
Q_INVOKABLE QList<KWin::Window *> windowAt(const QPointF &pos, int count = 1) const;
/**
* Checks if a specific effect is currently active.
* @param pluginId The plugin Id of the effect to check.
* @return @c true if the effect is loaded and currently active, @c false otherwise.
* @since 6.0
*/
Q_INVOKABLE bool isEffectActive(const QString &pluginId) const;
public Q_SLOTS:
// all the available key bindings
void slotSwitchDesktopNext();
void slotSwitchDesktopPrevious();
void slotSwitchDesktopRight();
void slotSwitchDesktopLeft();
void slotSwitchDesktopUp();
void slotSwitchDesktopDown();
void slotSwitchToNextScreen();
void slotSwitchToPrevScreen();
void slotSwitchToRightScreen();
void slotSwitchToLeftScreen();
void slotSwitchToAboveScreen();
void slotSwitchToBelowScreen();
void slotWindowToNextScreen();
void slotWindowToPrevScreen();
void slotWindowToRightScreen();
void slotWindowToLeftScreen();
void slotWindowToAboveScreen();
void slotWindowToBelowScreen();
void slotToggleShowDesktop();
void slotWindowMaximize();
void slotWindowMaximizeVertical();
void slotWindowMaximizeHorizontal();
void slotWindowMinimize();
void slotWindowShade();
void slotWindowRaise();
void slotWindowLower();
void slotWindowRaiseOrLower();
void slotActivateAttentionWindow();
void slotWindowMoveLeft();
void slotWindowMoveRight();
void slotWindowMoveUp();
void slotWindowMoveDown();
void slotWindowExpandHorizontal();
void slotWindowExpandVertical();
void slotWindowShrinkHorizontal();
void slotWindowShrinkVertical();
void slotWindowQuickTileLeft();
void slotWindowQuickTileRight();
void slotWindowQuickTileTop();
void slotWindowQuickTileBottom();
void slotWindowQuickTileTopLeft();
void slotWindowQuickTileTopRight();
void slotWindowQuickTileBottomLeft();
void slotWindowQuickTileBottomRight();
void slotSwitchWindowUp();
void slotSwitchWindowDown();
void slotSwitchWindowRight();
void slotSwitchWindowLeft();
void slotIncreaseWindowOpacity();
void slotLowerWindowOpacity();
void slotWindowOperations();
void slotWindowClose();
void slotWindowMove();
void slotWindowResize();
void slotWindowAbove();
void slotWindowBelow();
void slotWindowOnAllDesktops();
void slotWindowFullScreen();
void slotWindowNoBorder();
void slotWindowToNextDesktop();
void slotWindowToPreviousDesktop();
void slotWindowToDesktopRight();
void slotWindowToDesktopLeft();
void slotWindowToDesktopUp();
void slotWindowToDesktopDown();
/**
* Sends the Window to the given @p output.
*/
void sendClientToScreen(KWin::Window *client, KWin::Output *output);
/**
* Shows an outline at the specified @p geometry.
* If an outline is already shown the outline is moved to the new position.
* Use hideOutline to remove the outline again.
*/
void showOutline(const QRect &geometry);
/**
* Overloaded method for convenience.
*/
void showOutline(int x, int y, int width, int height);
/**
* Hides the outline previously shown by showOutline.
*/
void hideOutline();
};
class QtScriptWorkspaceWrapper : public WorkspaceWrapper
{
Q_OBJECT
public:
/**
* List of windows currently managed by KWin.
*/
Q_INVOKABLE QList<KWin::Window *> windowList() const;
explicit QtScriptWorkspaceWrapper(QObject *parent = nullptr);
};
class DeclarativeScriptWorkspaceWrapper : public WorkspaceWrapper
{
Q_OBJECT
Q_PROPERTY(QQmlListProperty<KWin::Window> windows READ windows)
public:
QQmlListProperty<KWin::Window> windows();
static qsizetype countWindowList(QQmlListProperty<KWin::Window> *window);
static KWin::Window *atWindowList(QQmlListProperty<KWin::Window> *windows, qsizetype index);
explicit DeclarativeScriptWorkspaceWrapper(QObject *parent = nullptr);
};
}