From 6a8b79f69943b57e52c4d206b5c4f46432045b8a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Gr=C3=A4=C3=9Flin?= <mgraesslin@kde.org>
Date: Sun, 19 Feb 2012 15:43:24 +0100
Subject: [PATCH] Support declarative KWin scripts

For this the Script class is slightly refactored to have a common
base for JavaScript and QML based scripts.

Why QML bindings? This allows to use QML for example for the
desktop change OSD or for fullscreen effects like Present Windows.
---
 scripting/scripting.cpp | 101 +++++++++++++++++++++++++++++++++-------
 scripting/scripting.h   |  68 +++++++++++++++++++++++----
 2 files changed, 142 insertions(+), 27 deletions(-)

diff --git a/scripting/scripting.cpp b/scripting/scripting.cpp
index c6b6d8ebb7..da3b7037c2 100644
--- a/scripting/scripting.cpp
+++ b/scripting/scripting.cpp
@@ -23,15 +23,21 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 // own
 #include "meta.h"
 #include "workspace_wrapper.h"
+#include "../thumbnailitem.h"
 // KDE
 #include <kstandarddirs.h>
 #include <KDE/KConfigGroup>
 #include <KDE/KDebug>
 #include <KDE/KPluginInfo>
 #include <KDE/KServiceTypeTrader>
+#include <kdeclarative.h>
 // Qt
 #include <QtDBus/QDBusConnection>
 #include <QtCore/QSettings>
+#include <QtDeclarative/QDeclarativeContext>
+#include <QtDeclarative/QDeclarativeEngine>
+#include <QtDeclarative/QDeclarativeView>
+#include <QtDeclarative/qdeclarative.h>
 #include <QtScript/QScriptEngine>
 #include <QtScript/QScriptValue>
 
@@ -50,36 +56,49 @@ QScriptValue kwinScriptPrint(QScriptContext *context, QScriptEngine *engine)
     return engine->undefinedValue();
 }
 
-
-KWin::Script::Script(int scriptId, QString scriptName, QObject *parent)
+KWin::AbstractScript::AbstractScript (int id, QString scriptName, QObject *parent)
     : QObject(parent)
-    , m_scriptId(scriptId)
-    , m_engine(new QScriptEngine(this))
-    , m_workspace(new WorkspaceWrapper(m_engine))
+    , m_scriptId(id)
     , m_running(false)
