File indexing completed on 2025-03-23 11:14:13
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 }