File indexing completed on 2024-05-19 16:34:52

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2010 Rohan Prabhu <rohan@rohanprabhu.com>
0006     SPDX-FileCopyrightText: 2011 Martin Gräßlin <mgraesslin@kde.org>
0007     SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include "scripting.h"
0013 // own
0014 #include "dbuscall.h"
0015 #include "desktopbackgrounditem.h"
0016 #include "kwinquickeffect.h"
0017 #include "screenedgeitem.h"
0018 #include "scripting_logging.h"
0019 #include "scriptingutils.h"
0020 #include "windowthumbnailitem.h"
0021 #include "workspace_wrapper.h"
0022 
0023 #include "v2/clientmodel.h"
0024 #include "v3/clientmodel.h"
0025 #include "v3/virtualdesktopmodel.h"
0026 
0027 #include "input.h"
0028 #include "options.h"
0029 #include "screenedge.h"
0030 #include "tiles/tilemanager.h"
0031 #include "virtualdesktops.h"
0032 #include "workspace.h"
0033 #include "x11window.h"
0034 // KDE
0035 #include <KConfigGroup>
0036 #include <KGlobalAccel>
0037 #include <KPackage/PackageLoader>
0038 // Qt
0039 #include <QDBusConnection>
0040 #include <QDBusPendingCallWatcher>
0041 #include <QDebug>
0042 #include <QFutureWatcher>
0043 #include <QMenu>
0044 #include <QQmlContext>
0045 #include <QQmlEngine>
0046 #include <QQmlExpression>
0047 #include <QQuickWindow>
0048 #include <QSettings>
0049 #include <QStandardPaths>
0050 #include <QtConcurrentRun>
0051 
0052 #include "scriptadaptor.h"
0053 
0054 static QRect scriptValueToRect(const QJSValue &value)
0055 {
0056     return QRect(value.property(QStringLiteral("x")).toInt(),
0057                  value.property(QStringLiteral("y")).toInt(),
0058                  value.property(QStringLiteral("width")).toInt(),
0059                  value.property(QStringLiteral("height")).toInt());
0060 }
0061 
0062 static QRectF scriptValueToRectF(const QJSValue &value)
0063 {
0064     return QRectF(value.property(QStringLiteral("x")).toNumber(),
0065                   value.property(QStringLiteral("y")).toNumber(),
0066                   value.property(QStringLiteral("width")).toNumber(),
0067                   value.property(QStringLiteral("height")).toNumber());
0068 }
0069 
0070 static QPoint scriptValueToPoint(const QJSValue &value)
0071 {
0072     return QPoint(value.property(QStringLiteral("x")).toInt(),
0073                   value.property(QStringLiteral("y")).toInt());
0074 }
0075 
0076 static QPointF scriptValueToPointF(const QJSValue &value)
0077 {
0078     return QPointF(value.property(QStringLiteral("x")).toNumber(),
0079                    value.property(QStringLiteral("y")).toNumber());
0080 }
0081 
0082 static QSize scriptValueToSize(const QJSValue &value)
0083 {
0084     return QSize(value.property(QStringLiteral("width")).toInt(),
0085                  value.property(QStringLiteral("height")).toInt());
0086 }
0087 
0088 static QSizeF scriptValueToSizeF(const QJSValue &value)
0089 {
0090     return QSizeF(value.property(QStringLiteral("width")).toNumber(),
0091                   value.property(QStringLiteral("height")).toNumber());
0092 }
0093 
0094 KWin::AbstractScript::AbstractScript(int id, QString scriptName, QString pluginName, QObject *parent)
0095     : QObject(parent)
0096     , m_scriptId(id)
0097     , m_fileName(scriptName)
0098     , m_pluginName(pluginName)
0099     , m_running(false)
0100 {
0101     if (m_pluginName.isNull()) {
0102         m_pluginName = scriptName;
0103     }
0104 
0105     new ScriptAdaptor(this);
0106     QDBusConnection::sessionBus().registerObject(QLatin1Char('/') + QString::number(scriptId()), this, QDBusConnection::ExportAdaptors);
0107 }
0108 
0109 KWin::AbstractScript::~AbstractScript()
0110 {
0111 }
0112 
0113 KConfigGroup KWin::AbstractScript::config() const
0114 {
0115     return kwinApp()->config()->group(QLatin1String("Script-") + m_pluginName);
0116 }
0117 
0118 void KWin::AbstractScript::stop()
0119 {
0120     deleteLater();
0121 }
0122 
0123 KWin::ScriptTimer::ScriptTimer(QObject *parent)
0124     : QTimer(parent)
0125 {
0126 }
0127 
0128 KWin::Script::Script(int id, QString scriptName, QString pluginName, QObject *parent)
0129     : AbstractScript(id, scriptName, pluginName, parent)
0130     , m_engine(new QJSEngine(this))
0131     , m_starting(false)
0132 {
0133     // TODO: Remove in kwin 6. We have these converters only for compatibility reasons.
0134     if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QRect>()) {
0135         QMetaType::registerConverter<QJSValue, QRect>(scriptValueToRect);
0136     }
0137     if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QRectF>()) {
0138         QMetaType::registerConverter<QJSValue, QRectF>(scriptValueToRectF);
0139     }
0140 
0141     if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QPoint>()) {
0142         QMetaType::registerConverter<QJSValue, QPoint>(scriptValueToPoint);
0143     }
0144     if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QPointF>()) {
0145         QMetaType::registerConverter<QJSValue, QPointF>(scriptValueToPointF);
0146     }
0147 
0148     if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QSize>()) {
0149         QMetaType::registerConverter<QJSValue, QSize>(scriptValueToSize);
0150     }
0151     if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QSizeF>()) {
0152         QMetaType::registerConverter<QJSValue, QSizeF>(scriptValueToSizeF);
0153     }
0154 
0155     qRegisterMetaType<QList<KWin::Window *>>();
0156 }
0157 
0158 KWin::Script::~Script()
0159 {
0160 }
0161 
0162 void KWin::Script::run()
0163 {
0164     if (running() || m_starting) {
0165         return;
0166     }
0167 
0168     if (calledFromDBus()) {
0169         m_invocationContext = message();
0170         setDelayedReply(true);
0171     }
0172 
0173     m_starting = true;
0174     QFutureWatcher<QByteArray> *watcher = new QFutureWatcher<QByteArray>(this);
0175     connect(watcher, &QFutureWatcherBase::finished, this, &Script::slotScriptLoadedFromFile);
0176 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0177     watcher->setFuture(QtConcurrent::run(this, &KWin::Script::loadScriptFromFile, fileName()));
0178 #else
0179     watcher->setFuture(QtConcurrent::run(&KWin::Script::loadScriptFromFile, this, fileName()));
0180 #endif
0181 }
0182 
0183 QByteArray KWin::Script::loadScriptFromFile(const QString &fileName)
0184 {
0185     QFile file(fileName);
0186     if (!file.open(QIODevice::ReadOnly)) {
0187         return QByteArray();
0188     }
0189     QByteArray result(file.readAll());
0190     return result;
0191 }
0192 
0193 void KWin::Script::slotScriptLoadedFromFile()
0194 {
0195     QFutureWatcher<QByteArray> *watcher = dynamic_cast<QFutureWatcher<QByteArray> *>(sender());
0196     if (!watcher) {
0197         // not invoked from a QFutureWatcher
0198         return;
0199     }
0200     if (watcher->result().isNull()) {
0201         // do not load empty script
0202         deleteLater();
0203         watcher->deleteLater();
0204 
0205         if (m_invocationContext.type() == QDBusMessage::MethodCallMessage) {
0206             auto reply = m_invocationContext.createErrorReply("org.kde.kwin.Scripting.FileError", QString("Could not open %1").arg(fileName()));
0207             QDBusConnection::sessionBus().send(reply);
0208             m_invocationContext = QDBusMessage();
0209         }
0210 
0211         return;
0212     }
0213 
0214     // Install console functions (e.g. console.assert(), console.log(), etc).
0215     m_engine->installExtensions(QJSEngine::ConsoleExtension);
0216 
0217     // Make the timer visible to QJSEngine.
0218     QJSValue timerMetaObject = m_engine->newQMetaObject(&ScriptTimer::staticMetaObject);
0219     m_engine->globalObject().setProperty("QTimer", timerMetaObject);
0220 
0221     // Expose enums.
0222     m_engine->globalObject().setProperty(QStringLiteral("KWin"), m_engine->newQMetaObject(&QtScriptWorkspaceWrapper::staticMetaObject));
0223 
0224     // Make the options object visible to QJSEngine.
0225     QJSValue optionsObject = m_engine->newQObject(options);
0226     QQmlEngine::setObjectOwnership(options, QQmlEngine::CppOwnership);
0227     m_engine->globalObject().setProperty(QStringLiteral("options"), optionsObject);
0228 
0229     // Make the workspace visible to QJSEngine.
0230     QJSValue workspaceObject = m_engine->newQObject(Scripting::self()->workspaceWrapper());
0231     QQmlEngine::setObjectOwnership(Scripting::self()->workspaceWrapper(), QQmlEngine::CppOwnership);
0232     m_engine->globalObject().setProperty(QStringLiteral("workspace"), workspaceObject);
0233 
0234     QJSValue self = m_engine->newQObject(this);
0235     QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
0236 
0237     static const QStringList globalProperties{
0238         QStringLiteral("readConfig"),
0239         QStringLiteral("callDBus"),
0240 
0241         QStringLiteral("registerShortcut"),
0242         QStringLiteral("registerScreenEdge"),
0243         QStringLiteral("unregisterScreenEdge"),
0244         QStringLiteral("registerTouchScreenEdge"),
0245         QStringLiteral("unregisterTouchScreenEdge"),
0246         QStringLiteral("registerUserActionsMenu"),
0247     };
0248 
0249     for (const QString &propertyName : globalProperties) {
0250         m_engine->globalObject().setProperty(propertyName, self.property(propertyName));
0251     }
0252 
0253     // Inject assertion functions. It would be better to create a module with all
0254     // this assert functions or just deprecate them in favor of console.assert().
0255     QJSValue result = m_engine->evaluate(QStringLiteral(R"(
0256         function assert(condition, message) {
0257             console.assert(condition, message || 'Assertion failed');
0258         }
0259         function assertTrue(condition, message) {
0260             console.assert(condition, message || 'Assertion failed');
0261         }
0262         function assertFalse(condition, message) {
0263             console.assert(!condition, message || 'Assertion failed');
0264         }
0265         function assertNull(value, message) {
0266             console.assert(value === null, message || 'Assertion failed');
0267         }
0268         function assertNotNull(value, message) {
0269             console.assert(value !== null, message || 'Assertion failed');
0270         }
0271         function assertEquals(expected, actual, message) {
0272             console.assert(expected === actual, message || 'Assertion failed');
0273         }
0274     )"));
0275     Q_ASSERT(!result.isError());
0276 
0277     result = m_engine->evaluate(QString::fromUtf8(watcher->result()), fileName());
0278     if (result.isError()) {
0279         qCWarning(KWIN_SCRIPTING, "%s:%d: error: %s", qPrintable(fileName()),
0280                   result.property(QStringLiteral("lineNumber")).toInt(),
0281                   qPrintable(result.property(QStringLiteral("message")).toString()));
0282         deleteLater();
0283     }
0284 
0285     if (m_invocationContext.type() == QDBusMessage::MethodCallMessage) {
0286         auto reply = m_invocationContext.createReply();
0287         QDBusConnection::sessionBus().send(reply);
0288         m_invocationContext = QDBusMessage();
0289     }
0290 
0291     watcher->deleteLater();
0292     setRunning(true);
0293     m_starting = false;
0294 }
0295 
0296 QVariant KWin::Script::readConfig(const QString &key, const QVariant &defaultValue)
0297 {
0298     return config().readEntry(key, defaultValue);
0299 }
0300 
0301 void KWin::Script::callDBus(const QString &service, const QString &path, const QString &interface,
0302                             const QString &method, const QJSValue &arg1, const QJSValue &arg2,
0303                             const QJSValue &arg3, const QJSValue &arg4, const QJSValue &arg5,
0304                             const QJSValue &arg6, const QJSValue &arg7, const QJSValue &arg8,
0305                             const QJSValue &arg9)
0306 {
0307     QJSValueList jsArguments;
0308     jsArguments.reserve(9);
0309 
0310     if (!arg1.isUndefined()) {
0311         jsArguments << arg1;
0312     }
0313     if (!arg2.isUndefined()) {
0314         jsArguments << arg2;
0315     }
0316     if (!arg3.isUndefined()) {
0317         jsArguments << arg3;
0318     }
0319     if (!arg4.isUndefined()) {
0320         jsArguments << arg4;
0321     }
0322     if (!arg5.isUndefined()) {
0323         jsArguments << arg5;
0324     }
0325     if (!arg6.isUndefined()) {
0326         jsArguments << arg6;
0327     }
0328     if (!arg7.isUndefined()) {
0329         jsArguments << arg7;
0330     }
0331     if (!arg8.isUndefined()) {
0332         jsArguments << arg8;
0333     }
0334     if (!arg9.isUndefined()) {
0335         jsArguments << arg9;
0336     }
0337 
0338     QJSValue callback;
0339     if (!jsArguments.isEmpty() && jsArguments.last().isCallable()) {
0340         callback = jsArguments.takeLast();
0341     }
0342 
0343     QVariantList dbusArguments;
0344     dbusArguments.reserve(jsArguments.count());
0345     for (const QJSValue &jsArgument : std::as_const(jsArguments)) {
0346         dbusArguments << jsArgument.toVariant();
0347     }
0348 
0349     QDBusMessage message = QDBusMessage::createMethodCall(service, path, interface, method);
0350     message.setArguments(dbusArguments);
0351 
0352     const QDBusPendingCall call = QDBusConnection::sessionBus().asyncCall(message);
0353     if (callback.isUndefined()) {
0354         return;
0355     }
0356 
0357     QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
0358     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, callback](QDBusPendingCallWatcher *self) {
0359         self->deleteLater();
0360 
0361         if (self->isError()) {
0362             qCWarning(KWIN_SCRIPTING) << "Received D-Bus message is error:" << self->error().message();
0363             return;
0364         }
0365 
0366         QJSValueList arguments;
0367         const QVariantList reply = self->reply().arguments();
0368         for (const QVariant &variant : reply) {
0369             arguments << m_engine->toScriptValue(dbusToVariant(variant));
0370         }
0371 
0372         QJSValue(callback).call(arguments);
0373     });
0374 }
0375 
0376 bool KWin::Script::registerShortcut(const QString &objectName, const QString &text, const QString &keySequence, const QJSValue &callback)
0377 {
0378     if (!callback.isCallable()) {
0379         m_engine->throwError(QStringLiteral("Shortcut handler must be callable"));
0380         return false;
0381     }
0382 
0383     QAction *action = new QAction(this);
0384     action->setObjectName(objectName);
0385     action->setText(text);
0386 
0387     const QKeySequence shortcut = keySequence;
0388     KGlobalAccel::self()->setShortcut(action, {shortcut});
0389 
0390     connect(action, &QAction::triggered, this, [this, action, callback]() {
0391         QJSValue(callback).call({m_engine->toScriptValue(action)});
0392     });
0393 
0394     return true;
0395 }
0396 
0397 bool KWin::Script::registerScreenEdge(int edge, const QJSValue &callback)
0398 {
0399     if (!callback.isCallable()) {
0400         m_engine->throwError(QStringLiteral("Screen edge handler must be callable"));
0401         return false;
0402     }
0403 
0404     QJSValueList &callbacks = m_screenEdgeCallbacks[edge];
0405     if (callbacks.isEmpty()) {
0406         workspace()->screenEdges()->reserve(static_cast<KWin::ElectricBorder>(edge), this, "slotBorderActivated");
0407     }
0408 
0409     callbacks << callback;
0410 
0411     return true;
0412 }
0413 
0414 bool KWin::Script::unregisterScreenEdge(int edge)
0415 {
0416     auto it = m_screenEdgeCallbacks.find(edge);
0417     if (it == m_screenEdgeCallbacks.end()) {
0418         return false;
0419     }
0420 
0421     workspace()->screenEdges()->unreserve(static_cast<KWin::ElectricBorder>(edge), this);
0422     m_screenEdgeCallbacks.erase(it);
0423 
0424     return true;
0425 }
0426 
0427 bool KWin::Script::registerTouchScreenEdge(int edge, const QJSValue &callback)
0428 {
0429     if (!callback.isCallable()) {
0430         m_engine->throwError(QStringLiteral("Touch screen edge handler must be callable"));
0431         return false;
0432     }
0433     if (m_touchScreenEdgeCallbacks.contains(edge)) {
0434         return false;
0435     }
0436 
0437     QAction *action = new QAction(this);
0438     workspace()->screenEdges()->reserveTouch(KWin::ElectricBorder(edge), action);
0439     m_touchScreenEdgeCallbacks.insert(edge, action);
0440 
0441     connect(action, &QAction::triggered, this, [callback]() {
0442         QJSValue(callback).call();
0443     });
0444 
0445     return true;
0446 }
0447 
0448 bool KWin::Script::unregisterTouchScreenEdge(int edge)
0449 {
0450     auto it = m_touchScreenEdgeCallbacks.find(edge);
0451     if (it == m_touchScreenEdgeCallbacks.end()) {
0452         return false;
0453     }
0454 
0455     delete it.value();
0456     m_touchScreenEdgeCallbacks.erase(it);
0457 
0458     return true;
0459 }
0460 
0461 void KWin::Script::registerUserActionsMenu(const QJSValue &callback)
0462 {
0463     if (!callback.isCallable()) {
0464         m_engine->throwError(QStringLiteral("User action handler must be callable"));
0465         return;
0466     }
0467     m_userActionsMenuCallbacks.append(callback);
0468 }
0469 
0470 QList<QAction *> KWin::Script::actionsForUserActionMenu(KWin::Window *client, QMenu *parent)
0471 {
0472     QList<QAction *> actions;
0473     actions.reserve(m_userActionsMenuCallbacks.count());
0474 
0475     for (QJSValue callback : std::as_const(m_userActionsMenuCallbacks)) {
0476         const QJSValue result = callback.call({m_engine->toScriptValue(client)});
0477         if (result.isError()) {
0478             continue;
0479         }
0480         if (!result.isObject()) {
0481             continue;
0482         }
0483         if (QAction *action = scriptValueToAction(result, parent)) {
0484             actions << action;
0485         }
0486     }
0487 
0488     return actions;
0489 }
0490 
0491 bool KWin::Script::slotBorderActivated(ElectricBorder border)
0492 {
0493     const QJSValueList callbacks = m_screenEdgeCallbacks.value(border);
0494     if (callbacks.isEmpty()) {
0495         return false;
0496     }
0497     std::for_each(callbacks.begin(), callbacks.end(), [](QJSValue callback) {
0498         callback.call();
0499     });
0500     return true;
0501 }
0502 
0503 QAction *KWin::Script::scriptValueToAction(const QJSValue &value, QMenu *parent)
0504 {
0505     const QString title = value.property(QStringLiteral("text")).toString();
0506     if (title.isEmpty()) {
0507         return nullptr;
0508     }
0509 
0510     // Either a menu or a menu item.
0511     const QJSValue itemsValue = value.property(QStringLiteral("items"));
0512     if (!itemsValue.isUndefined()) {
0513         return createMenu(title, itemsValue, parent);
0514     }
0515 
0516     return createAction(title, value, parent);
0517 }
0518 
0519 QAction *KWin::Script::createAction(const QString &title, const QJSValue &item, QMenu *parent)
0520 {
0521     const QJSValue callback = item.property(QStringLiteral("triggered"));
0522     if (!callback.isCallable()) {
0523         return nullptr;
0524     }
0525 
0526     const bool checkable = item.property(QStringLiteral("checkable")).toBool();
0527     const bool checked = item.property(QStringLiteral("checked")).toBool();
0528 
0529     QAction *action = new QAction(title, parent);
0530     action->setCheckable(checkable);
0531     action->setChecked(checked);
0532 
0533     connect(action, &QAction::triggered, this, [this, action, callback]() {
0534         QJSValue(callback).call({m_engine->toScriptValue(action)});
0535     });
0536 
0537     return action;
0538 }
0539 
0540 QAction *KWin::Script::createMenu(const QString &title, const QJSValue &items, QMenu *parent)
0541 {
0542     if (!items.isArray()) {
0543         return nullptr;
0544     }
0545 
0546     const int length = items.property(QStringLiteral("length")).toInt();
0547     if (!length) {
0548         return nullptr;
0549     }
0550 
0551     QMenu *menu = new QMenu(title, parent);
0552     for (int i = 0; i < length; ++i) {
0553         const QJSValue value = items.property(QString::number(i));
0554         if (!value.isObject()) {
0555             continue;
0556         }
0557         if (QAction *action = scriptValueToAction(value, menu)) {
0558             menu->addAction(action);
0559         }
0560     }
0561 
0562     return menu->menuAction();
0563 }
0564 
0565 KWin::DeclarativeScript::DeclarativeScript(int id, QString scriptName, QString pluginName, QObject *parent)
0566     : AbstractScript(id, scriptName, pluginName, parent)
0567     , m_context(new QQmlContext(Scripting::self()->declarativeScriptSharedContext(), this))
0568     , m_component(new QQmlComponent(Scripting::self()->qmlEngine(), this))
0569 {
0570     m_context->setContextProperty(QStringLiteral("KWin"), new JSEngineGlobalMethodsWrapper(this));
0571 }
0572 
0573 KWin::DeclarativeScript::~DeclarativeScript()
0574 {
0575 }
0576 
0577 void KWin::DeclarativeScript::run()
0578 {
0579     if (running()) {
0580         return;
0581     }
0582 
0583     m_component->loadUrl(QUrl::fromLocalFile(fileName()));
0584     if (m_component->isLoading()) {
0585         connect(m_component, &QQmlComponent::statusChanged, this, &DeclarativeScript::createComponent);
0586     } else {
0587         createComponent();
0588     }
0589 }
0590 
0591 void KWin::DeclarativeScript::createComponent()
0592 {
0593     if (m_component->isError()) {
0594         qCWarning(KWIN_SCRIPTING) << "Component failed to load: " << m_component->errors();
0595     } else {
0596         if (QObject *object = m_component->create(m_context)) {
0597             object->setParent(this);
0598         }
0599     }
0600     setRunning(true);
0601 }
0602 
0603 KWin::JSEngineGlobalMethodsWrapper::JSEngineGlobalMethodsWrapper(KWin::DeclarativeScript *parent)
0604     : QObject(parent)
0605     , m_script(parent)
0606 {
0607 }
0608 
0609 KWin::JSEngineGlobalMethodsWrapper::~JSEngineGlobalMethodsWrapper()
0610 {
0611 }
0612 
0613 QVariant KWin::JSEngineGlobalMethodsWrapper::readConfig(const QString &key, QVariant defaultValue)
0614 {
0615     return m_script->config().readEntry(key, defaultValue);
0616 }
0617 
0618 void KWin::JSEngineGlobalMethodsWrapper::registerWindow(QQuickWindow *window)
0619 {
0620     QPointer<QQuickWindow> guard = window;
0621     connect(
0622         window, &QWindow::visibilityChanged, this, [guard](QWindow::Visibility visibility) {
0623             if (guard && visibility == QWindow::Hidden) {
0624                 guard->destroy();
0625             }
0626         },
0627         Qt::QueuedConnection);
0628 }
0629 
0630 bool KWin::JSEngineGlobalMethodsWrapper::registerShortcut(const QString &name, const QString &text, const QKeySequence &keys, QJSValue function)
0631 {
0632     if (!function.isCallable()) {
0633         qCDebug(KWIN_SCRIPTING) << "Fourth and final argument must be a javascript function";
0634         return false;
0635     }
0636 
0637     QAction *a = new QAction(this);
0638     a->setObjectName(name);
0639     a->setText(text);
0640     const QKeySequence shortcut = QKeySequence(keys);
0641     KGlobalAccel::self()->setShortcut(a, QList<QKeySequence>{shortcut});
0642 
0643     connect(a, &QAction::triggered, this, [=]() mutable {
0644         QJSValueList arguments;
0645         arguments << Scripting::self()->qmlEngine()->toScriptValue(a);
0646         function.call(arguments);
0647     });
0648     return true;
0649 }
0650 
0651 KWin::Scripting *KWin::Scripting::s_self = nullptr;
0652 
0653 KWin::Scripting *KWin::Scripting::create(QObject *parent)
0654 {
0655     Q_ASSERT(!s_self);
0656     s_self = new Scripting(parent);
0657     return s_self;
0658 }
0659 
0660 KWin::Scripting::Scripting(QObject *parent)
0661     : QObject(parent)
0662     , m_scriptsLock(new QRecursiveMutex)
0663     , m_qmlEngine(new QQmlEngine(this))
0664     , m_declarativeScriptSharedContext(new QQmlContext(m_qmlEngine, this))
0665     , m_workspaceWrapper(new QtScriptWorkspaceWrapper(this))
0666 {
0667     init();
0668     QDBusConnection::sessionBus().registerObject(QStringLiteral("/Scripting"), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportScriptableInvokables);
0669     connect(Workspace::self(), &Workspace::configChanged, this, &Scripting::start);
0670     connect(Workspace::self(), &Workspace::workspaceInitialized, this, &Scripting::start);
0671 }
0672 
0673 void KWin::Scripting::init()
0674 {
0675     qmlRegisterType<WindowThumbnailItem>("org.kde.kwin", 2, 0, "ThumbnailItem");
0676     qmlRegisterType<DBusCall>("org.kde.kwin", 2, 0, "DBusCall");
0677     qmlRegisterType<ScreenEdgeItem>("org.kde.kwin", 2, 0, "ScreenEdgeItem");
0678     qmlRegisterAnonymousType<ScriptingModels::V2::ClientModel>("org.kde.kwin", 2);
0679     qmlRegisterType<ScriptingModels::V2::SimpleClientModel>("org.kde.kwin", 2, 0, "ClientModel");
0680     qmlRegisterType<ScriptingModels::V2::ClientModelByScreen>("org.kde.kwin", 2, 0, "ClientModelByScreen");
0681     qmlRegisterType<ScriptingModels::V2::ClientModelByScreenAndDesktop>("org.kde.kwin", 2, 0, "ClientModelByScreenAndDesktop");
0682     qmlRegisterType<ScriptingModels::V2::ClientModelByScreenAndActivity>("org.kde.kwin", 2, 1, "ClientModelByScreenAndActivity");
0683     qmlRegisterType<ScriptingModels::V2::ClientFilterModel>("org.kde.kwin", 2, 0, "ClientFilterModel");
0684 
0685     qmlRegisterType<DesktopBackgroundItem>("org.kde.kwin", 3, 0, "DesktopBackgroundItem");
0686     qmlRegisterType<WindowThumbnailItem>("org.kde.kwin", 3, 0, "WindowThumbnailItem");
0687     qmlRegisterType<DBusCall>("org.kde.kwin", 3, 0, "DBusCall");
0688     qmlRegisterType<ScreenEdgeItem>("org.kde.kwin", 3, 0, "ScreenEdgeItem");
0689     qmlRegisterType<ScriptingModels::V3::ClientModel>("org.kde.kwin", 3, 0, "ClientModel");
0690     qmlRegisterType<ScriptingModels::V3::ClientFilterModel>("org.kde.kwin", 3, 0, "ClientFilterModel");
0691     qmlRegisterType<ScriptingModels::V3::VirtualDesktopModel>("org.kde.kwin", 3, 0, "VirtualDesktopModel");
0692     qmlRegisterUncreatableType<KWin::QuickSceneView>("org.kde.kwin", 3, 0, "SceneView", QStringLiteral("Can't instantiate an object of type SceneView"));
0693 
0694     qmlRegisterSingletonType<DeclarativeScriptWorkspaceWrapper>("org.kde.kwin", 3, 0, "Workspace", [](QQmlEngine *qmlEngine, QJSEngine *jsEngine) {
0695         return new DeclarativeScriptWorkspaceWrapper();
0696     });
0697     qmlRegisterSingletonInstance("org.kde.kwin", 3, 0, "Options", options);
0698 
0699     qmlRegisterAnonymousType<KWin::Window>("org.kde.kwin", 2);
0700     qmlRegisterAnonymousType<KWin::VirtualDesktop>("org.kde.kwin", 2);
0701     qmlRegisterAnonymousType<KWin::X11Window>("org.kde.kwin", 2);
0702     qmlRegisterAnonymousType<QAbstractItemModel>("org.kde.kwin", 2);
0703     qmlRegisterAnonymousType<KWin::Window>("org.kde.kwin", 3);
0704     qmlRegisterAnonymousType<KWin::VirtualDesktop>("org.kde.kwin", 3);
0705     qmlRegisterAnonymousType<KWin::X11Window>("org.kde.kwin", 3);
0706     qmlRegisterAnonymousType<QAbstractItemModel>("org.kde.kwin", 3);
0707     qmlRegisterAnonymousType<KWin::TileManager>("org.kde.kwin", 3);
0708     // TODO: call the qml types as the C++ types?
0709     qmlRegisterUncreatableType<KWin::CustomTile>("org.kde.kwin", 3, 0, "CustomTile", QStringLiteral("Cannot create objects of type Tile"));
0710     qmlRegisterUncreatableType<KWin::Tile>("org.kde.kwin", 3, 0, "Tile", QStringLiteral("Cannot create objects of type AbstractTile"));
0711 
0712     // TODO Plasma 6: Drop context properties.
0713     m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("workspace"), m_workspaceWrapper);
0714     m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("options"), options);
0715     m_declarativeScriptSharedContext->setContextProperty(QStringLiteral("workspace"), new DeclarativeScriptWorkspaceWrapper(this));
0716 
0717     // QQmlListProperty interfaces only work via properties, rebind them as functions here
0718     QQmlExpression expr(m_declarativeScriptSharedContext, nullptr, "workspace.clientList = function() { return workspace.clients }");
0719     expr.evaluate();
0720 }
0721 
0722 void KWin::Scripting::start()
0723 {
0724 #if 0
0725     // TODO make this threaded again once KConfigGroup is sufficiently thread safe, bug #305361 and friends
0726     // perform querying for the services in a thread
0727     QFutureWatcher<LoadScriptList> *watcher = new QFutureWatcher<LoadScriptList>(this);
0728     connect(watcher, &QFutureWatcher<LoadScriptList>::finished, this, &Scripting::slotScriptsQueried);
0729     watcher->setFuture(QtConcurrent::run(this, &KWin::Scripting::queryScriptsToLoad, pluginStates, offers));
0730 #else
0731     LoadScriptList scriptsToLoad = queryScriptsToLoad();
0732     for (LoadScriptList::const_iterator it = scriptsToLoad.constBegin();
0733          it != scriptsToLoad.constEnd();
0734          ++it) {
0735         if (it->first) {
0736             loadScript(it->second.first, it->second.second);
0737         } else {
0738             loadDeclarativeScript(it->second.first, it->second.second);
0739         }
0740     }
0741 
0742     runScripts();
0743 #endif
0744 }
0745 
0746 LoadScriptList KWin::Scripting::queryScriptsToLoad()
0747 {
0748     KSharedConfig::Ptr _config = kwinApp()->config();
0749     static bool s_started = false;
0750     if (s_started) {
0751         _config->reparseConfiguration();
0752     } else {
0753         s_started = true;
0754     }
0755     QMap<QString, QString> pluginStates = KConfigGroup(_config, "Plugins").entryMap();
0756     const QString scriptFolder = QStringLiteral("kwin/scripts/");
0757     const auto offers = KPackage::PackageLoader::self()->listPackages(QStringLiteral("KWin/Script"), scriptFolder);
0758 
0759     LoadScriptList scriptsToLoad;
0760 
0761     for (const KPluginMetaData &service : offers) {
0762         const QString value = pluginStates.value(service.pluginId() + QLatin1String("Enabled"), QString());
0763         const bool enabled = value.isNull() ? service.isEnabledByDefault() : QVariant(value).toBool();
0764         const bool javaScript = service.value(QStringLiteral("X-Plasma-API")) == QLatin1String("javascript");
0765         const bool declarativeScript = service.value(QStringLiteral("X-Plasma-API")) == QLatin1String("declarativescript");
0766         if (!javaScript && !declarativeScript) {
0767             continue;
0768         }
0769 
0770         if (!enabled) {
0771             if (isScriptLoaded(service.pluginId())) {
0772                 // unload the script
0773                 unloadScript(service.pluginId());
0774             }
0775             continue;
0776         }
0777         const QString pluginName = service.pluginId();
0778         const QString scriptName = service.value(QStringLiteral("X-Plasma-MainScript"));
0779         const QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, scriptFolder + pluginName + QLatin1String("/contents/") + scriptName);
0780         if (file.isNull()) {
0781             qCDebug(KWIN_SCRIPTING) << "Could not find script file for " << pluginName;
0782             continue;
0783         }
0784         scriptsToLoad << qMakePair(javaScript, qMakePair(file, pluginName));
0785     }
0786     return scriptsToLoad;
0787 }
0788 
0789 void KWin::Scripting::slotScriptsQueried()
0790 {
0791     QFutureWatcher<LoadScriptList> *watcher = dynamic_cast<QFutureWatcher<LoadScriptList> *>(sender());
0792     if (!watcher) {
0793         // slot invoked not from a FutureWatcher
0794         return;
0795     }
0796 
0797     LoadScriptList scriptsToLoad = watcher->result();
0798     for (LoadScriptList::const_iterator it = scriptsToLoad.constBegin();
0799          it != scriptsToLoad.constEnd();
0800          ++it) {
0801         if (it->first) {
0802             loadScript(it->second.first, it->second.second);
0803         } else {
0804             loadDeclarativeScript(it->second.first, it->second.second);
0805         }
0806     }
0807 
0808     runScripts();
0809     watcher->deleteLater();
0810 }
0811 
0812 bool KWin::Scripting::isScriptLoaded(const QString &pluginName) const
0813 {
0814     return findScript(pluginName) != nullptr;
0815 }
0816 
0817 KWin::AbstractScript *KWin::Scripting::findScript(const QString &pluginName) const
0818 {
0819     QMutexLocker locker(m_scriptsLock.get());
0820     for (AbstractScript *script : std::as_const(scripts)) {
0821         if (script->pluginName() == pluginName) {
0822             return script;
0823         }
0824     }
0825     return nullptr;
0826 }
0827 
0828 bool KWin::Scripting::unloadScript(const QString &pluginName)
0829 {
0830     QMutexLocker locker(m_scriptsLock.get());
0831     for (AbstractScript *script : std::as_const(scripts)) {
0832         if (script->pluginName() == pluginName) {
0833             script->deleteLater();
0834             return true;
0835         }
0836     }
0837     return false;
0838 }
0839 
0840 void KWin::Scripting::runScripts()
0841 {
0842     QMutexLocker locker(m_scriptsLock.get());
0843     for (int i = 0; i < scripts.size(); i++) {
0844         scripts.at(i)->run();
0845     }
0846 }
0847 
0848 void KWin::Scripting::scriptDestroyed(QObject *object)
0849 {
0850     QMutexLocker locker(m_scriptsLock.get());
0851     scripts.removeAll(static_cast<KWin::Script *>(object));
0852 }
0853 
0854 int KWin::Scripting::loadScript(const QString &filePath, const QString &pluginName)
0855 {
0856     QMutexLocker locker(m_scriptsLock.get());
0857     if (isScriptLoaded(pluginName)) {
0858         return -1;
0859     }
0860     const int id = scripts.size();
0861     KWin::Script *script = new KWin::Script(id, filePath, pluginName, this);
0862     connect(script, &QObject::destroyed, this, &Scripting::scriptDestroyed);
0863     scripts.append(script);
0864     return id;
0865 }
0866 
0867 int KWin::Scripting::loadDeclarativeScript(const QString &filePath, const QString &pluginName)
0868 {
0869     QMutexLocker locker(m_scriptsLock.get());
0870     if (isScriptLoaded(pluginName)) {
0871         return -1;
0872     }
0873     const int id = scripts.size();
0874     KWin::DeclarativeScript *script = new KWin::DeclarativeScript(id, filePath, pluginName, this);
0875     connect(script, &QObject::destroyed, this, &Scripting::scriptDestroyed);
0876     scripts.append(script);
0877     return id;
0878 }
0879 
0880 KWin::Scripting::~Scripting()
0881 {
0882     QDBusConnection::sessionBus().unregisterObject(QStringLiteral("/Scripting"));
0883     s_self = nullptr;
0884 }
0885 
0886 QList<QAction *> KWin::Scripting::actionsForUserActionMenu(KWin::Window *c, QMenu *parent)
0887 {
0888     QList<QAction *> actions;
0889     for (AbstractScript *s : std::as_const(scripts)) {
0890         // TODO: Allow declarative scripts to add their own user actions.
0891         if (Script *script = qobject_cast<Script *>(s)) {
0892             actions << script->actionsForUserActionMenu(c, parent);
0893         }
0894     }
0895     return actions;
0896 }