1 | /******************************************************************** |
2 | KWin - the KDE window manager |
3 | This file is part of the KDE project. |
4 | |
5 | Copyright (C) 2010 Rohan Prabhu <rohan@rohanprabhu.com> |
6 | Copyright (C) 2011 Martin Gräßlin <mgraesslin@kde.org> |
7 | |
8 | This program is free software; you can redistribute it and/or modify |
9 | it under the terms of the GNU General Public License as published by |
10 | the Free Software Foundation; either version 2 of the License, or |
11 | (at your option) any later version. |
12 | |
13 | This program is distributed in the hope that it will be useful, |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | GNU General Public License for more details. |
17 | |
18 | You should have received a copy of the GNU General Public License |
19 | along 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 | |
55 | QScriptValue 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 | |
79 | QScriptValue 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 | |
97 | QScriptValue kwinScriptGlobalShortcut(QScriptContext *context, QScriptEngine *engine) |
98 | { |
99 | return KWin::globalShortcut<KWin::AbstractScript*>(context, engine); |
100 | } |
101 | |
102 | QScriptValue kwinAssertTrue(QScriptContext *context, QScriptEngine *engine) |
103 | { |
104 | return KWin::scriptingAssert<bool>(context, engine, 1, 2, true); |
105 | } |
106 | |
107 | QScriptValue kwinAssertFalse(QScriptContext *context, QScriptEngine *engine) |
108 | { |
109 | return KWin::scriptingAssert<bool>(context, engine, 1, 2, false); |
110 | } |
111 | |
112 | QScriptValue kwinAssertEquals(QScriptContext *context, QScriptEngine *engine) |
113 | { |
114 | return KWin::scriptingAssert<QVariant>(context, engine, 2, 3); |
115 | } |
116 | |
117 | QScriptValue 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 | |
135 | QScriptValue 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 | |
153 | QScriptValue kwinRegisterScreenEdge(QScriptContext *context, QScriptEngine *engine) |
154 | { |
155 | return KWin::registerScreenEdge<KWin::AbstractScript*>(context, engine); |
156 | } |
157 | |
158 | QScriptValue (QScriptContext *context, QScriptEngine *engine) |
159 | { |
160 | return KWin::registerUserActionsMenu<KWin::AbstractScript*>(context, engine); |
161 | } |
162 | |
163 | QScriptValue 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 | |
215 | KWin::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 | |
228 | KWin::AbstractScript::~AbstractScript() |
229 | { |
230 | } |
231 | |
232 | KConfigGroup KWin::AbstractScript::config() const |
233 | { |
234 | return KGlobal::config()->group("Script-" + m_pluginName); |
235 | } |
236 | |
237 | void KWin::AbstractScript::stop() |
238 | { |
239 | deleteLater(); |
240 | } |
241 | |
242 | void KWin::AbstractScript::printMessage(const QString &message) |
243 | { |
244 | kDebug(1212) << scriptFile().fileName() << ":" << message; |
245 | emit print(message); |
246 | } |
247 | |
248 | void KWin::AbstractScript::registerShortcut(QAction *a, QScriptValue callback) |
249 | { |
250 | m_shortcutCallbacks.insert(a, callback); |
251 | connect(a, SIGNAL(triggered(bool)), SLOT(globalShortcutTriggered())); |
252 | } |
253 | |
254 | void KWin::AbstractScript::globalShortcutTriggered() |
255 | { |
256 | callGlobalShortcutCallback<KWin::AbstractScript*>(this, sender()); |
257 | } |
258 | |
259 | bool KWin::AbstractScript::borderActivated(KWin::ElectricBorder edge) |
260 | { |
261 | screenEdgeActivated(this, edge); |
262 | return true; |
263 | } |
264 | |
265 | void 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 | |
306 | int KWin::AbstractScript::registerCallback(QScriptValue value) |
307 | { |
308 | int id = m_callbacks.size(); |
309 | m_callbacks.insert(id, value); |
310 | return id; |
311 | } |
312 | |
313 | void 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 | |
332 | void KWin::AbstractScript::(QScriptValue callback) |
333 | { |
334 | m_userActionsMenuCallbacks.append(callback); |
335 | } |
336 | |
337 | QList< QAction * > KWin::AbstractScript::(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 | |
360 | QAction *KWin::AbstractScript::(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 | |
394 | QAction *KWin::AbstractScript::(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 | |
406 | QAction *KWin::AbstractScript::(const QString &title, QScriptValue &items, QMenu *parent) |
407 | { |
408 | QMenu * = 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 | |
425 | void KWin::AbstractScript::actionDestroyed(QObject *object) |
426 | { |
427 | // TODO: Qt 5 - change to lambda function |
428 | m_shortcutCallbacks.remove(static_cast<QAction*>(object)); |
429 | } |
430 | |
431 | KWin::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 | |
440 | KWin::Script::~Script() |
441 | { |
442 | QDBusConnection::sessionBus().unregisterObject('/' + QString::number(scriptId())); |
443 | } |
444 | |
445 | void 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 | |
456 | QByteArray 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 | |
466 | void 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 | |
499 | void 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 | |
517 | KWin::ScriptUnloaderAgent::ScriptUnloaderAgent(KWin::Script *script) |
518 | : QScriptEngineAgent(script->engine()) |
519 | , m_script(script) |
520 | { |
521 | script->engine()->setAgent(this); |
522 | } |
523 | |
524 | void KWin::ScriptUnloaderAgent::scriptUnload(qint64 id) |
525 | { |
526 | Q_UNUSED(id) |
527 | m_script->stop(); |
528 | } |
529 | |
530 | KWin::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 | |
538 | KWin::DeclarativeScript::~DeclarativeScript() |
539 | { |
540 | } |
541 | |
542 | void 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 | |
572 | void 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 | |
582 | KWin::Scripting *KWin::Scripting::s_self = NULL; |
583 | |
584 | KWin::Scripting *KWin::Scripting::create(QObject *parent) |
585 | { |
586 | Q_ASSERT(!s_self); |
587 | s_self = new Scripting(parent); |
588 | return s_self; |
589 | } |
590 | |
591 | KWin::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 | |
601 | void 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 | |
625 | LoadScriptList 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 | |
668 | void 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 | |
691 | bool 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 | |
702 | bool 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 | |
714 | void 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 | |
722 | void KWin::Scripting::scriptDestroyed(QObject *object) |
723 | { |
724 | QMutexLocker locker(m_scriptsLock.data()); |
725 | scripts.removeAll(static_cast<KWin::Script*>(object)); |
726 | } |
727 | |
728 | int 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 | |
741 | int 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 | |
754 | KWin::Scripting::~Scripting() |
755 | { |
756 | QDBusConnection::sessionBus().unregisterObject("/Scripting" ); |
757 | QDBusConnection::sessionBus().unregisterService("org.kde.kwin.Scripting" ); |
758 | s_self = NULL; |
759 | } |
760 | |
761 | QList< QAction * > KWin::Scripting::(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 | |