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