Changeset View
Changeset View
Standalone View
Standalone View
scripting/scripting.cpp
1 | /******************************************************************** | 1 | /******************************************************************** | ||
---|---|---|---|---|---|
2 | KWin - the KDE window manager | 2 | KWin - the KDE window manager | ||
3 | This file is part of the KDE project. | 3 | This file is part of the KDE project. | ||
4 | 4 | | |||
5 | Copyright (C) 2010 Rohan Prabhu <rohan@rohanprabhu.com> | 5 | Copyright (C) 2010 Rohan Prabhu <rohan@rohanprabhu.com> | ||
6 | Copyright (C) 2011 Martin Gräßlin <mgraesslin@kde.org> | 6 | Copyright (C) 2011 Martin Gräßlin <mgraesslin@kde.org> | ||
7 | Copyright (C) 2019 Vlad Zagorodniy <vladzzag@gmail.com> | ||||
7 | 8 | | |||
8 | This program is free software; you can redistribute it and/or modify | 9 | 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 | 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 | the Free Software Foundation; either version 2 of the License, or | ||
11 | (at your option) any later version. | 12 | (at your option) any later version. | ||
12 | 13 | | |||
13 | This program is distributed in the hope that it will be useful, | 14 | This program is distributed in the hope that it will be useful, | ||
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of | 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | GNU General Public License for more details. | 17 | GNU General Public License for more details. | ||
17 | 18 | | |||
18 | You should have received a copy of the GNU General Public License | 19 | 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 | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
20 | *********************************************************************/ | 21 | *********************************************************************/ | ||
21 | 22 | | |||
22 | #include "scripting.h" | 23 | #include "scripting.h" | ||
23 | // own | 24 | // own | ||
24 | #include "dbuscall.h" | 25 | #include "dbuscall.h" | ||
25 | #include "meta.h" | | |||
26 | #include "scriptingutils.h" | 26 | #include "scriptingutils.h" | ||
27 | #include "workspace_wrapper.h" | 27 | #include "workspace_wrapper.h" | ||
28 | #include "screenedgeitem.h" | 28 | #include "screenedgeitem.h" | ||
29 | #include "scripting_model.h" | 29 | #include "scripting_model.h" | ||
30 | #include "scripting_logging.h" | 30 | #include "scripting_logging.h" | ||
31 | #include "timer_wrapper.h" | ||||
31 | #include "../client.h" | 32 | #include "../client.h" | ||
32 | #include "../thumbnailitem.h" | 33 | #include "../thumbnailitem.h" | ||
33 | #include "../options.h" | 34 | #include "../options.h" | ||
34 | #include "../workspace.h" | 35 | #include "../workspace.h" | ||
35 | // KDE | 36 | // KDE | ||
36 | #include <KConfigGroup> | 37 | #include <KConfigGroup> | ||
37 | #include <KPackage/PackageLoader> | 38 | #include <KPackage/PackageLoader> | ||
38 | // Qt | 39 | // Qt | ||
39 | #include <QDBusConnection> | 40 | #include <QDBusConnection> | ||
40 | #include <QDBusMessage> | 41 | #include <QDBusMessage> | ||
41 | #include <QDBusPendingCallWatcher> | 42 | #include <QDBusPendingCallWatcher> | ||
42 | #include <QDebug> | 43 | #include <QDebug> | ||
43 | #include <QFutureWatcher> | 44 | #include <QFutureWatcher> | ||
44 | #include <QSettings> | 45 | #include <QSettings> | ||
45 | #include <QtConcurrentRun> | 46 | #include <QtConcurrentRun> | ||
46 | #include <QMenu> | 47 | #include <QMenu> | ||
47 | #include <QQmlContext> | 48 | #include <QQmlContext> | ||
48 | #include <QQmlEngine> | 49 | #include <QQmlEngine> | ||
49 | #include <QQmlExpression> | 50 | #include <QQmlExpression> | ||
50 | #include <QtScript/QScriptEngine> | | |||
51 | #include <QtScript/QScriptValue> | | |||
52 | #include <QStandardPaths> | 51 | #include <QStandardPaths> | ||
53 | #include <QQuickWindow> | 52 | #include <QQuickWindow> | ||
54 | 53 | | |||
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 | qCDebug(KWIN_SCRIPTING) << "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 kwinUnregisterScreenEdge(QScriptContext *context, QScriptEngine *engine) | | |||
159 | { | | |||
160 | return KWin::unregisterScreenEdge<KWin::AbstractScript*>(context, engine); | | |||
161 | } | | |||
162 | | ||||
163 | QScriptValue kwinRegisterTouchScreenEdge(QScriptContext *context, QScriptEngine *engine) | | |||
164 | { | | |||
165 | return KWin::registerTouchScreenEdge<KWin::Script*>(context, engine); | | |||
166 | } | | |||
167 | | ||||
168 | QScriptValue kwinUnregisterTouchScreenEdge(QScriptContext *context, QScriptEngine *engine) | | |||
169 | { | | |||
170 | return KWin::unregisterTouchScreenEdge<KWin::Script*>(context, engine); | | |||
171 | } | | |||
172 | | ||||
173 | QScriptValue kwinRegisterUserActionsMenu(QScriptContext *context, QScriptEngine *engine) | | |||
174 | { | | |||
175 | return KWin::registerUserActionsMenu<KWin::AbstractScript*>(context, engine); | | |||
176 | } | | |||
177 | | ||||
178 | QScriptValue kwinCallDBus(QScriptContext *context, QScriptEngine *engine) | | |||
179 | { | | |||
180 | KWin::AbstractScript *script = qobject_cast<KWin::AbstractScript*>(context->callee().data().toQObject()); | | |||
181 | if (!script) { | | |||
182 | context->throwError(QScriptContext::UnknownError, QStringLiteral("Internal Error: script not registered")); | | |||
183 | return engine->undefinedValue(); | | |||
184 | } | | |||
185 | if (context->argumentCount() < 4) { | | |||
186 | context->throwError(QScriptContext::SyntaxError, | | |||
187 | i18nc("Error in KWin Script", | | |||
188 | "Invalid number of arguments. At least service, path, interface and method need to be provided")); | | |||
189 | return engine->undefinedValue(); | | |||
190 | } | | |||
191 | if (!KWin::validateArgumentType<QString, QString, QString, QString>(context)) { | | |||
192 | context->throwError(QScriptContext::SyntaxError, | | |||
193 | i18nc("Error in KWin Script", | | |||
194 | "Invalid type. Service, path, interface and method need to be string values")); | | |||
195 | return engine->undefinedValue(); | | |||
196 | } | | |||
197 | const QString service = context->argument(0).toString(); | | |||
198 | const QString path = context->argument(1).toString(); | | |||
199 | const QString interface = context->argument(2).toString(); | | |||
200 | const QString method = context->argument(3).toString(); | | |||
201 | int argumentsCount = context->argumentCount(); | | |||
202 | if (context->argument(argumentsCount-1).isFunction()) { | | |||
203 | --argumentsCount; | | |||
204 | } | | |||
205 | QDBusMessage msg = QDBusMessage::createMethodCall(service, path, interface, method); | | |||
206 | QVariantList arguments; | | |||
207 | for (int i=4; i<argumentsCount; ++i) { | | |||
208 | if (context->argument(i).isArray()) { | | |||
209 | QStringList stringArray = engine->fromScriptValue<QStringList>(context->argument(i)); | | |||
210 | arguments << qVariantFromValue(stringArray); | | |||
211 | } else { | | |||
212 | arguments << context->argument(i).toVariant(); | | |||
213 | } | | |||
214 | } | | |||
215 | if (!arguments.isEmpty()) { | | |||
216 | msg.setArguments(arguments); | | |||
217 | } | | |||
218 | if (argumentsCount == context->argumentCount()) { | | |||
219 | // no callback, just fire and forget | | |||
220 | QDBusConnection::sessionBus().asyncCall(msg); | | |||
221 | } else { | | |||
222 | // with a callback | | |||
223 | QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(QDBusConnection::sessionBus().asyncCall(msg), script); | | |||
224 | watcher->setProperty("callback", script->registerCallback(context->argument(context->argumentCount()-1))); | | |||
225 | QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), script, SLOT(slotPendingDBusCall(QDBusPendingCallWatcher*))); | | |||
226 | } | | |||
227 | return engine->undefinedValue(); | | |||
228 | } | | |||
229 | | ||||
230 | KWin::AbstractScript::AbstractScript(int id, QString scriptName, QString pluginName, QObject *parent) | 54 | KWin::AbstractScript::AbstractScript(int id, QString scriptName, QString pluginName, QObject *parent) | ||
231 | : QObject(parent) | 55 | : QObject(parent) | ||
232 | , m_scriptId(id) | 56 | , m_scriptId(id) | ||
233 | , m_fileName(scriptName) | 57 | , m_fileName(scriptName) | ||
234 | , m_pluginName(pluginName) | 58 | , m_pluginName(pluginName) | ||
235 | , m_running(false) | 59 | , m_running(false) | ||
236 | { | 60 | { | ||
237 | if (m_pluginName.isNull()) { | 61 | if (m_pluginName.isNull()) { | ||
Show All 10 Lines | 71 | { | |||
248 | return kwinApp()->config()->group(QLatin1String("Script-") + m_pluginName); | 72 | return kwinApp()->config()->group(QLatin1String("Script-") + m_pluginName); | ||
249 | } | 73 | } | ||
250 | 74 | | |||
251 | void KWin::AbstractScript::stop() | 75 | void KWin::AbstractScript::stop() | ||
252 | { | 76 | { | ||
253 | deleteLater(); | 77 | deleteLater(); | ||
254 | } | 78 | } | ||
255 | 79 | | |||
256 | void KWin::AbstractScript::printMessage(const QString &message) | | |||
257 | { | | |||
258 | qCDebug(KWIN_SCRIPTING) << fileName() << ":" << message; | | |||
259 | emit print(message); | | |||
260 | } | | |||
261 | | ||||
262 | void KWin::AbstractScript::registerShortcut(QAction *a, QScriptValue callback) | | |||
263 | { | | |||
264 | m_shortcutCallbacks.insert(a, callback); | | |||
265 | connect(a, SIGNAL(triggered(bool)), SLOT(globalShortcutTriggered())); | | |||
266 | } | | |||
267 | | ||||
268 | void KWin::AbstractScript::globalShortcutTriggered() | | |||
269 | { | | |||
270 | callGlobalShortcutCallback<KWin::AbstractScript*>(this, sender()); | | |||
271 | } | | |||
272 | | ||||
273 | bool KWin::AbstractScript::borderActivated(KWin::ElectricBorder edge) | | |||
274 | { | | |||
275 | screenEdgeActivated(this, edge); | | |||
276 | return true; | | |||
277 | } | | |||
278 | | ||||
279 | void KWin::Script::installScriptFunctions(QScriptEngine* engine) | | |||
280 | { | | |||
281 | // add our print | | |||
282 | QScriptValue printFunc = engine->newFunction(kwinScriptPrint); | | |||
283 | printFunc.setData(engine->newQObject(this)); | | |||
284 | engine->globalObject().setProperty(QStringLiteral("print"), printFunc); | | |||
285 | // add read config | | |||
286 | QScriptValue configFunc = engine->newFunction(kwinScriptReadConfig); | | |||
287 | configFunc.setData(engine->newQObject(this)); | | |||
288 | engine->globalObject().setProperty(QStringLiteral("readConfig"), configFunc); | | |||
289 | QScriptValue dbusCallFunc = engine->newFunction(kwinCallDBus); | | |||
290 | dbusCallFunc.setData(engine->newQObject(this)); | | |||
291 | engine->globalObject().setProperty(QStringLiteral("callDBus"), dbusCallFunc); | | |||
292 | // add global Shortcut | | |||
293 | registerGlobalShortcutFunction(this, engine, kwinScriptGlobalShortcut); | | |||
294 | // add screen edge | | |||
295 | registerScreenEdgeFunction(this, engine, kwinRegisterScreenEdge); | | |||
296 | unregisterScreenEdgeFunction(this, engine, kwinUnregisterScreenEdge); | | |||
297 | registerTouchScreenEdgeFunction(this, engine, kwinRegisterTouchScreenEdge); | | |||
298 | unregisterTouchScreenEdgeFunction(this, engine, kwinUnregisterTouchScreenEdge); | | |||
299 | | ||||
300 | // add user actions menu register function | | |||
301 | registerUserActionsMenuFunction(this, engine, kwinRegisterUserActionsMenu); | | |||
302 | // add assertions | | |||
303 | QScriptValue assertTrueFunc = engine->newFunction(kwinAssertTrue); | | |||
304 | engine->globalObject().setProperty(QStringLiteral("assertTrue"), assertTrueFunc); | | |||
305 | engine->globalObject().setProperty(QStringLiteral("assert"), assertTrueFunc); | | |||
306 | QScriptValue assertFalseFunc = engine->newFunction(kwinAssertFalse); | | |||
307 | engine->globalObject().setProperty(QStringLiteral("assertFalse"), assertFalseFunc); | | |||
308 | QScriptValue assertEqualsFunc = engine->newFunction(kwinAssertEquals); | | |||
309 | engine->globalObject().setProperty(QStringLiteral("assertEquals"), assertEqualsFunc); | | |||
310 | QScriptValue assertNullFunc = engine->newFunction(kwinAssertNull); | | |||
311 | engine->globalObject().setProperty(QStringLiteral("assertNull"), assertNullFunc); | | |||
312 | engine->globalObject().setProperty(QStringLiteral("assertEquals"), assertEqualsFunc); | | |||
313 | QScriptValue assertNotNullFunc = engine->newFunction(kwinAssertNotNull); | | |||
314 | engine->globalObject().setProperty(QStringLiteral("assertNotNull"), assertNotNullFunc); | | |||
315 | // global properties | | |||
316 | engine->globalObject().setProperty(QStringLiteral("KWin"), engine->newQMetaObject(&QtScriptWorkspaceWrapper::staticMetaObject)); | | |||
317 | QScriptValue workspace = engine->newQObject(Scripting::self()->workspaceWrapper(), QScriptEngine::QtOwnership, | | |||
318 | QScriptEngine::ExcludeDeleteLater); | | |||
319 | engine->globalObject().setProperty(QStringLiteral("workspace"), workspace, QScriptValue::Undeletable); | | |||
320 | // install meta functions | | |||
321 | KWin::MetaScripting::registration(engine); | | |||
322 | } | | |||
323 | | ||||
324 | int KWin::AbstractScript::registerCallback(QScriptValue value) | | |||
325 | { | | |||
326 | int id = m_callbacks.size(); | | |||
327 | m_callbacks.insert(id, value); | | |||
328 | return id; | | |||
329 | } | | |||
330 | | ||||
331 | void KWin::AbstractScript::slotPendingDBusCall(QDBusPendingCallWatcher* watcher) | | |||
332 | { | | |||
333 | if (watcher->isError()) { | | |||
334 | qCDebug(KWIN_SCRIPTING) << "Received D-Bus message is error"; | | |||
335 | watcher->deleteLater(); | | |||
336 | return; | | |||
337 | } | | |||
338 | const int id = watcher->property("callback").toInt(); | | |||
339 | QDBusMessage reply = watcher->reply(); | | |||
340 | QScriptValue callback (m_callbacks.value(id)); | | |||
341 | QScriptValueList arguments; | | |||
342 | foreach (const QVariant &argument, reply.arguments()) { | | |||
343 | arguments << callback.engine()->newVariant(argument); | | |||
344 | } | | |||
345 | callback.call(QScriptValue(), arguments); | | |||
346 | m_callbacks.remove(id); | | |||
347 | watcher->deleteLater(); | | |||
348 | } | | |||
349 | | ||||
350 | void KWin::AbstractScript::registerUseractionsMenuCallback(QScriptValue callback) | | |||
351 | { | | |||
352 | m_userActionsMenuCallbacks.append(callback); | | |||
353 | } | | |||
354 | | ||||
355 | QList< QAction * > KWin::AbstractScript::actionsForUserActionMenu(KWin::AbstractClient *c, QMenu *parent) | | |||
356 | { | | |||
357 | QList<QAction*> returnActions; | | |||
358 | for (QList<QScriptValue>::const_iterator it = m_userActionsMenuCallbacks.constBegin(); it != m_userActionsMenuCallbacks.constEnd(); ++it) { | | |||
359 | QScriptValue callback(*it); | | |||
360 | QScriptValueList arguments; | | |||
361 | arguments << callback.engine()->newQObject(c); | | |||
362 | QScriptValue actions = callback.call(QScriptValue(), arguments); | | |||
363 | if (!actions.isValid() || actions.isUndefined() || actions.isNull()) { | | |||
364 | // script does not want to handle this Client | | |||
365 | continue; | | |||
366 | } | | |||
367 | if (actions.isObject()) { | | |||
368 | QAction *a = scriptValueToAction(actions, parent); | | |||
369 | if (a) { | | |||
370 | returnActions << a; | | |||
371 | } | | |||
372 | } | | |||
373 | } | | |||
374 | | ||||
375 | return returnActions; | | |||
376 | } | | |||
377 | | ||||
378 | QAction *KWin::AbstractScript::scriptValueToAction(QScriptValue &value, QMenu *parent) | | |||
379 | { | | |||
380 | QScriptValue titleValue = value.property(QStringLiteral("text")); | | |||
381 | QScriptValue checkableValue = value.property(QStringLiteral("checkable")); | | |||
382 | QScriptValue checkedValue = value.property(QStringLiteral("checked")); | | |||
383 | QScriptValue itemsValue = value.property(QStringLiteral("items")); | | |||
384 | QScriptValue triggeredValue = value.property(QStringLiteral("triggered")); | | |||
385 | | ||||
386 | if (!titleValue.isValid()) { | | |||
387 | // title not specified - does not make any sense to include | | |||
388 | return nullptr; | | |||
389 | } | | |||
390 | const QString title = titleValue.toString(); | | |||
391 | const bool checkable = checkableValue.isValid() && checkableValue.toBool(); | | |||
392 | const bool checked = checkable && checkedValue.isValid() && checkedValue.toBool(); | | |||
393 | // either a menu or a menu item | | |||
394 | if (itemsValue.isValid()) { | | |||
395 | if (!itemsValue.isArray()) { | | |||
396 | // not an array, so cannot be a menu | | |||
397 | return nullptr; | | |||
398 | } | | |||
399 | QScriptValue lengthValue = itemsValue.property(QStringLiteral("length")); | | |||
400 | if (!lengthValue.isValid() || !lengthValue.isNumber() || lengthValue.toInteger() == 0) { | | |||
401 | // length property missing | | |||
402 | return nullptr; | | |||
403 | } | | |||
404 | return createMenu(title, itemsValue, parent); | | |||
405 | } else if (triggeredValue.isValid()) { | | |||
406 | // normal item | | |||
407 | return createAction(title, checkable, checked, triggeredValue, parent); | | |||
408 | } | | |||
409 | return nullptr; | | |||
410 | } | | |||
411 | | ||||
412 | QAction *KWin::AbstractScript::createAction(const QString &title, bool checkable, bool checked, QScriptValue &callback, QMenu *parent) | | |||
413 | { | | |||
414 | QAction *action = new QAction(title, parent); | | |||
415 | action->setCheckable(checkable); | | |||
416 | action->setChecked(checked); | | |||
417 | // TODO: rename m_shortcutCallbacks | | |||
418 | m_shortcutCallbacks.insert(action, callback); | | |||
419 | connect(action, SIGNAL(triggered(bool)), SLOT(globalShortcutTriggered())); | | |||
420 | connect(action, SIGNAL(destroyed(QObject*)), SLOT(actionDestroyed(QObject*))); | | |||
421 | return action; | | |||
422 | } | | |||
423 | | ||||
424 | QAction *KWin::AbstractScript::createMenu(const QString &title, QScriptValue &items, QMenu *parent) | | |||
425 | { | | |||
426 | QMenu *menu = new QMenu(title, parent); | | |||
427 | const int length = static_cast<int>(items.property(QStringLiteral("length")).toInteger()); | | |||
428 | for (int i=0; i<length; ++i) { | | |||
429 | QScriptValue value = items.property(QString::number(i)); | | |||
430 | if (!value.isValid()) { | | |||
431 | continue; | | |||
432 | } | | |||
433 | if (value.isObject()) { | | |||
434 | QAction *a = scriptValueToAction(value, menu); | | |||
435 | if (a) { | | |||
436 | menu->addAction(a); | | |||
437 | } | | |||
438 | } | | |||
439 | } | | |||
440 | return menu->menuAction(); | | |||
441 | } | | |||
442 | | ||||
443 | void KWin::AbstractScript::actionDestroyed(QObject *object) | | |||
444 | { | | |||
445 | // TODO: Qt 5 - change to lambda function | | |||
446 | m_shortcutCallbacks.remove(static_cast<QAction*>(object)); | | |||
447 | } | | |||
448 | | ||||
449 | KWin::Script::Script(int id, QString scriptName, QString pluginName, QObject* parent) | 80 | KWin::Script::Script(int id, QString scriptName, QString pluginName, QObject* parent) | ||
450 | : AbstractScript(id, scriptName, pluginName, parent) | 81 | : AbstractScript(id, scriptName, pluginName, parent) | ||
451 | , m_engine(new QScriptEngine(this)) | 82 | , m_engine(new QJSEngine(this)) | ||
452 | , m_starting(false) | 83 | , m_starting(false) | ||
453 | , m_agent(new ScriptUnloaderAgent(this)) | | |||
454 | { | 84 | { | ||
455 | QDBusConnection::sessionBus().registerObject(QLatin1Char('/') + QString::number(scriptId()), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportScriptableInvokables); | 85 | QDBusConnection::sessionBus().registerObject(QLatin1Char('/') + QString::number(scriptId()), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportScriptableInvokables); | ||
456 | } | 86 | } | ||
457 | 87 | | |||
458 | KWin::Script::~Script() | 88 | KWin::Script::~Script() | ||
459 | { | 89 | { | ||
460 | QDBusConnection::sessionBus().unregisterObject(QLatin1Char('/') + QString::number(scriptId())); | 90 | QDBusConnection::sessionBus().unregisterObject(QLatin1Char('/') + QString::number(scriptId())); | ||
461 | } | 91 | } | ||
▲ Show 20 Lines • Show All 41 Lines • ▼ Show 20 Line(s) | 132 | if (m_invocationContext.type() == QDBusMessage::MethodCallMessage) { | |||
503 | auto reply = m_invocationContext.createErrorReply("org.kde.kwin.Scripting.FileError", QString("Could not open %1").arg(fileName())); | 133 | auto reply = m_invocationContext.createErrorReply("org.kde.kwin.Scripting.FileError", QString("Could not open %1").arg(fileName())); | ||
504 | QDBusConnection::sessionBus().send(reply); | 134 | QDBusConnection::sessionBus().send(reply); | ||
505 | m_invocationContext = QDBusMessage(); | 135 | m_invocationContext = QDBusMessage(); | ||
506 | } | 136 | } | ||
507 | 137 | | |||
508 | return; | 138 | return; | ||
509 | } | 139 | } | ||
510 | 140 | | |||
511 | QScriptValue optionsValue = m_engine->newQObject(options, QScriptEngine::QtOwnership, | 141 | // Install console functions (e.g. console.assert(), console.log(), etc). | ||
512 | QScriptEngine::ExcludeSuperClassContents | QScriptEngine::ExcludeDeleteLater); | 142 | m_engine->installExtensions(QJSEngine::ConsoleExtension); | ||
513 | m_engine->globalObject().setProperty(QStringLiteral("options"), optionsValue, QScriptValue::Undeletable); | | |||
514 | m_engine->globalObject().setProperty(QStringLiteral("QTimer"), constructTimerClass(m_engine)); | | |||
515 | QObject::connect(m_engine, SIGNAL(signalHandlerException(QScriptValue)), this, SLOT(sigException(QScriptValue))); | | |||
516 | KWin::MetaScripting::supplyConfig(m_engine); | | |||
517 | installScriptFunctions(m_engine); | | |||
518 | 143 | | |||
519 | QScriptValue ret = m_engine->evaluate(QString::fromUtf8(watcher->result())); | 144 | // Make the timer visible to QJSEngine. | ||
520 | 145 | QJSValue timerMetaObject = m_engine->newQMetaObject(&TimerWrapper::staticMetaObject); | |||
521 | if (ret.isError()) { | 146 | m_engine->globalObject().setProperty("QTimer", timerMetaObject); | ||
522 | sigException(ret); | 147 | m_engine->globalObject().setProperty("Timer", timerMetaObject); | ||
148 | | ||||
149 | // Expose enums. | ||||
150 | m_engine->globalObject().setProperty(QStringLiteral("KWin"), m_engine->newQMetaObject(&QtScriptWorkspaceWrapper::staticMetaObject)); | ||||
151 | | ||||
152 | // Make the options object visible to QJSEngine. | ||||
153 | QJSValue optionsObject = m_engine->newQObject(options); | ||||
154 | QQmlEngine::setObjectOwnership(options, QQmlEngine::CppOwnership); | ||||
155 | m_engine->globalObject().setProperty(QStringLiteral("options"), optionsObject); | ||||
156 | | ||||
157 | // Make the workspace visible to QJSEngine. | ||||
158 | QJSValue workspaceObject = m_engine->newQObject(Scripting::self()->workspaceWrapper()); | ||||
159 | QQmlEngine::setObjectOwnership(Scripting::self()->workspaceWrapper(), QQmlEngine::CppOwnership); | ||||
160 | m_engine->globalObject().setProperty(QStringLiteral("workspace"), workspaceObject); | ||||
161 | | ||||
162 | QJSValue self = m_engine->newQObject(this); | ||||
163 | QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); | ||||
164 | m_engine->globalObject().setProperty(QStringLiteral("readConfig"), self.property(QStringLiteral("readConfig"))); | ||||
165 | m_engine->globalObject().setProperty(QStringLiteral("callDBus"), self.property(QStringLiteral("callDBus"))); | ||||
166 | m_engine->globalObject().setProperty(QStringLiteral("registerShortcut"), self.property(QStringLiteral("registerShortcut"))); | ||||
167 | m_engine->globalObject().setProperty(QStringLiteral("registerScreenEdge"), self.property(QStringLiteral("registerScreenEdge"))); | ||||
168 | m_engine->globalObject().setProperty(QStringLiteral("unregisterScreenEdge"), self.property(QStringLiteral("unregisterScreenEdge"))); | ||||
169 | m_engine->globalObject().setProperty(QStringLiteral("registerTouchScreenEdge"), self.property(QStringLiteral("registerTouchScreenEdge"))); | ||||
170 | m_engine->globalObject().setProperty(QStringLiteral("unregisterTouchScreenEdge"), self.property(QStringLiteral("unregisterTouchScreenEdge"))); | ||||
171 | m_engine->globalObject().setProperty(QStringLiteral("registerUserActionsMenu"), self.property(QStringLiteral("registerUserActionsMenu"))); | ||||
172 | | ||||
173 | // Inject assertion functions. It would be better to create a module with all | ||||
174 | // this assert functions or just deprecate them in favor of console.assert(). | ||||
175 | QJSValue result = m_engine->evaluate(QStringLiteral( | ||||
176 | "function assert(condition, message) {" | ||||
177 | " console.assert(condition, message || 'Assertion failed');" | ||||
178 | "}" | ||||
179 | "" | ||||
180 | "function assertTrue(condition, message) {" | ||||
181 | " console.assert(condition, message || 'Assertion failed');" | ||||
182 | "}" | ||||
183 | "" | ||||
184 | "function assertFalse(condition, message) {" | ||||
185 | " console.assert(!condition, message || 'Assertion failed');" | ||||
186 | "}" | ||||
187 | "" | ||||
188 | "function assertNull(value, message) {" | ||||
189 | " console.assert(value === null, message || 'Assertion failed');" | ||||
190 | "}" | ||||
191 | "" | ||||
192 | "function assertNotNull(value, message) {" | ||||
193 | " console.assert(value !== null, message || 'Assertion failed');" | ||||
194 | "}" | ||||
195 | "" | ||||
196 | "function assertEquals(expected, actual, message) {" | ||||
197 | " console.assert(expected === actual, message || 'Assertion failed');" | ||||
198 | "}" | ||||
199 | )); | ||||
200 | Q_ASSERT(!result.isError()); | ||||
201 | | ||||
202 | result = m_engine->evaluate(QString::fromUtf8(watcher->result()), fileName()); | ||||
203 | if (result.isError()) { | ||||
204 | const QString message = fileName() + QLatin1String(":") | ||||
205 | + result.property(QStringLiteral("lineNumber")).toString() | ||||
206 | + QLatin1String(": error: ") | ||||
207 | + result.property(QStringLiteral("message")).toString(); | ||||
208 | qCWarning(KWIN_SCRIPTING) << qPrintable(message); | ||||
523 | deleteLater(); | 209 | deleteLater(); | ||
524 | } | 210 | } | ||
525 | 211 | | |||
526 | if (m_invocationContext.type() == QDBusMessage::MethodCallMessage) { | 212 | if (m_invocationContext.type() == QDBusMessage::MethodCallMessage) { | ||
527 | auto reply = m_invocationContext.createReply(); | 213 | auto reply = m_invocationContext.createReply(); | ||
528 | QDBusConnection::sessionBus().send(reply); | 214 | QDBusConnection::sessionBus().send(reply); | ||
529 | m_invocationContext = QDBusMessage(); | 215 | m_invocationContext = QDBusMessage(); | ||
530 | } | 216 | } | ||
531 | 217 | | |||
532 | watcher->deleteLater(); | 218 | watcher->deleteLater(); | ||
533 | setRunning(true); | 219 | setRunning(true); | ||
534 | m_starting = false; | 220 | m_starting = false; | ||
535 | } | 221 | } | ||
536 | 222 | | |||
537 | void KWin::Script::sigException(const QScriptValue& exception) | 223 | QVariant KWin::Script::readConfig(const QString &key, const QVariant &defaultValue) | ||
224 | { | ||||
225 | return config().readEntry(key, defaultValue); | ||||
226 | } | ||||
227 | | ||||
228 | void KWin::Script::callDBus(const QString &service, const QString &path, const QString &interface, | ||||
229 | const QString &method, const QJSValue &arg1, const QJSValue &arg2, const QJSValue &arg3, | ||||
230 | const QJSValue &arg4, const QJSValue &arg5, const QJSValue &arg6, const QJSValue &arg7, | ||||
231 | const QJSValue &arg8, const QJSValue &arg9) | ||||
538 | { | 232 | { | ||
539 | QScriptValue ret = exception; | 233 | QJSValueList jsArguments; | ||
540 | if (ret.isError()) { | 234 | jsArguments.reserve(9); | ||
541 | qCDebug(KWIN_SCRIPTING) << "defaultscript encountered an error at [Line " << m_engine->uncaughtExceptionLineNumber() << "]"; | | |||
542 | qCDebug(KWIN_SCRIPTING) << "Message: " << ret.toString(); | | |||
543 | qCDebug(KWIN_SCRIPTING) << "-----------------"; | | |||
544 | 235 | | |||
545 | QScriptValueIterator iter(ret); | 236 | if (!arg1.isUndefined()) { | ||
546 | while (iter.hasNext()) { | 237 | jsArguments << arg1; | ||
547 | iter.next(); | | |||
548 | qCDebug(KWIN_SCRIPTING) << " " << iter.name() << ": " << iter.value().toString(); | | |||
549 | } | 238 | } | ||
239 | if (!arg2.isUndefined()) { | ||||
240 | jsArguments << arg2; | ||||
241 | } | ||||
242 | if (!arg3.isUndefined()) { | ||||
243 | jsArguments << arg3; | ||||
244 | } | ||||
245 | if (!arg4.isUndefined()) { | ||||
246 | jsArguments << arg4; | ||||
247 | } | ||||
248 | if (!arg5.isUndefined()) { | ||||
249 | jsArguments << arg5; | ||||
250 | } | ||||
251 | if (!arg6.isUndefined()) { | ||||
252 | jsArguments << arg6; | ||||
253 | } | ||||
254 | if (!arg7.isUndefined()) { | ||||
255 | jsArguments << arg7; | ||||
256 | } | ||||
257 | if (!arg8.isUndefined()) { | ||||
258 | jsArguments << arg8; | ||||
259 | } | ||||
260 | if (!arg9.isUndefined()) { | ||||
261 | jsArguments << arg9; | ||||
262 | } | ||||
263 | | ||||
264 | QJSValue callback; | ||||
265 | if (!jsArguments.isEmpty() && jsArguments.last().isCallable()) { | ||||
266 | callback = jsArguments.takeLast(); | ||||
550 | } | 267 | } | ||
551 | emit printError(exception.toString()); | 268 | | ||
552 | stop(); | 269 | QVariantList dbusArguments; | ||
270 | dbusArguments.reserve(jsArguments.count()); | ||||
271 | for (const QJSValue &jsArgument : jsArguments) { | ||||
272 | dbusArguments << jsArgument.toVariant(); | ||||
273 | } | ||||
274 | | ||||
275 | QDBusMessage message = QDBusMessage::createMethodCall(service, path, interface, method); | ||||
276 | message.setArguments(dbusArguments); | ||||
277 | | ||||
278 | const QDBusPendingCall call = QDBusConnection::sessionBus().asyncCall(message); | ||||
279 | if (callback.isUndefined()) { | ||||
280 | return; | ||||
281 | } | ||||
282 | | ||||
283 | QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); | ||||
284 | connect(watcher, &QDBusPendingCallWatcher::finished, this, | ||||
285 | [this, callback](QDBusPendingCallWatcher *self) { | ||||
286 | self->deleteLater(); | ||||
287 | | ||||
288 | if (self->isError()) { | ||||
289 | qCDebug(KWIN_SCRIPTING) << "Received D-Bus message is error"; | ||||
290 | return; | ||||
291 | } | ||||
292 | | ||||
293 | QJSValueList arguments; | ||||
294 | const QVariantList reply = self->reply().arguments(); | ||||
295 | for (const QVariant &variant : reply) { | ||||
296 | arguments << m_engine->toScriptValue(dbusToVariant(variant)); | ||||
297 | } | ||||
298 | | ||||
299 | QJSValue(callback).call(arguments); | ||||
300 | } | ||||
301 | ); | ||||
553 | } | 302 | } | ||
554 | 303 | | |||
555 | bool KWin::Script::registerTouchScreenCallback(int edge, QScriptValue callback) | 304 | bool KWin::Script::registerShortcut(const QString &objectName, const QString &text, const QString &keySequence, const QJSValue &callback) | ||
556 | { | 305 | { | ||
557 | if (m_touchScreenEdgeCallbacks.constFind(edge) != m_touchScreenEdgeCallbacks.constEnd()) { | 306 | if (!callback.isCallable()) { | ||
558 | return false; | 307 | return false; | ||
559 | } | 308 | } | ||
309 | | ||||
560 | QAction *action = new QAction(this); | 310 | QAction *action = new QAction(this); | ||
311 | action->setObjectName(objectName); | ||||
312 | action->setText(text); | ||||
313 | | ||||
314 | const QKeySequence shortcut = keySequence; | ||||
315 | KGlobalAccel::self()->setShortcut(action, { shortcut }); | ||||
316 | input()->registerShortcut(shortcut, action); | ||||
317 | | ||||
561 | connect(action, &QAction::triggered, this, | 318 | connect(action, &QAction::triggered, this, | ||
562 | [callback] { | 319 | [this, action, callback] { | ||
563 | QScriptValue invoke(callback); | 320 | QJSValue(callback).call({ m_engine->toScriptValue(action) }); | ||
564 | invoke.call(); | | |||
565 | } | 321 | } | ||
566 | ); | 322 | ); | ||
323 | | ||||
324 | return true; | ||||
325 | } | ||||
326 | | ||||
327 | bool KWin::Script::registerScreenEdge(int edge, const QJSValue &callback) | ||||
328 | { | ||||
329 | if (!callback.isCallable()) { | ||||
330 | return false; | ||||
331 | } | ||||
332 | | ||||
333 | QJSValueList &callbacks = m_screenEdgeCallbacks[edge]; | ||||
334 | if (callbacks.isEmpty()) { | ||||
335 | ScreenEdges::self()->reserve(static_cast<KWin::ElectricBorder>(edge), this, "slotBorderActivated"); | ||||
336 | } | ||||
337 | | ||||
338 | callbacks << callback; | ||||
339 | | ||||
340 | return true; | ||||
341 | } | ||||
342 | | ||||
343 | bool KWin::Script::unregisterScreenEdge(int edge) | ||||
344 | { | ||||
345 | auto it = m_screenEdgeCallbacks.find(edge); | ||||
346 | if (it == m_screenEdgeCallbacks.end()) { | ||||
347 | return false; | ||||
348 | } | ||||
349 | | ||||
350 | ScreenEdges::self()->unreserve(static_cast<KWin::ElectricBorder>(edge), this); | ||||
351 | m_screenEdgeCallbacks.erase(it); | ||||
352 | | ||||
353 | return true; | ||||
354 | } | ||||
355 | | ||||
356 | bool KWin::Script::registerTouchScreenEdge(int edge, const QJSValue &callback) | ||||
357 | { | ||||
358 | if (!callback.isCallable()) { | ||||
359 | return false; | ||||
360 | } | ||||
361 | if (m_touchScreenEdgeCallbacks.contains(edge)) { | ||||
362 | return false; | ||||
363 | } | ||||
364 | | ||||
365 | QAction *action = new QAction(this); | ||||
567 | ScreenEdges::self()->reserveTouch(KWin::ElectricBorder(edge), action); | 366 | ScreenEdges::self()->reserveTouch(KWin::ElectricBorder(edge), action); | ||
568 | m_touchScreenEdgeCallbacks.insert(edge, action); | 367 | m_touchScreenEdgeCallbacks.insert(edge, action); | ||
368 | | ||||
369 | connect(action, &QAction::triggered, this, | ||||
370 | [callback] { | ||||
371 | QJSValue(callback).call(); | ||||
372 | } | ||||
373 | ); | ||||
374 | | ||||
569 | return true; | 375 | return true; | ||
570 | } | 376 | } | ||
571 | 377 | | |||
572 | bool KWin::Script::unregisterTouchScreenCallback(int edge) | 378 | bool KWin::Script::unregisterTouchScreenEdge(int edge) | ||
573 | { | 379 | { | ||
574 | auto it = m_touchScreenEdgeCallbacks.find(edge); | 380 | auto it = m_touchScreenEdgeCallbacks.find(edge); | ||
575 | if (it == m_touchScreenEdgeCallbacks.end()) { | 381 | if (it == m_touchScreenEdgeCallbacks.end()) { | ||
576 | return false; | 382 | return false; | ||
577 | } | 383 | } | ||
384 | | ||||
578 | delete it.value(); | 385 | delete it.value(); | ||
579 | m_touchScreenEdgeCallbacks.erase(it); | 386 | m_touchScreenEdgeCallbacks.erase(it); | ||
387 | | ||||
580 | return true; | 388 | return true; | ||
581 | } | 389 | } | ||
582 | 390 | | |||
583 | KWin::ScriptUnloaderAgent::ScriptUnloaderAgent(KWin::Script *script) | 391 | void KWin::Script::registerUserActionsMenu(const QJSValue &callback) | ||
584 | : QScriptEngineAgent(script->engine()) | 392 | { | ||
585 | , m_script(script) | 393 | if (!callback.isCallable()) { | ||
394 | return; | ||||
395 | } | ||||
396 | m_userActionsMenuCallbacks.append(callback); | ||||
397 | } | ||||
398 | | ||||
399 | QList<QAction *> KWin::Script::actionsForUserActionMenu(KWin::AbstractClient *client, QMenu *parent) | ||||
400 | { | ||||
401 | QList<QAction *> actions; | ||||
402 | actions.reserve(m_userActionsMenuCallbacks.count()); | ||||
403 | | ||||
404 | for (QJSValue callback : m_userActionsMenuCallbacks) { | ||||
405 | QJSValue result = callback.call({ m_engine->toScriptValue(client) }); | ||||
406 | if (result.isError()) { | ||||
407 | continue; | ||||
408 | } | ||||
409 | if (!result.isObject()) { | ||||
410 | continue; | ||||
411 | } | ||||
412 | if (QAction *action = scriptValueToAction(result, parent)) { | ||||
413 | actions << action; | ||||
414 | } | ||||
415 | } | ||||
416 | | ||||
417 | return actions; | ||||
418 | } | ||||
419 | | ||||
420 | bool KWin::Script::slotBorderActivated(ElectricBorder border) | ||||
421 | { | ||||
422 | const QJSValueList callbacks = m_screenEdgeCallbacks.value(border); | ||||
423 | if (callbacks.isEmpty()) { | ||||
424 | return false; | ||||
425 | } | ||||
426 | std::for_each(callbacks.begin(), callbacks.end(), [](QJSValue callback) { | ||||
427 | callback.call(); | ||||
428 | }); | ||||
429 | return true; | ||||
430 | } | ||||
431 | | ||||
432 | QAction *KWin::Script::scriptValueToAction(const QJSValue &value, QMenu *parent) | ||||
433 | { | ||||
434 | const QString title = value.property(QStringLiteral("text")).toString(); | ||||
435 | if (title.isEmpty()) { | ||||
436 | return nullptr; | ||||
437 | } | ||||
438 | | ||||
439 | // Either a menu or a menu item. | ||||
440 | const QJSValue itemsValue = value.property(QStringLiteral("items")); | ||||
441 | if (!itemsValue.isUndefined()) { | ||||
442 | return createMenu(title, itemsValue, parent); | ||||
443 | } | ||||
444 | | ||||
445 | return createAction(title, value, parent); | ||||
446 | } | ||||
447 | | ||||
448 | QAction *KWin::Script::createAction(const QString &title, const QJSValue &item, QMenu *parent) | ||||
586 | { | 449 | { | ||
587 | script->engine()->setAgent(this); | 450 | const QJSValue callback = item.property(QStringLiteral("triggered")); | ||
451 | if (!callback.isCallable()) { | ||||
452 | return nullptr; | ||||
453 | } | ||||
454 | | ||||
455 | const bool checkable = item.property(QStringLiteral("checkable")).toBool(); | ||||
456 | const bool checked = item.property(QStringLiteral("checked")).toBool(); | ||||
457 | | ||||
458 | QAction *action = new QAction(title, parent); | ||||
459 | action->setCheckable(checkable); | ||||
460 | action->setChecked(checked); | ||||
461 | | ||||
462 | connect(action, &QAction::triggered, this, | ||||
463 | [this, action, callback] { | ||||
464 | QJSValue(callback).call({ m_engine->toScriptValue(action) }); | ||||
588 | } | 465 | } | ||
466 | ); | ||||
589 | 467 | | |||
590 | void KWin::ScriptUnloaderAgent::scriptUnload(qint64 id) | 468 | return action; | ||
469 | } | ||||
470 | | ||||
471 | QAction *KWin::Script::createMenu(const QString &title, const QJSValue &items, QMenu *parent) | ||||
591 | { | 472 | { | ||
592 | Q_UNUSED(id) | 473 | if (!items.isArray()) { | ||
593 | m_script->stop(); | 474 | return nullptr; | ||
475 | } | ||||
476 | | ||||
477 | const int length = items.property(QStringLiteral("length")).toInt(); | ||||
478 | if (!length) { | ||||
479 | return nullptr; | ||||
480 | } | ||||
481 | | ||||
482 | QMenu *menu = new QMenu(title, parent); | ||||
483 | for (int i = 0; i < length; ++i) { | ||||
484 | const QJSValue value = items.property(QString::number(i)); | ||||
485 | if (!value.isObject()) { | ||||
486 | continue; | ||||
487 | } | ||||
488 | if (QAction *action = scriptValueToAction(value, menu)) { | ||||
489 | menu->addAction(action); | ||||
490 | } | ||||
491 | } | ||||
492 | | ||||
493 | return menu->menuAction(); | ||||
594 | } | 494 | } | ||
595 | 495 | | |||
596 | KWin::DeclarativeScript::DeclarativeScript(int id, QString scriptName, QString pluginName, QObject* parent) | 496 | KWin::DeclarativeScript::DeclarativeScript(int id, QString scriptName, QString pluginName, QObject* parent) | ||
597 | : AbstractScript(id, scriptName, pluginName, parent) | 497 | : AbstractScript(id, scriptName, pluginName, parent) | ||
598 | , m_context(new QQmlContext(Scripting::self()->declarativeScriptSharedContext(), this)) | 498 | , m_context(new QQmlContext(Scripting::self()->declarativeScriptSharedContext(), this)) | ||
599 | , m_component(new QQmlComponent(Scripting::self()->qmlEngine(), this)) | 499 | , m_component(new QQmlComponent(Scripting::self()->qmlEngine(), this)) | ||
600 | { | 500 | { | ||
601 | m_context->setContextProperty(QStringLiteral("KWin"), new JSEngineGlobalMethodsWrapper(this)); | 501 | m_context->setContextProperty(QStringLiteral("KWin"), new JSEngineGlobalMethodsWrapper(this)); | ||
▲ Show 20 Lines • Show All 283 Lines • ▼ Show 20 Line(s) | |||||
885 | { | 785 | { | ||
886 | QDBusConnection::sessionBus().unregisterObject(QStringLiteral("/Scripting")); | 786 | QDBusConnection::sessionBus().unregisterObject(QStringLiteral("/Scripting")); | ||
887 | s_self = nullptr; | 787 | s_self = nullptr; | ||
888 | } | 788 | } | ||
889 | 789 | | |||
890 | QList< QAction * > KWin::Scripting::actionsForUserActionMenu(KWin::AbstractClient *c, QMenu *parent) | 790 | QList< QAction * > KWin::Scripting::actionsForUserActionMenu(KWin::AbstractClient *c, QMenu *parent) | ||
891 | { | 791 | { | ||
892 | QList<QAction*> actions; | 792 | QList<QAction*> actions; | ||
893 | foreach (AbstractScript *script, scripts) { | 793 | for (AbstractScript *s : scripts) { | ||
794 | // TODO: Allow declarative scripts to add their own user actions. | ||||
795 | if (Script *script = qobject_cast<Script *>(s)) { | ||||
894 | actions << script->actionsForUserActionMenu(c, parent); | 796 | actions << script->actionsForUserActionMenu(c, parent); | ||
895 | } | 797 | } | ||
798 | } | ||||
896 | return actions; | 799 | return actions; | ||
897 | } | 800 | } | ||
898 | 801 | |