+    , m_workspace(new WorkspaceWrapper(this))
 {
     m_scriptFile.setFileName(scriptName);
-    QDBusConnection::sessionBus().registerObject('/' + QString::number(m_scriptId), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportScriptableInvokables);
+}
+
+KWin::AbstractScript::~AbstractScript()
+{
+}
+
+void KWin::AbstractScript::stop()
+{
+    deleteLater();
+}
+
+KWin::Script::Script(int id, QString scriptName, QObject *parent)
+    : AbstractScript(id, scriptName, parent)
+    , m_engine(new QScriptEngine(this))
+{
+    QDBusConnection::sessionBus().registerObject('/' + QString::number(scriptId()), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportScriptableInvokables);
 }
 
 KWin::Script::~Script()
 {
-    QDBusConnection::sessionBus().unregisterObject('/' + QString::number(m_scriptId));
+    QDBusConnection::sessionBus().unregisterObject('/' + QString::number(scriptId()));
 }
 
 void KWin::Script::printMessage(const QString &message)
 {
-    kDebug(1212) << m_scriptFile.fileName() << ":" << message;
+    kDebug(1212) << scriptFile().fileName() << ":" << message;
     emit print(message);
 }
 
 void KWin::Script::run()
 {
-    if (m_running) {
+    if (running()) {
         return;
     }
-    if (m_scriptFile.open(QIODevice::ReadOnly)) {
-        QScriptValue workspace = m_engine->newQObject(m_workspace, QScriptEngine::QtOwnership,
+    if (scriptFile().open(QIODevice::ReadOnly)) {
+        QScriptValue workspace = m_engine->newQObject(AbstractScript::workspace(), QScriptEngine::QtOwnership,
                                 QScriptEngine::ExcludeSuperClassContents | QScriptEngine::ExcludeDeleteLater);
         m_engine->globalObject().setProperty("workspace", workspace, QScriptValue::Undeletable);
         m_engine->globalObject().setProperty("QTimer", constructTimerClass(m_engine));
@@ -92,14 +111,14 @@ void KWin::Script::run()
         printFunc.setData(m_engine->newQObject(this));
         m_engine->globalObject().setProperty("print", printFunc);
 
-        QScriptValue ret = m_engine->evaluate(m_scriptFile.readAll());
+        QScriptValue ret = m_engine->evaluate(scriptFile().readAll());
 
         if (ret.isError()) {
             sigException(ret);
             deleteLater();
         }
     }
-    m_running = true;
+    setRunning(true);
 }
 
 void KWin::Script::sigException(const QScriptValue& exception)
@@ -119,11 +138,44 @@ void KWin::Script::sigException(const QScriptValue& exception)
     emit printError(exception.toString());
 }
 
-void KWin::Script::stop()
+KWin::DeclarativeScript::DeclarativeScript(int id, QString scriptName, QObject *parent)
+    : AbstractScript(id, scriptName, parent)
+    , m_view(new QDeclarativeView())
 {
-    deleteLater();
 }
 
+KWin::DeclarativeScript::~DeclarativeScript()
+{
+}
+
+void KWin::DeclarativeScript::run()
+{
+    if (running()) {
+        return;
+    }
+    m_view->setAttribute(Qt::WA_TranslucentBackground);
+    m_view->setWindowFlags(Qt::X11BypassWindowManagerHint);
+    m_view->setResizeMode(QDeclarativeView::SizeViewToRootObject);
+    QPalette pal = m_view->palette();
+    pal.setColor(m_view->backgroundRole(), Qt::transparent);
+    m_view->setPalette(pal);
+
+
+    foreach (const QString &importPath, KGlobal::dirs()->findDirs("module", "imports")) {
+        m_view->engine()->addImportPath(importPath);
+    }
+    KDeclarative kdeclarative;
+    kdeclarative.setDeclarativeEngine(m_view->engine());
+    kdeclarative.initialize();
+    kdeclarative.setupBindings();
+    qmlRegisterType<ThumbnailItem>("org.kde.kwin", 0, 1, "ThumbnailItem");
+    qmlRegisterType<WorkspaceWrapper>("org.kde.kwin", 0, 1, "KWin");
+
+    m_view->rootContext()->setContextProperty("workspace", workspace());
+
+    m_view->setSource(QUrl::fromLocalFile(scriptFile().fileName()));
+    setRunning(true);
+}
 
 KWin::Scripting::Scripting(QObject *parent)
     : QObject(parent)
@@ -142,7 +194,9 @@ void KWin::Scripting::start()
     foreach (const KService::Ptr & service, offers) {
         KPluginInfo plugininfo(service);
         plugininfo.load(conf);
-        if (service->property("X-Plasma-API").toString() != "javascript") {
+        const bool javaScript = service->property("X-Plasma-API").toString() == "javascript";
+        const bool declarativeScript = service->property("X-Plasma-API").toString() == "declarativescript";
+        if (!javaScript && !declarativeScript) {
             continue;
         }
 
@@ -156,7 +210,11 @@ void KWin::Scripting::start()
             kDebug(1212) << "Could not find script file for " << pluginName;
             continue;
         }
-        loadScript(file);
+        if (javaScript) {
+            loadScript(file);
+        } else if (declarativeScript) {
+            loadDeclarativeScript(file);
+        }
     }
 
     runScripts();
@@ -183,6 +241,15 @@ int KWin::Scripting::loadScript(const QString &filePath)
     return id;
 }
 
+int KWin::Scripting::loadDeclarativeScript(const QString &filePath)
+{
+    const int id = scripts.size();
+    KWin::DeclarativeScript *script = new KWin::DeclarativeScript(id, filePath, this);
+    connect(script, SIGNAL(destroyed(QObject*)), SLOT(scriptDestroyed(QObject*)));
+    scripts.append(script);
+    return id;
+}
+
 KWin::Scripting::~Scripting()
 {
     QDBusConnection::sessionBus().unregisterObject("/Scripting");
diff --git a/scripting/scripting.h b/scripting/scripting.h
index e9e91e8c5f..d28f06a896 100644
--- a/scripting/scripting.h
+++ b/scripting/scripting.h
@@ -25,6 +25,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #include <QtCore/QFile>
 #include <QtCore/QStringList>
 
+class QDeclarativeView;
 class QScriptEngine;
 class QScriptValue;
 
@@ -32,7 +33,46 @@ namespace KWin
 {
 class WorkspaceWrapper;
 
-class Script : public QObject
+class AbstractScript : public QObject
+{
+    Q_OBJECT
+public:
+    AbstractScript(int id, QString scriptName, QObject *parent = NULL);
+    ~AbstractScript();
+    QString fileName() const {
+        return m_scriptFile.fileName();
+    }
+
+public Q_SLOTS:
+    Q_SCRIPTABLE void stop();
+    Q_SCRIPTABLE virtual void run() = 0;
+
+protected:
+    QFile &scriptFile() {
+        return m_scriptFile;
+    }
+    bool running() const {
+        return m_running;
+    }
+    void setRunning(bool running) {
+        m_running = running;
+    }
+    int scriptId() const {
+        return m_scriptId;
+    }
+
+    WorkspaceWrapper *workspace() {
+        return m_workspace;
+    }
+
+private:
+    int m_scriptId;
+    QFile m_scriptFile;
+    bool m_running;
+    WorkspaceWrapper *m_workspace;
+};
+
+class Script : public AbstractScript
 {
     Q_OBJECT
     Q_CLASSINFO("D-Bus Interface", "org.kde.kwin.Scripting")
@@ -40,14 +80,10 @@ public:
 
     Script(int id, QString scriptName, QObject *parent = NULL);
     virtual ~Script();
-    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:
@@ -62,11 +98,22 @@ private slots:
     void sigException(const QScriptValue &exception);
 
 private:
-    int m_scriptId;
     QScriptEngine *m_engine;
-    QFile m_scriptFile;
-    WorkspaceWrapper *m_workspace;
-    bool m_running;
+};
+
+class DeclarativeScript : public AbstractScript
+{
+    Q_OBJECT
+    Q_CLASSINFO("D-Bus Interface", "org.kde.kwin.Scripting")
+public:
+    explicit DeclarativeScript(int id, QString scriptName, QObject *parent = 0);
+    virtual ~DeclarativeScript();
+
+public Q_SLOTS:
+    Q_SCRIPTABLE void run();
+
+private:
+    QDeclarativeView *m_view;
 };
 
 /**
@@ -78,7 +125,7 @@ class Scripting : public QObject
     Q_CLASSINFO("D-Bus Interface", "org.kde.kwin.Scripting")
 private:
     QStringList scriptList;
-    QList<KWin::Script*> scripts;
+    QList<KWin::AbstractScript*> scripts;
 
     // Preferably call ONLY at load time
     void runScripts();
@@ -92,6 +139,7 @@ public:
     void start();
     ~Scripting();
     Q_SCRIPTABLE Q_INVOKABLE int loadScript(const QString &filePath);
+    Q_SCRIPTABLE Q_INVOKABLE int loadDeclarativeScript(const QString &filePath);
 
 public Q_SLOTS:
     void scriptDestroyed(QObject *object);