1/********************************************************************
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5Copyright (C) 2010 Rohan Prabhu <rohan@rohanprabhu.com>
6Copyright (C) 2011 Martin Gräßlin <mgraesslin@kde.org>
7
8This program is free software; you can redistribute it and/or modify
9it under the terms of the GNU General Public License as published by
10the Free Software Foundation; either version 2 of the License, or
11(at your option) any later version.
12
13This program is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16GNU General Public License for more details.
17
18You should have received a copy of the GNU General Public License
19along with this program. If not, see <http://www.gnu.org/licenses/>.
20*********************************************************************/
21
22#include "scripting.h"
23// own
24#include "meta.h"
25#include "scriptingutils.h"
26#include "workspace_wrapper.h"
27#include "scripting_model.h"
28#include "../client.h"
29#include "../thumbnailitem.h"
30#include "../options.h"
31#include "../workspace.h"
32// KDE
33#include <kstandarddirs.h>
34#include <KDE/KConfigGroup>
35#include <KDE/KDebug>
36#include <KDE/KGlobal>
37#include <KDE/KPluginInfo>
38#include <KDE/KServiceTypeTrader>
39#include <kdeclarative.h>
40// Qt
41#include <QtDBus/QDBusConnection>
42#include <QtDBus/QDBusMessage>
43#include <QtDBus/QDBusPendingCallWatcher>
44#include <QFutureWatcher>
45#include <QSettings>
46#include <QtConcurrentRun>
47#include <QtDeclarative/QDeclarativeContext>
48#include <QtDeclarative/QDeclarativeEngine>
49#include <QtDeclarative/QDeclarativeView>
50#include <QtDeclarative/qdeclarative.h>
51#include <QMenu>
52#include <QtScript/QScriptEngine>
53#include <QtScript/QScriptValue>
54
55QScriptValue kwinScriptPrint(QScriptContext *context, QScriptEngine *engine)
56{
57 KWin::AbstractScript *script = qobject_cast<KWin::Script*>(context->callee().data().toQObject());
58 if (!script) {
59 return engine->undefinedValue();
60 }
61 QString result;
62 QTextStream stream(&result);
63 for (int i = 0; i < context->argumentCount(); ++i) {
64 if (i > 0) {
65 stream << " ";
66 }
67 QScriptValue argument = context->argument(i);
68 if (KWin::Client *client = qscriptvalue_cast<KWin::Client*>(argument)) {
69 client->print<QTextStream>(stream);
70 } else {
71 stream << argument.toString();
72 }
73 }
74 script->printMessage(result);
75
76 return engine->undefinedValue();
77}
78
79QScriptValue kwinScriptReadConfig(QScriptContext *context, QScriptEngine *engine)
80{
81 KWin::AbstractScript *script = qobject_cast<KWin::AbstractScript*>(context->callee().data().toQObject());
82 if (!script) {
83 return engine->undefinedValue();
84 }
85 if (context->argumentCount() < 1 || context->argumentCount() > 2) {
86 kDebug(1212) << "Incorrect number of arguments";
87 return engine->undefinedValue();
88 }
89 const QString key = context->argument(0).toString();
90 QVariant defaultValue;
91 if (context->argumentCount() == 2) {
92 defaultValue = context->argument(1).toVariant();
93 }
94 return engine->newVariant(script->config().readEntry(key, defaultValue));
95}
96
97QScriptValue kwinScriptGlobalShortcut(QScriptContext *context, QScriptEngine *engine)
98{
99 return KWin::globalShortcut<KWin::AbstractScript*>(context, engine);
100}
101
102QScriptValue kwinAssertTrue(QScriptContext *context, QScriptEngine *engine)
103{
104 return KWin::scriptingAssert<bool>(context, engine, 1, 2, true);
105}
106
107QScriptValue kwinAssertFalse(QScriptContext *context, QScriptEngine *engine)
108{
109 return KWin::scriptingAssert<bool>(context, engine, 1, 2, false);
110}
111
112QScriptValue kwinAssertEquals(QScriptContext *context, QScriptEngine *engine)
113{
114 return KWin::scriptingAssert<QVariant>(context, engine, 2, 3);
115}
116
117QScriptValue kwinAssertNull(QScriptContext *context, QScriptEngine *engine)
118{
119 if (!KWin::validateParameters(context, 1, 2)) {
120 return engine->undefinedValue();
121 }
122 if (!context->argument(0).isNull()) {
123 if (context->argumentCount() == 2) {
124 context->throwError(QScriptContext::UnknownError, context->argument(1).toString());
125 } else {
126 context->throwError(QScriptContext::UnknownError,
127 i18nc("Assertion failed in KWin script with given value",
128 "Assertion failed: %1 is not null", context->argument(0).toString()));
129 }
130 return engine->undefinedValue();
131 }
132 return true;
133}
134
135QScriptValue kwinAssertNotNull(QScriptContext *context, QScriptEngine *engine)
136{
137 if (!KWin::validateParameters(context, 1, 2)) {
138 return engine->undefinedValue();
139 }
140 if (context->argument(0).isNull()) {
141 if (context->argumentCount() == 2) {
142 context->throwError(QScriptContext::UnknownError, context->argument(1).toString());
143 } else {
144 context->throwError(QScriptContext::UnknownError,
145 i18nc("Assertion failed in KWin script",
146 "Assertion failed: argument is null"));
147 }
148 return engine->undefinedValue();
149 }
150 return true;
151}
152
153QScriptValue kwinRegisterScreenEdge(QScriptContext *context, QScriptEngine *engine)
154{
155 return KWin::registerScreenEdge<KWin::AbstractScript*>(context, engine);
156}
157
158QScriptValue kwinRegisterUserActionsMenu(QScriptContext *context, QScriptEngine *engine)
159{
160 return KWin::registerUserActionsMenu<KWin::AbstractScript*>(context, engine);
161}
162
163QScriptValue kwinCallDBus(QScriptContext *context, QScriptEngine *engine)
164{
165 KWin::AbstractScript *script = qobject_cast<KWin::AbstractScript*>(context->callee().data().toQObject());
166 if (!script) {
167 context->throwError(QScriptContext::UnknownError, "Internal Error: script not registered");
168 return engine->undefinedValue();
169 }
170 if (context->argumentCount() < 4) {
171 context->throwError(QScriptContext::SyntaxError,
172 i18nc("Error in KWin Script",
173 "Invalid number of arguments. At least service, path, interface and method need to be provided"));
174 return engine->undefinedValue();
175 }
176 if (!KWin::validateArgumentType<QString, QString, QString, QString>(context)) {
177 context->throwError(QScriptContext::SyntaxError,
178 i18nc("Error in KWin Script",
179 "Invalid type. Service, path, interface and method need to be string values"));
180 return engine->undefinedValue();
181 }
182 const QString service = context->argument(0).toString();
183 const QString path = context->argument(1).toString();
184 const QString interface = context->argument(2).toString();
185 const QString method = context->argument(3).toString();
186 int argumentsCount = context->argumentCount();
187 if (context->argument(argumentsCount-1).isFunction()) {
188 --argumentsCount;
189 }
190 QDBusMessage msg = QDBusMessage::createMethodCall(service, path, interface, method);
191 QVariantList arguments;
192 for (int i=4; i<argumentsCount; ++i) {
193 if (context->argument(i).isArray()) {
194 QStringList stringArray = engine->fromScriptValue<QStringList>(context->argument(i));
195 arguments << qVariantFromValue(stringArray);
196 } else {
197 arguments << context->argument(i).toVariant();
198 }
199 }
200 if (!arguments.isEmpty()) {
201 msg.setArguments(arguments);
202 }
203 if (argumentsCount == context->argumentCount()) {
204 // no callback, just fire and forget
205 QDBusConnection::sessionBus().asyncCall(msg);
206 } else {
207 // with a callback
208 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(QDBusConnection::sessionBus().asyncCall(msg), script);
209 watcher->setProperty("callback", script->registerCallback(context->argument(context->argumentCount()-1)));
210 QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), script, SLOT(slotPendingDBusCall(QDBusPendingCallWatcher*)));
211 }
212 return engine->undefinedValue();
213}
214
215KWin::AbstractScript::AbstractScript(int id, QString scriptName, QString pluginName, QObject *parent)
216 : QObject(parent)
217 , m_scriptId(id)
218 , m_pluginName(pluginName)
219 , m_running(false)
220 , m_workspace(new WorkspaceWrapper(this))
221{
222 m_scriptFile.setFileName(scriptName);
223 if (m_pluginName.isNull()) {
224 m_pluginName = scriptName;
225 }
226}
227
228KWin::AbstractScript::~AbstractScript()
229{
230}
231
232KConfigGroup KWin::AbstractScript::config() const
233{
234 return KGlobal::config()->group("Script-" + m_pluginName);
235}
236
237void KWin::AbstractScript::stop()
238{
239 deleteLater();
240}
241
242void KWin::AbstractScript::printMessage(const QString &message)
243{
244 kDebug(1212) << scriptFile().fileName() << ":" << message;
245 emit print(message);
246}
247
248void KWin::AbstractScript::registerShortcut(QAction *a, QScriptValue callback)
249{
250 m_shortcutCallbacks.insert(a, callback);
251 connect(a, SIGNAL(triggered(bool)), SLOT(globalShortcutTriggered()));
252}
253
254void KWin::AbstractScript::globalShortcutTriggered()
255{
256 callGlobalShortcutCallback<KWin::AbstractScript*>(this, sender());
257}
258
259bool KWin::AbstractScript::borderActivated(KWin::ElectricBorder edge)
260{
261 screenEdgeActivated(this, edge);
262 return true;
263}
264
265void KWin::AbstractScript::installScriptFunctions(QScriptEngine* engine)
266{
267 // add our print
268 QScriptValue printFunc = engine->newFunction(kwinScriptPrint);
269 printFunc.setData(engine->newQObject(this));
270 engine->globalObject().setProperty("print", printFunc);
271 // add read config
272 QScriptValue configFunc = engine->newFunction(kwinScriptReadConfig);
273 configFunc.setData(engine->newQObject(this));
274 engine->globalObject().setProperty("readConfig", configFunc);
275 QScriptValue dbusCallFunc = engine->newFunction(kwinCallDBus);
276 dbusCallFunc.setData(engine->newQObject(this));
277 engine->globalObject().setProperty("callDBus", dbusCallFunc);
278 // add global Shortcut
279 registerGlobalShortcutFunction(this, engine, kwinScriptGlobalShortcut);
280 // add screen edge
281 registerScreenEdgeFunction(this, engine, kwinRegisterScreenEdge);
282 // add user actions menu register function
283 regesterUserActionsMenuFunction(this, engine, kwinRegisterUserActionsMenu);
284 // add assertions
285 QScriptValue assertTrueFunc = engine->newFunction(kwinAssertTrue);
286 engine->globalObject().setProperty("assertTrue", assertTrueFunc);
287 engine->globalObject().setProperty("assert", assertTrueFunc);
288 QScriptValue assertFalseFunc = engine->newFunction(kwinAssertFalse);
289 engine->globalObject().setProperty("assertFalse", assertFalseFunc);
290 QScriptValue assertEqualsFunc = engine->newFunction(kwinAssertEquals);
291 engine->globalObject().setProperty("assertEquals", assertEqualsFunc);
292 QScriptValue assertNullFunc = engine->newFunction(kwinAssertNull);
293 engine->globalObject().setProperty("assertNull", assertNullFunc);
294 engine->globalObject().setProperty("assertEquals", assertEqualsFunc);
295 QScriptValue assertNotNullFunc = engine->newFunction(kwinAssertNotNull);
296 engine->globalObject().setProperty("assertNotNull", assertNotNullFunc);
297 // global properties
298 engine->globalObject().setProperty("KWin", engine->newQMetaObject(&WorkspaceWrapper::staticMetaObject));
299 QScriptValue workspace = engine->newQObject(AbstractScript::workspace(), QScriptEngine::QtOwnership,
300 QScriptEngine::ExcludeSuperClassContents | QScriptEngine::ExcludeDeleteLater);
301 engine->globalObject().setProperty("workspace", workspace, QScriptValue::Undeletable);
302 // install meta functions
303 KWin::MetaScripting::registration(engine);
304}
305
306int KWin::AbstractScript::registerCallback(QScriptValue value)
307{
308 int id = m_callbacks.size();
309 m_callbacks.insert(id, value);
310 return id;
311}
312
313void KWin::AbstractScript::slotPendingDBusCall(QDBusPendingCallWatcher* watcher)
314{
315 if (watcher->isError()) {
316 kDebug(1212) << "Received D-Bus message is error";
317 watcher->deleteLater();
318 return;
319 }
320 const int id = watcher->property("callback").toInt();
321 QDBusMessage reply = watcher->reply();
322 QScriptValue callback (m_callbacks.value(id));
323 QScriptValueList arguments;
324 foreach (const QVariant &argument, reply.arguments()) {
325 arguments << callback.engine()->newVariant(argument);
326 }
327 callback.call(QScriptValue(), arguments);
328 m_callbacks.remove(id);
329 watcher->deleteLater();
330}
331
332void KWin::AbstractScript::registerUseractionsMenuCallback(QScriptValue callback)
333{
334 m_userActionsMenuCallbacks.append(callback);
335}
336
337QList< QAction * > KWin::AbstractScript::actionsForUserActionMenu(KWin::Client *c, QMenu *parent)
338{
339 QList<QAction*> returnActions;
340 for (QList<QScriptValue>::const_iterator it = m_userActionsMenuCallbacks.constBegin(); it != m_userActionsMenuCallbacks.constEnd(); ++it) {
341 QScriptValue callback(*it);
342 QScriptValueList arguments;
343 arguments << callback.engine()->newQObject(c);
344 QScriptValue actions = callback.call(QScriptValue(), arguments);
345 if (!actions.isValid() || actions.isUndefined() || actions.isNull()) {
346 // script does not want to handle this Client
347 continue;
348 }
349 if (actions.isObject()) {
350 QAction *a = scriptValueToAction(actions, parent);
351 if (a) {
352 returnActions << a;
353 }
354 }
355 }
356
357 return returnActions;
358}
359
360QAction *KWin::AbstractScript::scriptValueToAction(QScriptValue &value, QMenu *parent)
361{
362 QScriptValue titleValue = value.property("text");
363 QScriptValue checkableValue = value.property("checkable");
364 QScriptValue checkedValue = value.property("checked");
365 QScriptValue itemsValue = value.property("items");
366 QScriptValue triggeredValue = value.property("triggered");
367
368 if (!titleValue.isValid()) {
369 // title not specified - does not make any sense to include
370 return NULL;
371 }
372 const QString title = titleValue.toString();
373 const bool checkable = checkableValue.isValid() && checkableValue.toBool();
374 const bool checked = checkable && checkedValue.isValid() && checkedValue.toBool();
375 // either a menu or a menu item
376 if (itemsValue.isValid()) {
377 if (!itemsValue.isArray()) {
378 // not an array, so cannot be a menu
379 return NULL;
380 }
381 QScriptValue lengthValue = itemsValue.property("length");
382 if (!lengthValue.isValid() || !lengthValue.isNumber() || lengthValue.toInteger() == 0) {
383 // length property missing
384 return NULL;
385 }
386 return createMenu(title, itemsValue, parent);
387 } else if (triggeredValue.isValid()) {
388 // normal item
389 return createAction(title, checkable, checked, triggeredValue, parent);
390 }
391 return NULL;
392}
393
394QAction *KWin::AbstractScript::createAction(const QString &title, bool checkable, bool checked, QScriptValue &callback, QMenu *parent)
395{
396 QAction *action = new QAction(title, parent);
397 action->setCheckable(checkable);
398 action->setChecked(checked);
399 // TODO: rename m_shortcutCallbacks
400 m_shortcutCallbacks.insert(action, callback);
401 connect(action, SIGNAL(triggered(bool)), SLOT(globalShortcutTriggered()));
402 connect(action, SIGNAL(destroyed(QObject*)), SLOT(actionDestroyed(QObject*)));
403 return action;
404}
405
406QAction *KWin::AbstractScript::createMenu(const QString &title, QScriptValue &items, QMenu *parent)
407{
408 QMenu *menu = new QMenu(title, parent);
409 const int length = static_cast<int>(items.property("length").toInteger());
410 for (int i=0; i<length; ++i) {
411 QScriptValue value = items.property(QString::number(i));
412 if (!value.isValid()) {
413 continue;
414 }
415 if (value.isObject()) {
416 QAction *a = scriptValueToAction(value, menu);
417 if (a) {
418 menu->addAction(a);
419 }
420 }
421 }
422 return menu->menuAction();
423}
424
425void KWin::AbstractScript::actionDestroyed(QObject *object)
426{
427 // TODO: Qt 5 - change to lambda function
428 m_shortcutCallbacks.remove(static_cast<QAction*>(object));
429}
430
431KWin::Script::Script(int id, QString scriptName, QString pluginName, QObject* parent)
432 : AbstractScript(id, scriptName, pluginName, parent)
433 , m_engine(new QScriptEngine(this))
434 , m_starting(false)
435 , m_agent(new ScriptUnloaderAgent(this))
436{
437 QDBusConnection::sessionBus().registerObject('/' + QString::number(scriptId()), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportScriptableInvokables);
438}
439
440KWin::Script::~Script()
441{
442 QDBusConnection::sessionBus().unregisterObject('/' + QString::number(scriptId()));
443}
444
445void KWin::Script::run()
446{
447 if (running() || m_starting) {
448 return;
449 }
450 m_starting = true;
451 QFutureWatcher<QByteArray> *watcher = new QFutureWatcher<QByteArray>(this);
452 connect(watcher, SIGNAL(finished()), SLOT(slotScriptLoadedFromFile()));
453 watcher->setFuture(QtConcurrent::run(this, &KWin::Script::loadScriptFromFile));
454}
455
456QByteArray KWin::Script::loadScriptFromFile()
457{
458 if (!scriptFile().open(QIODevice::ReadOnly)) {
459 return QByteArray();
460 }
461 QByteArray result(scriptFile().readAll());
462 scriptFile().close();
463 return result;
464}
465
466void KWin::Script::slotScriptLoadedFromFile()
467{
468 QFutureWatcher<QByteArray> *watcher = dynamic_cast< QFutureWatcher< QByteArray>* >(sender());
469 if (!watcher) {
470 // not invoked from a QFutureWatcher
471 return;
472 }
473 if (watcher->result().isNull()) {
474 // do not load empty script
475 deleteLater();
476 watcher->deleteLater();
477 return;
478 }
479 QScriptValue optionsValue = m_engine->newQObject(options, QScriptEngine::QtOwnership,
480 QScriptEngine::ExcludeSuperClassContents | QScriptEngine::ExcludeDeleteLater);
481 m_engine->globalObject().setProperty("options", optionsValue, QScriptValue::Undeletable);
482 m_engine->globalObject().setProperty("QTimer", constructTimerClass(m_engine));
483 QObject::connect(m_engine, SIGNAL(signalHandlerException(QScriptValue)), this, SLOT(sigException(QScriptValue)));
484 KWin::MetaScripting::supplyConfig(m_engine);
485 installScriptFunctions(m_engine);
486
487 QScriptValue ret = m_engine->evaluate(watcher->result());
488
489 if (ret.isError()) {
490 sigException(ret);
491 deleteLater();
492 }
493
494 watcher->deleteLater();
495 setRunning(true);
496 m_starting = false;
497}
498
499void KWin::Script::sigException(const QScriptValue& exception)
500{
501 QScriptValue ret = exception;
502 if (ret.isError()) {
503 kDebug(1212) << "defaultscript encountered an error at [Line " << m_engine->uncaughtExceptionLineNumber() << "]";
504 kDebug(1212) << "Message: " << ret.toString();
505 kDebug(1212) << "-----------------";
506
507 QScriptValueIterator iter(ret);
508 while (iter.hasNext()) {
509 iter.next();
510 qDebug() << " " << iter.name() << ": " << iter.value().toString();
511 }
512 }
513 emit printError(exception.toString());
514 stop();
515}
516
517KWin::ScriptUnloaderAgent::ScriptUnloaderAgent(KWin::Script *script)
518 : QScriptEngineAgent(script->engine())
519 , m_script(script)
520{
521 script->engine()->setAgent(this);
522}
523
524void KWin::ScriptUnloaderAgent::scriptUnload(qint64 id)
525{
526 Q_UNUSED(id)
527 m_script->stop();
528}
529
530KWin::DeclarativeScript::DeclarativeScript(int id, QString scriptName, QString pluginName, QObject* parent)
531 : AbstractScript(id, scriptName, pluginName, parent)
532 , m_engine(new QDeclarativeEngine(this))
533 , m_component(new QDeclarativeComponent(m_engine, this))
534 , m_scene(new QGraphicsScene(this))
535{
536}
537
538KWin::DeclarativeScript::~DeclarativeScript()
539{
540}
541
542void KWin::DeclarativeScript::run()
543{
544 if (running()) {
545 return;
546 }
547 // add read config
548 KDeclarative kdeclarative;
549 kdeclarative.setDeclarativeEngine(m_engine);
550 kdeclarative.initialize();
551 kdeclarative.setupBindings();
552 installScriptFunctions(kdeclarative.scriptEngine());
553 qmlRegisterType<DesktopThumbnailItem>("org.kde.kwin", 0, 1, "DesktopThumbnailItem");
554 qmlRegisterType<WindowThumbnailItem>("org.kde.kwin", 0, 1, "ThumbnailItem");
555 qmlRegisterType<KWin::ScriptingClientModel::ClientModel>();
556 qmlRegisterType<KWin::ScriptingClientModel::SimpleClientModel>("org.kde.kwin", 0, 1, "ClientModel");
557 qmlRegisterType<KWin::ScriptingClientModel::ClientModelByScreen>("org.kde.kwin", 0, 1, "ClientModelByScreen");
558 qmlRegisterType<KWin::ScriptingClientModel::ClientModelByScreenAndDesktop>("org.kde.kwin", 0, 1, "ClientModelByScreenAndDesktop");
559 qmlRegisterType<KWin::ScriptingClientModel::ClientFilterModel>("org.kde.kwin", 0, 1, "ClientFilterModel");
560 qmlRegisterType<KWin::Client>();
561
562 m_engine->rootContext()->setContextProperty("options", options);
563
564 m_component->loadUrl(QUrl::fromLocalFile(scriptFile().fileName()));
565 if (m_component->isLoading()) {
566 connect(m_component, SIGNAL(statusChanged(QDeclarativeComponent::Status)), SLOT(createComponent()));
567 } else {
568 createComponent();
569 }
570}
571
572void KWin::DeclarativeScript::createComponent()
573{
574 if (m_component->isError()) {
575 kDebug(1212) << "Component failed to load: " << m_component->errors();
576 } else {
577 m_scene->addItem(qobject_cast<QDeclarativeItem*>(m_component->create()));
578 }
579 setRunning(true);
580}
581
582KWin::Scripting *KWin::Scripting::s_self = NULL;
583
584KWin::Scripting *KWin::Scripting::create(QObject *parent)
585{
586 Q_ASSERT(!s_self);
587 s_self = new Scripting(parent);
588 return s_self;
589}
590
591KWin::Scripting::Scripting(QObject *parent)
592 : QObject(parent)
593 , m_scriptsLock(new QMutex(QMutex::Recursive))
594{
595 QDBusConnection::sessionBus().registerObject("/Scripting", this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportScriptableInvokables);
596 QDBusConnection::sessionBus().registerService("org.kde.kwin.Scripting");
597 connect(Workspace::self(), SIGNAL(configChanged()), SLOT(start()));
598 connect(Workspace::self(), SIGNAL(workspaceInitialized()), SLOT(start()));
599}
600
601void KWin::Scripting::start()
602{
603#if 0
604 // TODO make this threaded again once KConfigGroup is sufficiently thread safe, bug #305361 and friends
605 // perform querying for the services in a thread
606 QFutureWatcher<LoadScriptList> *watcher = new QFutureWatcher<LoadScriptList>(this);
607 connect(watcher, SIGNAL(finished()), this, SLOT(slotScriptsQueried()));
608 watcher->setFuture(QtConcurrent::run(this, &KWin::Scripting::queryScriptsToLoad, pluginStates, offers));
609#else
610 LoadScriptList scriptsToLoad = queryScriptsToLoad();
611 for (LoadScriptList::const_iterator it = scriptsToLoad.constBegin();
612 it != scriptsToLoad.constEnd();
613 ++it) {
614 if (it->first) {
615 loadScript(it->second.first, it->second.second);
616 } else {
617 loadDeclarativeScript(it->second.first, it->second.second);
618 }
619 }
620
621 runScripts();
622#endif
623}
624
625LoadScriptList KWin::Scripting::queryScriptsToLoad()
626{
627 KSharedConfig::Ptr _config = KGlobal::config();
628 static bool s_started = false;
629 if (s_started) {
630 _config->reparseConfiguration();
631 } else {
632 s_started = true;
633 }
634 QMap<QString,QString> pluginStates = KConfigGroup(_config, "Plugins").entryMap();
635 KService::List offers = KServiceTypeTrader::self()->query("KWin/Script");
636
637 LoadScriptList scriptsToLoad;
638
639 foreach (const KService::Ptr & service, offers) {
640 KPluginInfo plugininfo(service);
641 const QString value = pluginStates.value(plugininfo.pluginName() + QString::fromLatin1("Enabled"), QString());
642 plugininfo.setPluginEnabled(value.isNull() ? plugininfo.isPluginEnabledByDefault() : QVariant(value).toBool());
643 const bool javaScript = service->property("X-Plasma-API").toString() == "javascript";
644 const bool declarativeScript = service->property("X-Plasma-API").toString() == "declarativescript";
645 if (!javaScript && !declarativeScript) {
646 continue;
647 }
648
649 if (!plugininfo.isPluginEnabled()) {
650 if (isScriptLoaded(plugininfo.pluginName())) {
651 // unload the script
652 unloadScript(plugininfo.pluginName());
653 }
654 continue;
655 }
656 const QString pluginName = service->property("X-KDE-PluginInfo-Name").toString();
657 const QString scriptName = service->property("X-Plasma-MainScript").toString();
658 const QString file = KStandardDirs::locate("data", QLatin1String(KWIN_NAME) + "/scripts/" + pluginName + "/contents/" + scriptName);
659 if (file.isNull()) {
660 kDebug(1212) << "Could not find script file for " << pluginName;
661 continue;
662 }
663 scriptsToLoad << qMakePair(javaScript, qMakePair(file, pluginName));
664 }
665 return scriptsToLoad;
666}
667
668void KWin::Scripting::slotScriptsQueried()
669{
670 QFutureWatcher<LoadScriptList> *watcher = dynamic_cast< QFutureWatcher<LoadScriptList>* >(sender());
671 if (!watcher) {
672 // slot invoked not from a FutureWatcher
673 return;
674 }
675
676 LoadScriptList scriptsToLoad = watcher->result();
677 for (LoadScriptList::const_iterator it = scriptsToLoad.constBegin();
678 it != scriptsToLoad.constEnd();
679 ++it) {
680 if (it->first) {
681 loadScript(it->second.first, it->second.second);
682 } else {
683 loadDeclarativeScript(it->second.first, it->second.second);
684 }
685 }
686
687 runScripts();
688 watcher->deleteLater();
689}
690
691bool KWin::Scripting::isScriptLoaded(const QString &pluginName) const
692{
693 QMutexLocker locker(m_scriptsLock.data());
694 foreach (AbstractScript *script, scripts) {
695 if (script->pluginName() == pluginName) {
696 return true;
697 }
698 }
699 return false;
700}
701
702bool KWin::Scripting::unloadScript(const QString &pluginName)
703{
704 QMutexLocker locker(m_scriptsLock.data());
705 foreach (AbstractScript *script, scripts) {
706 if (script->pluginName() == pluginName) {
707 script->deleteLater();
708 return true;
709 }
710 }
711 return false;
712}
713
714void KWin::Scripting::runScripts()
715{
716 QMutexLocker locker(m_scriptsLock.data());
717 for (int i = 0; i < scripts.size(); i++) {
718 scripts.at(i)->run();
719 }
720}
721
722void KWin::Scripting::scriptDestroyed(QObject *object)
723{
724 QMutexLocker locker(m_scriptsLock.data());
725 scripts.removeAll(static_cast<KWin::Script*>(object));
726}
727
728int KWin::Scripting::loadScript(const QString &filePath, const QString& pluginName)
729{
730 QMutexLocker locker(m_scriptsLock.data());
731 if (isScriptLoaded(pluginName)) {
732 return -1;
733 }
734 const int id = scripts.size();
735 KWin::Script *script = new KWin::Script(id, filePath, pluginName, this);
736 connect(script, SIGNAL(destroyed(QObject*)), SLOT(scriptDestroyed(QObject*)));
737 scripts.append(script);
738 return id;
739}
740
741int KWin::Scripting::loadDeclarativeScript(const QString& filePath, const QString& pluginName)
742{
743 QMutexLocker locker(m_scriptsLock.data());
744 if (isScriptLoaded(pluginName)) {
745 return -1;
746 }
747 const int id = scripts.size();
748 KWin::DeclarativeScript *script = new KWin::DeclarativeScript(id, filePath, pluginName, this);
749 connect(script, SIGNAL(destroyed(QObject*)), SLOT(scriptDestroyed(QObject*)));
750 scripts.append(script);
751 return id;
752}
753
754KWin::Scripting::~Scripting()
755{
756 QDBusConnection::sessionBus().unregisterObject("/Scripting");
757 QDBusConnection::sessionBus().unregisterService("org.kde.kwin.Scripting");
758 s_self = NULL;
759}
760
761QList< QAction * > KWin::Scripting::actionsForUserActionMenu(KWin::Client *c, QMenu *parent)
762{
763 QList<QAction*> actions;
764 foreach (AbstractScript *script, scripts) {
765 actions << script->actionsForUserActionMenu(c, parent);
766 }
767 return actions;
768}
769
770#include "scripting.moc"
771