/* * Copyright 2007-2008 Richard J. Moore * Copyright 2009 Aaron J. Seigo * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License version 2 as * published by the Free Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "scriptenv.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef USEGUI #include "simplebindings/filedialogproxy.h" #endif #include "javascriptaddonpackagestructure.h" Q_DECLARE_METATYPE(ScriptEnv*) ScriptEnv::ScriptEnv(QObject *parent, QScriptEngine *engine) : QObject(parent), m_allowedUrls(NoUrls), m_engine(engine) { QScriptValue global = m_engine->globalObject(); // Add utility functions global.setProperty("print", m_engine->newFunction(ScriptEnv::print)); global.setProperty("debug", m_engine->newFunction(ScriptEnv::debug)); global.setProperty("listAddons", m_engine->newFunction(ScriptEnv::listAddons)); global.setProperty("loadAddon", m_engine->newFunction(ScriptEnv::loadAddon)); // Add an accessor so we can find the scriptenv given only the engine. The // property is hidden from scripts. global.setProperty("__plasma_scriptenv", m_engine->newQObject(this), QScriptValue::ReadOnly|QScriptValue::Undeletable|QScriptValue::SkipInEnumeration); connect(m_engine, SIGNAL(signalHandlerException(QScriptValue)), this, SLOT(signalException())); } ScriptEnv::~ScriptEnv() { } QScriptEngine *ScriptEnv::engine() const { return m_engine; } ScriptEnv *ScriptEnv::findScriptEnv(QScriptEngine *engine) { QScriptValue global = engine->globalObject(); return qscriptvalue_cast(global.property("__plasma_scriptenv")); } void ScriptEnv::signalException() { checkForErrors(false); } void ScriptEnv::registerEnums(QScriptValue &scriptValue, const QMetaObject &meta) { //manually create enum values. ugh QScriptEngine *engine = scriptValue.engine(); for (int i = 0; i < meta.enumeratorCount(); ++i) { QMetaEnum e = meta.enumerator(i); //kDebug() << e.name(); for (int i=0; i < e.keyCount(); ++i) { //kDebug() << e.key(i) << e.value(i); scriptValue.setProperty(e.key(i), QScriptValue(engine, e.value(i))); } } } bool ScriptEnv::include(const QString &path) { QFile file(path); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { kWarning() << i18n("Unable to load script file: %1", path); return false; } QString script = file.readAll(); //kDebug() << "Script says" << script; // change the context to the parent context so that the include is actually // executed in the same context as the caller; seems to be what javascript // coders expect :) QScriptContext *ctx = m_engine->currentContext(); if (ctx && ctx->parentContext()) { ctx->setActivationObject(ctx->parentContext()->activationObject()); ctx->setThisObject(ctx->parentContext()->thisObject()); } m_engine->evaluate(script, path); return !checkForErrors(true); } bool ScriptEnv::checkForErrors(bool fatal) { if (m_engine->hasUncaughtException()) { emit reportError(this, fatal); if (!fatal) { m_engine->clearExceptions(); } return true; } return false; } QScriptValue ScriptEnv::runApplication(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() == 0) { return false; } KUrl::List urls; if (context->argumentCount() > 1) { urls = qscriptvalue_cast(context->argument(1)); } const QString app = context->argument(0).toString(); const QString exec = KGlobal::dirs()->findExe(app); if (!exec.isEmpty()) { return KRun::run(exec, urls, 0); } KService::Ptr service = KService::serviceByStorageId(app); if (service) { return KRun::run(*service, urls, 0); } return false; } QScriptValue ScriptEnv::runCommand(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine); if (context->argumentCount() == 0) { return false; } const QString exec = KGlobal::dirs()->findExe(context->argument(0).toString()); if (!exec.isEmpty()) { QString args; if (context->argumentCount() > 1) { const QStringList argList = qscriptvalue_cast(context->argument(1)); if (!argList.isEmpty()) { args = ' ' + KShell::joinArgs(argList); } } return KRun::runCommand(exec + args, 0); } return false; } QScriptValue ScriptEnv::openUrl(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() == 0) { return false; } QScriptValue v = context->argument(0); KUrl url = v.isString() ? KUrl(v.toString()) : qscriptvalue_cast(v); if (url.isValid()) { return KRun::runUrl(url, KMimeType::findByUrl(url)->name(), 0); } return false; } // TODO these should throw an exception QScriptValue ScriptEnv::getUrl(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() == 0) { return engine->undefinedValue(); } QScriptValue v = context->argument(0); KUrl url = v.isString() ? KUrl(v.toString()) : qscriptvalue_cast(v); if (!url.isValid()) { return engine->undefinedValue(); } ScriptEnv *env = ScriptEnv::findScriptEnv(engine); if (!env) { kDebug() << "findScriptEnv failed"; return engine->undefinedValue(); } if (url.isLocalFile()) { if (!(env->m_allowedUrls & LocalUrls)) { return engine->undefinedValue(); } } else if (!(env->m_allowedUrls & NetworkUrls) && !((env->m_allowedUrls & HttpUrls) && (url.protocol() == "http" || url.protocol() == "https"))) { return engine->undefinedValue(); } KIO::Job *job = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo); return engine->newQObject(job); } void ScriptEnv::registerGetUrl(QScriptValue &obj) { QScriptValue get = obj.property("getUrl"); if (!get.isValid()) { obj.setProperty("getUrl", m_engine->newFunction(ScriptEnv::getUrl)); } } bool ScriptEnv::importBuiltinExtension(const QString &extension, QScriptValue &obj) { kDebug() << extension; if ("filedialog" == extension) { #ifdef USEGUI FileDialogProxy::registerWithRuntime(m_engine); return true; #endif } else if ("launchapp" == extension) { obj.setProperty("runApplication", m_engine->newFunction(ScriptEnv::runApplication)); obj.setProperty("runCommand", m_engine->newFunction(ScriptEnv::runCommand)); obj.setProperty("openUrl", m_engine->newFunction(ScriptEnv::openUrl)); return true; } else if ("http" == extension) { m_allowedUrls |= HttpUrls; registerGetUrl(obj); return true; } else if ("networkio" == extension) { m_allowedUrls |= HttpUrls | NetworkUrls; registerGetUrl(obj); return true; } else if ("localio" == extension) { m_allowedUrls |= LocalUrls; registerGetUrl(obj); return true; } return false; } bool ScriptEnv::importExtensions(const KPluginInfo &info, QScriptValue &obj, Authorization &auth) { QStringList requiredExtensions = info.service()->property("X-Plasma-RequiredExtensions", QVariant::StringList).toStringList(); kDebug() << "required extensions are" << requiredExtensions; foreach (const QString &ext, requiredExtensions) { QString extension = ext.toLower(); if (m_extensions.contains(extension)) { continue; } if (!auth.authorizeRequiredExtension(extension)) { return false; } if (!importBuiltinExtension(extension, obj)) { if (auth.authorizeExternalExtensions()) { m_engine->importExtension(extension); } } if (checkForErrors(true)) { return false; } else { m_extensions << extension; } } QStringList optionalExtensions = info.service()->property("X-Plasma-OptionalExtensions", QVariant::StringList).toStringList(); kDebug() << "optional extensions are" << optionalExtensions; foreach (const QString &ext, optionalExtensions) { QString extension = ext.toLower(); if (m_extensions.contains(extension)) { continue; } if (!auth.authorizeOptionalExtension(extension)) { continue; } if (!importBuiltinExtension(extension, obj)) { if (auth.authorizeExternalExtensions()) { m_engine->importExtension(extension); } } if (!checkForErrors(false)) { m_extensions << extension; } } return true; } QSet ScriptEnv::loadedExtensions() const { return m_extensions; } QScriptValue ScriptEnv::debug(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() != 1) { return context->throwError(i18n("debug takes one argument")); } kDebug() << context->argument(0).toString(); return engine->undefinedValue(); } QScriptValue ScriptEnv::print(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() != 1) { return context->throwError(i18n("print() takes one argument")); } std::cout << context->argument(0).toString().toLocal8Bit().constData() << std::endl; return engine->undefinedValue(); } QScriptValue ScriptEnv::listAddons(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() < 1) { return context->throwError(i18n("listAddons takes one argument: addon type")); } QStringList addons; const QString type = context->argument(0).toString(); if (type.isEmpty()) { return qScriptValueFromValue(engine, addons); } const QString constraint = QString("[X-KDE-PluginInfo-Category] == '%1'").arg(type); KService::List offers = KServiceTypeTrader::self()->query("Plasma/JavascriptAddon", constraint); foreach (KService::Ptr offer, offers) { KPluginInfo info(offer); addons << info.pluginName(); } return qScriptValueFromValue(engine, addons); } QScriptValue ScriptEnv::loadAddon(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() < 2) { return context->throwError(i18n("listAddons takes two arguments: addon type and addon name to load")); } const QString type = context->argument(0).toString(); const QString plugin = context->argument(1).toString(); if (type.isEmpty() || plugin.isEmpty()) { return false; } const QString constraint = QString("[X-KDE-PluginInfo-Category] == '%1' and [X-KDE-PluginInfo-Name] == '%2'") .arg(type, plugin); KService::List offers = KServiceTypeTrader::self()->query("Plasma/JavascriptAddon", constraint); if (offers.isEmpty()) { return false; } Plasma::PackageStructure::Ptr structure(new JavascriptAddonPackageStructure); const QString subPath = structure->defaultPackageRoot() + '/' + plugin + '/'; const QString path = KStandardDirs::locate("data", subPath); Plasma::Package package(path, structure); //FIXME include() will not work from within addons; needs a solution QFile file(package.filePath("mainscript")); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { kError() << "failed to open script file" << path; return false; } QTextStream buffer(&file); QString code(buffer.readAll()); QScriptContext *innerContext = engine->pushContext(); innerContext->activationObject().setProperty("registerAddon", engine->newFunction(ScriptEnv::registerAddon)); engine->evaluate(code, file.fileName()); engine->popContext(); return engine->undefinedValue(); } QScriptValue ScriptEnv::registerAddon(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() > 0) { QScriptValue func = context->argument(0); if (func.isFunction()) { QScriptValue obj = func.construct(); QScriptValueList args; args << obj; ScriptEnv *env = ScriptEnv::findScriptEnv(engine); if (env) { env->callEventListeners("addonCreated", args); } } } return engine->undefinedValue(); } void ScriptEnv::callFunction(QScriptValue &func, const QScriptValueList &args, const QScriptValue &activator) { if (!func.isFunction()) { return; } QScriptContext *ctx = m_engine->pushContext(); ctx->setActivationObject(activator); func.call(activator, args); m_engine->popContext(); if (m_engine->hasUncaughtException()) { emit reportError(this, false); m_engine->clearExceptions(); } } bool ScriptEnv::hasEventListeners(const QString &event) const { return m_eventListeners.contains(event); } bool ScriptEnv::callEventListeners(const QString &event, const QScriptValueList &args) { if (!m_eventListeners.contains(event)) { return false; } QScriptValueList funcs = m_eventListeners.value(event); QMutableListIterator it(funcs); while (it.hasNext()) { callFunction(it.next(), args); } return true; } void ScriptEnv::addEventListener(const QString &event, const QScriptValue &func) { if (func.isFunction()) { m_eventListeners[event.toLower()].append(func); } } void ScriptEnv::removeEventListener(const QString &event, const QScriptValue &func) { if (func.isFunction()) { QScriptValueList funcs = m_eventListeners.value("mousepress"); QMutableListIterator it(funcs);//m_eventListeners.value("mousepress")); while (it.hasNext()) { if (it.next().equals(func)) { it.remove(); } } if (funcs.isEmpty()) { m_eventListeners.remove(event.toLower()); } else { m_eventListeners.insert(event.toLower(), funcs); } } } #ifndef USEGUI #include "scriptenv.moc" #include "javascriptaddonpackagestructure.moc" #endif