The Kross scripting framework provides full Python, Ruby and KDE JavaScript scripting support. The goal was to limit the work needed on applications to have them fully scriptable and to provide a modular way to transparently integrate additional interpreters and in that way extend your application with a new scripting-backend without any new line of code and even without any recompile. To achieve this internally Qt's introspection-functionality like signals, slots, properties, enums, QVariant, QObject, QMetaObject, QMetaType, etc. are used to deal with functionality at runtime.
Kross is a modular scripting framework that eases embedding of scripting interpreters like Python, Ruby and JavaScript transparently into native applications to bridge the static and dynamic worlds together.
Scripting interpreters are plugins that are loaded dynamically on demand at runtime by the Kross framework. The application that uses Kross to provide scripting functionality to the user does not need to know anything about the interpreter, itself or any other backend-details. All the application needs to know is that there exists scripting code that should be executed. Kross will then take care of it.
Kross does not implement its own scripting language, rather, it provides access to already existing solutions. Kross does not implement a complete Toolkit-binding, rather, it provides access to already existing solutions like PyQt/PyKDE or TkInter for Python or Korundum/QtRuby for Ruby. Kross does not auto-generate code, it is not needed to maintain any configuration files, and it does not come with yet another bindings-API. All it does is to connect things transparently together and to offer optional access to already existing technologies.
There exists a wide range of solutions to deal with scripting. Programming languages like Python have enjoyed years of evolution and a large community around them with 3rd-party modules for nearly everything. The same goes for a lot of other languages where there sometimes exists a special function that is only available to a particular language, and where each language comes with its own way of doing things. Each user may have their own preference for what scripting language they like to use, and over time, may have acquired skills in some specific areas.
Rather than limiting users to only one specific language with its own way of doing things, Kross offers a flexible way of supporting multiple programming languages without additional code and maintenance work, at the application level. Kross allows the user to choose what they like and know the most, and it minimizes the entry-barrier to get his things done and to be able to contribute to one's own solutions significantly.
As of today Python, Ruby and JavaScript are supported. While all of them are passing the unittests, current state is, that Python is the most complete one followed by Ruby which is near the same state and finally JavaScript which needs some more tweaks to be as rock-stable as the other both implementations.
Work on progress are krossjava (thanks to google for supporting this plugin within the Google Summer of Code 2007) that implements support for the Java Virtual Machine and krossfalcon that implements support for The Falcon Programming Language.
For sure any interpreter-plugin you may like to implement is very welcome. At the end the open source world is about choices :)
Kross offers a plugin interface to integrate interpreter backends like Python, Ruby and KDE-Javascript. They are loaded on demand at runtime. Neither the application nor Kross needs to know any details about the backends. This clear separation between the application and scripting details enables at the one hand, to deal with scripting at an abstract level without being bound to only one backend, and at the other hand makes it easy to integrate scripting into an already existing application.
Each interpreter plugin needs to implement two abstract classes;
The Python plugin implements access to the Python scripting language.
The unittest.py Python script file implements unittests for common functionality. For the unittests kross does use a special test-application that got compiled if cmake -DKDE4_BUILD_TESTS=1 was used for kdelibs. You are able to run the unittest with
cd kdelibs/kross/test ./krosstest unittest.py
Other scripts are...
The krosspython plugin consists of...
The Ruby plugin implements access to the Ruby scripting language.
The unittest.rb Ruby script file implements unittests for common functionality. For the unittests kross does use a special test-application that got compiled if cmake -DKDE4_BUILD_TESTS=1 was used for kdelibs. You are able to run the unittest with
cd kdelibs/kross/test ./krosstest unittest.rb
The krossruby plugin consists of...
The KDE JavaScript plugin uses the in kdelibs4 included Kjs and KjsEmbed4 frameworks to provide access to the JavaScript language.
The unittest.js KDE-JavaScript script file implements unittests for common functionality. For the unittests kross does use a special test-application that got compiled if cmake -DKDE4_BUILD_TESTS=1 was used for kdelibs. You are able to run the unittest with
cd kdelibs/kross/test ./krosstest unittest.js
For details about KDE-JavaScript see...
The Google Summer of Code 2007 did provide us the krossjava backend for Java :-)
The krossfalcon plugin does implement access to The Falcon Programming Language.
Modules are plugins loaded on demand at runtime to provide additional functionality. They only provide a factory to create and return a QObject instance that is then exposed to the scripting backend.
Since Qt's introspection functionality is used, we are able to throw in just QObject's and have them act as classes/objects within a scripting backend. Slots are membermethods while properties and enumerations are membervariables. If your application also likes to offer D-Bus support it may be an idea to reuse the QDBusAbstractAdaptor implementations your application has also for scripting, like for example KSpread did.
Let's take a look at the following code that implements the MyObject class which inherits from QObject;
class MyObject : public QObject {
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName)
public:
MyObject(QObject* parent = 0) : QObject(parent) {}
virtual ~MyObject() {}
QString name() const { return objectName(); }
void setName(const QString& name) {
return setObjectName(name);
}
public slots:
QObject* create(const QString& name,
QObject* parent=0) {
MyObject* obj = new MyObject(parent);
obj->setObjectName(name);
return obj;
}
QObject* parent() const { return QObject::parent(); }
void setParent(QObject* parent) {
QObject::setParent(parent);
emit parentChanged();
}
signals:
void parentChanged();
};
extern "C" {
KDE_EXPORT QObject* krossmodule() {
return new MyObject();
}
}
At the bottom the krossmodule() function will be called by Kross and returns an instance of MyObject that is then exposed to the scripting backend.
Then we just need to have our myobject.h and myobject.cpp files, filled with the content above, defined in the CMakeLists.txt file. The library needs to be named "krossmodule..." where the "..." is then the name the module is accessible as. For our example we use "krossmodulemyobjectmod" and therefore we are able to access the module if installed as "myobjectmod". The example does not depend on Kross, so you may like to replace ${KROSS_INCLUDES} with whatever else your module depends on.
include_directories(${CMAKE_SOURCE_DIR} ${KROSS_INCLUDES}) set(krossmodulemyobjectmod_PART_SRCS myobject.cpp) kde4_add_plugin(krossmodulemyobjectmod ${krossmodulemyobjectmod_PART_SRCS}) target_link_libraries(krossmodulemyobjectmod ${KDE4_KDECORE_LIBS} ${KDE4_KROSSCORE_LIBS}) install(TARGETS krossmodulemyobjectmod DESTINATION ${PLUGIN_INSTALL_DIR})
The following Python sample code accesses then the module at runtime and uses the QObject, calls its slots and properties and connects a signal with a python function (e.g. save as file named "myobjecttest.py" and execute with "kross ./myobjecttest.py");
#!/usr/bin/env kross
import Kross
m = Kross.module("myobjectmod")
m.name = "MyObjectModuleName"
obj1 = m.create("OtherObjectName")
def myCallbackFunc(args):
print "The parent of obj1 changed"
obj1.connect("parentChanged()",myCallbackFunc)
obj1.setParent(m)
print "%s %s" % (obj1.name,obj1.parent().name)
The Kross::Manager class is a singleton that provides access to the interpreters, to actions and to the modules. The Kross::Manager is available within scripting code as module named "Kross". Following Python script uses the Kross module to create a new Kross::Action instance, fills it with JavaScript code and executes that JavaScript code.
#!/usr/bin/env kross
import Kross
a = Kross.action("MyKjsScript")
a.setInterpreter("javascript")
a.setCode("println(\"Hello world from Kjs\");")
a.trigger()
The Kross::GuiClient class implements KXMLGUIClient to provide GUI functionality, handling of XML configuration files on a more abstract level and offers some predefined actions that could be optionally used in your application.
The Kross::Action class offers an abstract container to deal with scripts like a single standalone scriptfile. Each action holds a reference to the by the matching Kross::Interpreter instance created by Kross::Script instance. Following Python script accesses the Kross module and the self variable which is our Kross::Action instance that provides the context for the running Python script.
#!/usr/bin/env kross
import Kross
print "objectName=%s" % Kross.objectName()
print "interpreters=%s" % Kross.interpreters()
print "objectName=%s" % self.objectName()
print "text=%s" % self.text
print "enabled=%s" % self.enabled
print "currentPath=%s" % self.currentPath()
print "interpreter=%s" % self.interpreter()
print "description=%s" % self.description()
print "code=%s" % self.code()
With the QtDBus module Qt provides a library that a Qt/KDE application is able to use to make Inter-Process Communication using the D-BUS protocol.
The QtDBus module uses the Qt Signals and Slots mechanism. Applications that like to provide parts of their functionality to the D-Bus world are able to do so by implementing classes that inherit from the QDBusAbstractAdaptor class.
Kross is able to reuse such by an application provided bindings. So, once your application supports D-Bus, Kross is able to reuse those already existing code and offers transparent access to the scripting-backends to them. How does this differ from e.g. the python-dbus package? Well, first method-calls don't go through the dbus-socket and then it's not dbus-related at all except, that we are able to reuse what your application offers anyway: a clean interface to the outside world. But that's not all, we are not limited to what's possible with D-Bus. We are also able to exchange instance-pointers to QObject or QWidget instances.
For an example you may like to take a look at how it was done in the Calligra Sheets ScriptingModule class.