2
* Copyright 2009 Aaron Seigo <aseigo@kde.org>
4
* This program is free software; you can redistribute it and/or modify
5
* it under the terms of the GNU Library General Public License as
6
* published by the Free Software Foundation; either version 2, or
7
* (at your option) any later version.
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU General Public License for more details
14
* You should have received a copy of the GNU Library General Public
15
* License along with this program; if not, write to the
16
* Free Software Foundation, Inc.,
17
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
#include "scriptengine.h"
22
#include <QApplication>
23
#include <QDesktopWidget>
27
#include <QScriptValueIterator>
30
#include <kdeversion.h>
31
#include <KGlobalSettings>
32
#include <KMimeTypeTrader>
33
#include <KServiceTypeTrader>
35
#include <KStandardDirs>
38
#include <kemailsettings.h> // no camelcase include
40
#include <Plasma/Applet>
41
#include <Plasma/Containment>
42
#include <Plasma/Corona>
43
#include <Plasma/Package>
44
#include <Plasma/Wallpaper>
46
#include "appinterface.h"
47
#include "containment.h"
49
#include "layouttemplatepackagestructure.h"
52
QScriptValue constructQRectFClass(QScriptEngine *engine);
54
namespace WorkspaceScripting
57
ScriptEngine::ScriptEngine(Plasma::Corona *corona, QObject *parent)
58
: QScriptEngine(parent),
62
AppInterface *interface = new AppInterface(this);
63
connect(interface, SIGNAL(print(QString)), this, SIGNAL(print(QString)));
64
m_scriptSelf = newQObject(interface, QScriptEngine::QtOwnership,
65
QScriptEngine::ExcludeSuperClassProperties |
66
QScriptEngine::ExcludeSuperClassMethods);
68
connect(this, SIGNAL(signalHandlerException(QScriptValue)), this, SLOT(exception(QScriptValue)));
72
ScriptEngine::~ScriptEngine()
76
QScriptValue ScriptEngine::activityById(QScriptContext *context, QScriptEngine *engine)
78
if (context->argumentCount() == 0) {
79
return context->throwError(i18n("activityById requires an id"));
82
const uint id = context->argument(0).toInt32();
83
ScriptEngine *env = envFor(engine);
84
foreach (Plasma::Containment *c, env->m_corona->containments()) {
85
if (c->id() == id && !isPanel(c)) {
90
return engine->undefinedValue();
93
QScriptValue ScriptEngine::activityForScreen(QScriptContext *context, QScriptEngine *engine)
95
if (context->argumentCount() == 0) {
96
return context->throwError(i18n("activityForScreen requires a screen id"));
99
const uint screen = context->argument(0).toInt32();
100
const uint desktop = context->argumentCount() > 1 ? context->argument(1).toInt32() : -1;
101
ScriptEngine *env = envFor(engine);
102
return env->wrap(env->m_corona->containmentForScreen(screen, desktop));
105
QScriptValue ScriptEngine::newActivity(QScriptContext *context, QScriptEngine *engine)
107
return createContainment("desktop", "desktop", context, engine);
110
QScriptValue ScriptEngine::newPanel(QScriptContext *context, QScriptEngine *engine)
112
return createContainment("panel", "panel", context, engine);
115
QScriptValue ScriptEngine::createContainment(const QString &type, const QString &defaultPlugin,
116
QScriptContext *context, QScriptEngine *engine)
118
QString plugin = context->argumentCount() > 0 ? context->argument(0).toString() :
122
const KPluginInfo::List list = Plasma::Containment::listContainmentsOfType(type);
123
foreach (const KPluginInfo &info, list) {
124
if (info.pluginName() == plugin) {
131
return context->throwError(i18n("Could not find a plugin for %1 named %2.", type, plugin));
135
ScriptEngine *env = envFor(engine);
136
Plasma::Containment *c = env->m_corona->addContainment(plugin);
138
if (type == "panel") {
140
c->setScreen(env->defaultPanelScreen());
141
c->setLocation(Plasma::TopEdge);
143
c->updateConstraints(Plasma::AllConstraints | Plasma::StartupCompletedConstraint);
144
c->flushPendingConstraintsEvents();
145
emit env->createPendingPanelViews();
151
QScriptValue ScriptEngine::wrap(Plasma::Applet *w)
153
Widget *wrapper = new Widget(w);
154
QScriptValue v = newQObject(wrapper, QScriptEngine::ScriptOwnership,
155
QScriptEngine::ExcludeSuperClassProperties |
156
QScriptEngine::ExcludeSuperClassMethods);
160
QScriptValue ScriptEngine::wrap(Plasma::Containment *c)
162
Containment *wrapper = new Containment(c);
163
return wrap(wrapper);
166
QScriptValue ScriptEngine::wrap(Containment *c)
168
QScriptValue v = newQObject(c, QScriptEngine::ScriptOwnership,
169
QScriptEngine::ExcludeSuperClassProperties |
170
QScriptEngine::ExcludeSuperClassMethods);
171
v.setProperty("widgetById", newFunction(Containment::widgetById));
172
v.setProperty("addWidget", newFunction(Containment::addWidget));
173
v.setProperty("widgets", newFunction(Containment::widgets));
178
int ScriptEngine::defaultPanelScreen() const
180
return qApp ? qApp->desktop()->primaryScreen() : 0;
183
ScriptEngine *ScriptEngine::envFor(QScriptEngine *engine)
185
QObject *object = engine->globalObject().toQObject();
188
AppInterface *interface = qobject_cast<AppInterface *>(object);
191
ScriptEngine *env = qobject_cast<ScriptEngine *>(interface->parent());
197
QScriptValue ScriptEngine::panelById(QScriptContext *context, QScriptEngine *engine)
199
if (context->argumentCount() == 0) {
200
return context->throwError(i18n("activityById requires an id"));
203
const uint id = context->argument(0).toInt32();
204
ScriptEngine *env = envFor(engine);
205
foreach (Plasma::Containment *c, env->m_corona->containments()) {
206
if (c->id() == id && isPanel(c)) {
211
return engine->undefinedValue();
214
QScriptValue ScriptEngine::panels(QScriptContext *context, QScriptEngine *engine)
218
QScriptValue panels = engine->newArray();
219
ScriptEngine *env = envFor(engine);
222
foreach (Plasma::Containment *c, env->m_corona->containments()) {
224
panels.setProperty(count, env->wrap(c));
229
panels.setProperty("length", count);
233
QScriptValue ScriptEngine::fileExists(QScriptContext *context, QScriptEngine *engine)
236
if (context->argumentCount() == 0) {
240
const QString path = context->argument(0).toString();
241
if (path.isEmpty()) {
245
QFile f(KShell::tildeExpand(path));
249
QScriptValue ScriptEngine::loadTemplate(QScriptContext *context, QScriptEngine *engine)
252
if (context->argumentCount() == 0) {
253
kDebug() << "no arguments";
257
const QString layout = context->argument(0).toString();
258
if (layout.isEmpty() || layout.contains("'")) {
259
kDebug() << "layout is empty";
263
const QString constraint = QString("[X-Plasma-Shell] == '%1' and [X-KDE-PluginInfo-Name] == '%2'")
264
.arg(KGlobal::mainComponent().componentName(),layout);
265
KService::List offers = KServiceTypeTrader::self()->query("Plasma/LayoutTemplate", constraint);
267
if (offers.isEmpty()) {
268
kDebug() << "offers fail" << constraint;
272
Plasma::PackageStructure::Ptr structure(new LayoutTemplatePackageStructure);
273
KPluginInfo info(offers.first());
274
const QString path = KStandardDirs::locate("data", structure->defaultPackageRoot() + '/' + info.pluginName() + '/');
275
if (path.isEmpty()) {
276
kDebug() << "script path is empty";
280
Plasma::Package package(path, structure);
281
const QString scriptFile = package.filePath("mainscript");
282
if (scriptFile.isEmpty()) {
283
kDebug() << "scriptfile is empty";
287
QFile file(scriptFile);
288
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
289
kWarning() << i18n("Unable to load script file: %1", path);
293
QString script = file.readAll();
294
if (script.isEmpty()) {
295
kDebug() << "script is empty";
299
ScriptEngine *env = envFor(engine);
300
env->globalObject().setProperty("templateName", env->newVariant(info.name()), QScriptValue::ReadOnly | QScriptValue::Undeletable);
301
env->globalObject().setProperty("templateComment", env->newVariant(info.comment()), QScriptValue::ReadOnly | QScriptValue::Undeletable);
303
QScriptValue rv = env->newObject();
304
QScriptContext *ctx = env->pushContext();
305
ctx->setThisObject(rv);
307
env->evaluateScript(script, path);
313
QScriptValue ScriptEngine::applicationExists(QScriptContext *context, QScriptEngine *engine)
316
if (context->argumentCount() == 0) {
320
const QString application = context->argument(0).toString();
321
if (application.isEmpty()) {
325
// first, check for it in $PATH
326
if (!KStandardDirs::findExe(application).isEmpty()) {
330
if (KService::serviceByStorageId(application)) {
334
if (application.contains("'")) {
335
// apostrophes just screw up the trader lookups below, so check for it
339
// next, consult ksycoca for an app by that name
340
if (!KServiceTypeTrader::self()->query("Application", QString("Name =~ '%1'").arg(application)).isEmpty()) {
344
// next, consult ksycoca for an app by that generic name
345
if (!KServiceTypeTrader::self()->query("Application", QString("GenericName =~ '%1'").arg(application)).isEmpty()) {
352
QScriptValue ScriptEngine::defaultApplication(QScriptContext *context, QScriptEngine *engine)
355
if (context->argumentCount() == 0) {
359
const QString application = context->argument(0).toString();
360
if (application.isEmpty()) {
364
const bool storageId = context->argumentCount() < 2 ? false : context->argument(1).toBool();
366
// FIXME: there are some pretty horrible hacks below, in the sense that they assume a very
367
// specific implementation system. there is much room for improvement here. see
368
// kdebase-runtime/kcontrol/componentchooser/ for all the gory details ;)
369
if (application.compare("mailer", Qt::CaseInsensitive) == 0) {
370
KEMailSettings settings;
372
// in KToolInvocation, the default is kmail; but let's be friendlier :)
373
QString command = settings.getSetting(KEMailSettings::ClientProgram);
374
if (command.isEmpty()) {
375
if (KService::Ptr kontact = KService::serviceByStorageId("kontact")) {
376
return storageId ? kontact->storageId() : kontact->exec();
377
} else if (KService::Ptr kmail = KService::serviceByStorageId("kmail")) {
378
return storageId ? kmail->storageId() : kmail->exec();
382
if (!command.isEmpty()) {
383
if (settings.getSetting(KEMailSettings::ClientTerminal) == "true") {
384
KConfigGroup confGroup(KGlobal::config(), "General");
385
const QString preferredTerminal = confGroup.readPathEntry("TerminalApplication",
386
QString::fromLatin1("konsole"));
387
command = preferredTerminal + QString::fromLatin1(" -e ") + command;
392
} else if (application.compare("browser", Qt::CaseInsensitive) == 0) {
393
KConfigGroup config(KGlobal::config(), "General");
394
QString browserApp = config.readPathEntry("BrowserApplication", QString());
395
if (browserApp.isEmpty()) {
396
const KService::Ptr htmlApp = KMimeTypeTrader::self()->preferredService(QLatin1String("text/html"));
398
browserApp = storageId ? htmlApp->storageId() : htmlApp->exec();
400
} else if (browserApp.startsWith('!')) {
401
browserApp = browserApp.mid(1);
405
} else if (application.compare("terminal", Qt::CaseInsensitive) == 0) {
406
KConfigGroup confGroup(KGlobal::config(), "General");
407
return confGroup.readPathEntry("TerminalApplication", QString::fromLatin1("konsole"));
408
} else if (application.compare("filemanager", Qt::CaseInsensitive) == 0) {
409
KService::Ptr service = KMimeTypeTrader::self()->preferredService("inode/directory");
411
return storageId ? service->storageId() : service->exec();
413
} else if (application.compare("windowmanager", Qt::CaseInsensitive) == 0) {
414
KConfig cfg("ksmserverrc", KConfig::NoGlobals);
415
KConfigGroup confGroup(&cfg, "General");
416
return confGroup.readEntry("windowManager", QString::fromLatin1("konsole"));
417
} else if (KService::Ptr service = KMimeTypeTrader::self()->preferredService(application)) {
418
return storageId ? service->storageId() : service->exec();
420
// try the files in share/apps/kcm_componentchooser/
421
const QStringList services = KGlobal::dirs()->findAllResources("data","kcm_componentchooser/*.desktop", KStandardDirs::NoDuplicates);
422
//kDebug() << "ok, trying in" << services.count();
423
foreach (const QString &service, services) {
424
KConfig config(service, KConfig::SimpleConfig);
425
KConfigGroup cg = config.group(QByteArray());
426
const QString type = cg.readEntry("valueName", QString());
427
//kDebug() << " checking" << service << type << application;
428
if (type.compare(application, Qt::CaseInsensitive) == 0) {
429
KConfig store(cg.readPathEntry("storeInFile", "null"));
430
KConfigGroup storeCg(&store, cg.readEntry("valueSection", QString()));
431
const QString exec = storeCg.readPathEntry(cg.readEntry("valueName", "kcm_componenchooser_null"),
432
cg.readEntry("defaultImplementation", QString()));
433
if (!exec.isEmpty()) {
445
QScriptValue ScriptEngine::applicationPath(QScriptContext *context, QScriptEngine *engine)
448
if (context->argumentCount() == 0) {
452
const QString application = context->argument(0).toString();
453
if (application.isEmpty()) {
457
// first, check for it in $PATH
458
const QString path = KStandardDirs::findExe(application);
459
if (!path.isEmpty()) {
463
if (KService::Ptr service = KService::serviceByStorageId(application)) {
464
return KStandardDirs::locate("apps", service->entryPath());
467
if (application.contains("'")) {
468
// apostrophes just screw up the trader lookups below, so check for it
472
// next, consult ksycoca for an app by that name
473
KService::List offers = KServiceTypeTrader::self()->query("Application", QString("Name =~ '%1'").arg(application));
474
if (offers.isEmpty()) {
475
// next, consult ksycoca for an app by that generic name
476
offers = KServiceTypeTrader::self()->query("Application", QString("GenericName =~ '%1'").arg(application));
479
if (!offers.isEmpty()) {
480
KService::Ptr offer = offers.first();
481
return KStandardDirs::locate("apps", offer->entryPath());
487
QScriptValue ScriptEngine::userDataPath(QScriptContext *context, QScriptEngine *engine)
490
if (context->argumentCount() == 0) {
491
return QDir::homePath();
494
const QString type = context->argument(0).toString();
495
if (type.isEmpty()) {
496
return QDir::homePath();
499
if (context->argumentCount() > 1) {
500
const QString filename = context->argument(1).toString();
501
return KStandardDirs::locateLocal(type.toLatin1(), filename);
504
if (type.compare("desktop", Qt::CaseInsensitive) == 0) {
505
return KGlobalSettings::desktopPath();
506
} else if (type.compare("autostart", Qt::CaseInsensitive) == 0) {
507
return KGlobalSettings::autostartPath();
508
} else if (type.compare("documents", Qt::CaseInsensitive) == 0) {
509
return KGlobalSettings::documentPath();
510
} else if (type.compare("music", Qt::CaseInsensitive) == 0) {
511
return KGlobalSettings::musicPath();
512
} else if (type.compare("video", Qt::CaseInsensitive) == 0) {
513
return KGlobalSettings::videosPath();
514
} else if (type.compare("downloads", Qt::CaseInsensitive) == 0) {
515
return KGlobalSettings::downloadPath();
516
} else if (type.compare("pictures", Qt::CaseInsensitive) == 0) {
517
return KGlobalSettings::picturesPath();
523
QScriptValue ScriptEngine::knownWallpaperPlugins(QScriptContext *context, QScriptEngine *engine)
528
if (context->argumentCount() > 0) {
529
formFactor = context->argument(0).toString();
533
if (!formFactor.isEmpty()) {
534
constraint.append("[X-Plasma-FormFactors] ~~ '").append(formFactor).append("'");
537
KService::List services = KServiceTypeTrader::self()->query("Plasma/Wallpaper", constraint);
538
QScriptValue rv = engine->newArray(services.size());
539
foreach (const KService::Ptr service, services) {
540
QList<KServiceAction> modeActions = service->actions();
541
QScriptValue modes = engine->newArray(modeActions.size());
543
foreach (const KServiceAction &action, modeActions) {
544
modes.setProperty(i++, action.name());
547
rv.setProperty(service->name(), modes);
553
void ScriptEngine::setupEngine()
555
QScriptValue v = globalObject();
556
QScriptValueIterator it(v);
557
while (it.hasNext()) {
559
// we provide our own print implementation, but we want the rest
560
if (it.name() != "print") {
561
m_scriptSelf.setProperty(it.name(), it.value());
565
m_scriptSelf.setProperty("QRectF", constructQRectFClass(this));
566
m_scriptSelf.setProperty("Activity", newFunction(ScriptEngine::newActivity));
567
m_scriptSelf.setProperty("Panel", newFunction(ScriptEngine::newPanel));
568
m_scriptSelf.setProperty("activities", newFunction(ScriptEngine::activities));
569
m_scriptSelf.setProperty("activityById", newFunction(ScriptEngine::activityById));
570
m_scriptSelf.setProperty("activityForScreen", newFunction(ScriptEngine::activityForScreen));
571
m_scriptSelf.setProperty("panelById", newFunction(ScriptEngine::panelById));
572
m_scriptSelf.setProperty("panels", newFunction(ScriptEngine::panels));
573
m_scriptSelf.setProperty("fileExists", newFunction(ScriptEngine::fileExists));
574
m_scriptSelf.setProperty("loadTemplate", newFunction(ScriptEngine::loadTemplate));
575
m_scriptSelf.setProperty("applicationExists", newFunction(ScriptEngine::applicationExists));
576
m_scriptSelf.setProperty("defaultApplication", newFunction(ScriptEngine::defaultApplication));
577
m_scriptSelf.setProperty("userDataPath", newFunction(ScriptEngine::userDataPath));
578
m_scriptSelf.setProperty("applicationPath", newFunction(ScriptEngine::applicationPath));
579
m_scriptSelf.setProperty("knownWallpaperPlugins", newFunction(ScriptEngine::knownWallpaperPlugins));
581
setGlobalObject(m_scriptSelf);
584
bool ScriptEngine::isPanel(const Plasma::Containment *c)
590
return c->containmentType() == Plasma::Containment::PanelContainment ||
591
c->containmentType() == Plasma::Containment::CustomPanelContainment;
594
QScriptValue ScriptEngine::activities(QScriptContext *context, QScriptEngine *engine)
598
QScriptValue containments = engine->newArray();
599
ScriptEngine *env = envFor(engine);
602
foreach (Plasma::Containment *c, env->corona()->containments()) {
604
containments.setProperty(count, env->wrap(c));
609
containments.setProperty("length", count);
613
Plasma::Corona *ScriptEngine::corona() const
618
bool ScriptEngine::evaluateScript(const QString &script, const QString &path)
620
//kDebug() << "evaluating" << m_editor->toPlainText();
621
evaluate(script, path);
622
if (hasUncaughtException()) {
623
//kDebug() << "catch the exception!";
624
QString error = i18n("Error: %1 at line %2\n\nBacktrace:\n%3",
625
uncaughtException().toString(),
626
QString::number(uncaughtExceptionLineNumber()),
627
uncaughtExceptionBacktrace().join("\n "));
628
emit printError(error);
635
void ScriptEngine::exception(const QScriptValue &value)
637
//kDebug() << "exception caught!" << value.toVariant();
638
emit printError(value.toVariant().toString());
641
QStringList ScriptEngine::pendingUpdateScripts()
643
const QString appName = KGlobal::activeComponent().aboutData()->appName();
644
QStringList scripts = KGlobal::dirs()->findAllResources("data", appName + "/updates/*.js");
645
QStringList scriptPaths;
647
if (scripts.isEmpty()) {
648
//kDebug() << "no update scripts";
652
KConfigGroup cg(KGlobal::config(), "Updates");
653
QStringList performed = cg.readEntry("performed", QStringList());
654
const QString localDir = KGlobal::dirs()->localkdedir();
655
const QString localXdgDir = KGlobal::dirs()->localxdgdatadir();
657
foreach (const QString &script, scripts) {
658
if (performed.contains(script)) {
662
if (script.startsWith(localDir) || script.startsWith(localXdgDir)) {
663
kDebug() << "skipping user local script: " << script;
667
scriptPaths.append(script);
668
performed.append(script);
671
cg.writeEntry("performed", performed);
672
KGlobal::config()->sync();
676
QStringList ScriptEngine::defaultLayoutScripts()
678
const QString appName = KGlobal::activeComponent().aboutData()->appName();
679
QStringList scripts = KGlobal::dirs()->findAllResources("data", appName + "/init/*.js");
682
QStringList scriptPaths;
684
if (scripts.isEmpty()) {
685
//kDebug() << "no javascript based layouts";
689
const QString localDir = KGlobal::dirs()->localkdedir();
690
const QString localXdgDir = KGlobal::dirs()->localxdgdatadir();
692
QSet<QString> scriptNames;
693
foreach (const QString &script, scripts) {
694
if (script.startsWith(localDir) || script.startsWith(localXdgDir)) {
695
kDebug() << "skipping user local script: " << script;
700
QString filename = f.fileName();
701
if (!scriptNames.contains(filename)) {
702
scriptNames.insert(filename);
703
scriptPaths.append(script);
712
#include "scriptengine.moc"