Scripting meets D-Bus

Each KWin Script is also exported as a D-Bus object and can be
stopped (destroyed) and started through D-Bus. Output and errors
are emitted as D-Bus signals. That allows external applications
(e.g. Plasma desktop scripting console) to load a script and print
out the output.

The general interface is exported as /Scripting and allows to load
a new script by file. The script is not directly executed but only
loaded. To execute it the run method on the script object has to be
invoked.
This commit is contained in:
Martin Gräßlin 2011-12-23 11:52:06 +01:00
parent 0420ecd649
commit ab253cd178
2 changed files with 99 additions and 24 deletions

View file

@ -3,6 +3,7 @@
This file is part of the KDE project.
Copyright (C) 2010 Rohan Prabhu <rohan@rohanprabhu.com>
Copyright (C) 2011 Martin Gräßlin <mgraesslin@kde.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,25 +20,63 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "scripting.h"
// own
#include "chelate.h"
#include "meta.h"
#include "s_clientgroup.h"
// KDE
#include <kstandarddirs.h>
// Qt
#include <QtDBus/QDBusConnection>
#include <QtCore/QSettings>
#include <QtScript/QScriptEngine>
#include <QtScript/QScriptValue>
QScriptValue kwinScriptPrint(QScriptContext *context, QScriptEngine *engine)
{
KWin::Script *script = qobject_cast<KWin::Script*>(context->callee().data().toQObject());
QString result;
for (int i = 0; i < context->argumentCount(); ++i) {
if (i > 0) {
result.append(" ");
}
result.append(context->argument(i).toString());
}
script->printMessage(result);
return engine->undefinedValue();
}
KWin::Script::Script(QString scriptName, QDir dir, QObject *parent)
KWin::Script::Script(int scriptId, QString scriptName, QDir dir, QObject *parent)
: QObject(parent)
, m_scriptId(scriptId)
, m_engine(new QScriptEngine(this))
, m_scriptDir(dir)
, m_configFile(QFileInfo(m_scriptFile).completeBaseName() + QString(".kwscfg"))
, m_workspace(new SWrapper::Workspace(m_engine))
, m_running(false)
{
m_scriptFile.setFileName(dir.filePath(scriptName));
QDBusConnection::sessionBus().registerObject("/" + QString::number(m_scriptId), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportScriptableInvokables);
}
KWin::Script::~Script()
{
QDBusConnection::sessionBus().unregisterObject("/" + QString::number(m_scriptId));
}
void KWin::Script::printMessage(const QString &message)
{
kDebug(1212) << m_scriptFile.fileName() << ":" << message;
emit print(message);
}
void KWin::Script::run()
{
if (m_running) {
return;
}
if (m_scriptFile.open(QIODevice::ReadOnly)) {
m_workspace->attach(m_engine);
m_engine->globalObject().setProperty("QTimer", constructTimerClass(m_engine));
@ -60,13 +99,19 @@ void KWin::Script::run()
} else {
KWin::MetaScripting::supplyConfig(m_engine);
}
// add our print
QScriptValue printFunc = m_engine->newFunction(kwinScriptPrint);
printFunc.setData(m_engine->newQObject(this));
m_engine->globalObject().setProperty("print", printFunc);
QScriptValue ret = m_engine->evaluate(m_scriptFile.readAll());
if (ret.isError()) {
sigException(ret);
deleteLater();
}
}
m_running = true;
}
void KWin::Script::sigException(const QScriptValue& exception)
@ -83,12 +128,20 @@ void KWin::Script::sigException(const QScriptValue& exception)
qDebug() << " " << iter.name() << ": " << iter.value().toString();
}
}
emit printError(exception.toString());
}
void KWin::Script::stop()
{
deleteLater();
}
KWin::Scripting::Scripting(QObject *parent)
: QObject(parent)
{
QDBusConnection::sessionBus().registerObject("/Scripting", this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportScriptableInvokables);
QDBusConnection::sessionBus().registerService("org.kde.kwin.Scripting");
}
void KWin::Scripting::start()
@ -106,7 +159,7 @@ void KWin::Scripting::start()
scriptList = scriptsDir.entryList(scriptFilters, QDir::Files | QDir::Readable | QDir::Executable);
for (int i = 0; i < scriptList.size(); i++) {
scripts.append(new KWin::Script(scriptsDir.filePath(scriptList.at(i)), scriptsDir, this));
loadScript(scriptsDir.filePath(scriptList.at(i)));
}
// Initialize singletons. Currently, only KWin::Workspace.
@ -122,6 +175,24 @@ void KWin::Scripting::runScripts()
}
}
void KWin::Scripting::scriptDestroyed(QObject *object)
{
scripts.removeAll(static_cast<KWin::Script*>(object));
}
int KWin::Scripting::loadScript(const QString &filePath)
{
const int id = scripts.size();
KWin::Script *script = new KWin::Script(id, filePath, scriptsDir, this);
connect(script, SIGNAL(destroyed(QObject*)), SLOT(scriptDestroyed(QObject*)));
scripts.append(script);
return id;
}
KWin::Scripting::~Scripting()
{
QDBusConnection::sessionBus().unregisterObject("/Scripting");
QDBusConnection::sessionBus().unregisterService("org.kde.kwin.Scripting");
}
#include "scripting.moc"

View file

@ -3,6 +3,7 @@
This file is part of the KDE project.
Copyright (C) 2010 Rohan Prabhu <rohan@rohanprabhu.com>
Copyright (C) 2011 Martin Gräßlin <mgraesslin@kde.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -21,37 +22,38 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef KWIN_SCRIPTING_H
#define KWIN_SCRIPTING_H
#include <QScriptClass>
#include <QScriptEngine>
#include <QDir>
#include <QVector>
#include <QSettings>
#include <QVariant>
#include "chelate.h"
#include "workspace.h"
#include "workspaceproxy.h"
#include "s_clientgroup.h"
#include "./../workspace.h"
#include "meta.h"
class QScriptEngine;
class QScriptValue;
namespace KWin
{
/** This mostly behaves like a struct. Is used to store
* the scriptfile, the configfile and the QScriptEngine
* that will run this script
*/
class Script : public QObject
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.kwin.Scripting")
public:
Script(QString scriptName, QDir dir, QObject *parent = NULL);
Script(int id, QString scriptName, QDir dir, QObject *parent = NULL);
virtual ~Script();
void run();
QString fileName() const {
return m_scriptFile.fileName();
}
void printMessage(const QString &message);
public Q_SLOTS:
Q_SCRIPTABLE void stop();
Q_SCRIPTABLE void run();
Q_SIGNALS:
Q_SCRIPTABLE void print(const QString &text);
Q_SCRIPTABLE void printError(const QString &text);
private slots:
/**
@ -61,11 +63,13 @@ private slots:
void sigException(const QScriptValue &exception);
private:
int m_scriptId;
QScriptEngine *m_engine;
QDir m_scriptDir;
QFile m_scriptFile;
QString m_configFile;
SWrapper::Workspace *m_workspace;
bool m_running;
};
/**
@ -74,19 +78,15 @@ private:
class Scripting : public QObject
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.kwin.Scripting")
private:
QStringList scriptList;
QDir scriptsDir;
QVector<KWin::Script*> scripts;
QList<KWin::Script*> scripts;
// Preferably call ONLY at load time
void runScripts();
// NOTE: Runtime script running is not yet tested.
// Proceed with caution.
// An interface to run scripts at runtime
void runScript(KWin::Script*);
public:
Scripting(QObject *parent = NULL);
/**
@ -95,6 +95,10 @@ public:
*/
void start();
~Scripting();
Q_SCRIPTABLE Q_INVOKABLE int loadScript(const QString &filePath);
public Q_SLOTS:
void scriptDestroyed(QObject *object);
};
}