The current state of KWin scripting is quite incomplete and there are a lot of objects yet to be wrapped. Over a period of time, I didn’t see any commits to the scripting directory. So, I just thought that maybe, it would be a fault on my part to have not given enough documentation and to a good extent this happens to be true. Also, Martin contacted me recently asking me to put in some docs. In this document, I would like to outline the process of adding more functions and objects to the KWin Scripting system and the architecture as it is. Note that this document does not cover coding with KWin or the Qt Script subsystem in detail. This only outlines the architecture used to implement scripting in KWin. So if you are inexperienced with the QtScript module or KWin coding, I cannot assure that this document will be of extreme help to you. To begin with, allow me to present a few pointers when working with KWin scripting:
- There are three kinds of objects:
- Singleton: These objects cannot be instantiated. Only one object of that type exists and is provided throughout the scripting interface. In the current implementation, the
workspaceobject is an example of a singleton. - Floating: These objects cannot be instantiated and neither are available throughout the scripting scope but are available only as parameters of various triggered functions or return values of various function calls. All objects of type ‘
client‘ are floating. - Instantiable: Instances of these objects can be created and they may or may not manifest into actual KWin objects (some manifest purely into scripting objects). For example, a
ClientGroupcan be instantiated, and it also manifests into an actual KWin object i.e.KWin::ClientGroup. On the other hand, aQTimeris also instantiable, but it manifests into a scripting object only (also in an object on the C++ side), but not a KWin object.
- Singleton: These objects cannot be instantiated. Only one object of that type exists and is provided throughout the scripting interface. In the current implementation, the
- The ‘
workspace‘ object is the primary source of all events and generally, this is from where one can start interacting with what happens within the window manager. - Despite
KWin::Clientbeing aQObject, QObjects are never directly exposed to the scripting interface. This is largely in part due to the fact that not all methods are defined asQ_INVOKABLEand at the same time, there seems to be no rationale supporting any kind of reflections in a window manager. Also, this was the architecture lunak desired and it allows us to finely control what and what not is exposed to the scripting interface. - All wrappers are available under the
SWrappernamespace. For example, the wrapper ofKWin::ClientisSWrapper::Clientpresent in scripting/client.(h|cpp). - For every script that is running, we need a seperate
QScriptEngineobject. Every singleton object that is present needs to have a seperate instance of every singleton object as aQScriptValueis tightly binded to theQScriptEnginein question. In the beginning, I had a singleQScriptEnginewhich I made to just test and focus more on wrapping the various objects. When I made the switch to multiple QScriptEngines, it caused the inclusion of advanced objects like theWorkspaceProxy, which I will cover later on.
With that in mind, let’s proceed on to the way events are handled and managed in the KWin scripting implementation.
Signals and Slots
KWin scripting uses the Qt signals-and-slot mechanism to handle all events. As an outline, let’s take the case of two objects KWin::ObjectA and SWrapper::ObjectA. As is obvious, SWrapper::ObjectA wraps KWin::ObjectA and exposes it to the scripting interface. Now, KWin::ObjectA has an event, ateBacon which is emitted whenever ObjectA eats bacon. Also, this ‘emit’ is conceptual, as in, there may or may not be an associated signal with it. In such a case, here’s how KWin scripting is built up:
- A new signal is created in KWin::ObjectA called s_ateBacon.
- A new slot is created in SWrapper::ObjectA called sl_ateBacon.
- The slot in SWrapper::ObjectA generally re-emits another slot for, ‘ateBacon’, which can be caught by the scripting interface. The need for doing so is that the parameters of KWin::ObjectA are QScriptValues. This is so because, while adding events, it was decided that adding signals could be done in a way that would facilitate further development also and not just cater to the needs of scripting alone. If there are any signals which do not pass any parameters, the sl_ateBacon is not created and ‘KWin::ObjectA::s_ateBacon’ connects directly to the ‘SWrapper::ObjectA::teBacon’ signal.
- As is obvious that these signal and slot are connected, the connection may be carried out in either way: the constructor of KWin::ObjectA may perform the connections in it’s constructor or SWrapper::ObjectA might do it in it’s constructor, but it is always done in either of the constructors.
It is difficult to perfectly outline how exactly the connection procedure is, as it is different for different objects depending on whether it is a singleton, floating or an instantiable object. In the following sections, as I cover different objects and their wrapping, I will cover this.
Currently, not present in the KWSApi.html, but still present in the apidocs.xml file (look in your svn checkout in the kwin/scripting directory) is the various signals and slots used for handling events. For example,
<event name="currentDesktopChanged" signal="currentDesktopChanged" wslot="sl_currentDesktopChanged" wsignal="currentDesktopChaned">
In the above, signal, i.e., ‘currentDesktopChanged’ refers to the signal emitted by the SWrapper::Workspace object. This signal is emitted from the wslot, i.e., ‘SWrapper::Workspace::sl_currentDesktopChanged’ which is connected to wsignal, i.e., ‘KWin::Workspace::currentDesktopChanged’. To further clarify, let me show you how this looks in code:
This is from KWin::Workspace::setCurrentDesktop. This function is called to switch to a new desktop. This is exactly the function we are looking for when it comes to having an event for notifying the change of the current desktop. In here, we emit our signal ‘KWin::Workspace::currentDesktopChanged’.
bool Workspace::setCurrentDesktop(int new_desktop) {
// .....
// .....
emit currentDesktopChanged(old_desktop);
return true;
}
Now, looking over at the SWrapper::Workspace class, let us first look at scripting/workspace.h:
public slots:
void sl_desktopPresenceChanged(KWin::Client*, int);
signals:
void currentDesktopChanged(QScriptValue);
The signal ‘currentDesktopChanged(QScriptValue)’ can be directly accessed by the scripting interface as the SWrapper object is directly wrapped as QObject. More on that later.
Not all the signals of a wrapper are connected to the signals of the objects they wrap. For example, the ‘clientMinimized’ signal is emitted from the ‘KWin::Client’ object but both ‘SWrapper::Workspace’ and ‘SWrapper::Client’ connect to it. In this particular case, a different approach is needed, i.e., with a proxy object, which we will cover in later sections.
With that covered, let’s move on to the first and a quite important class, the KWinScripting object.
The KWinScripting object
This is the object that when instantiated, loads all the scripts and the corresponding configuration files. Details of how scripts are loaded is provided here. KWinScripting is declared in scripting/scripting.(h|cpp). Another class declared here is the ‘KWin::Script’, which is pretty self-explanatory. It simply holds information about the script file, the configuration file and most importantly, a pointer to the ‘SWrapper::Workspace’ object that is provided for the script and the QScriptEngine* that it is associated with. Changes to a workspace object within a script are reflected across all instances across the script, but NOT across different scripts. For example, if I have two files scripta.js and scriptb.js,
scripta.js
----------
workspace.prototype.moreBacon = function() {
getMoreBacon();
}
print(workspace.moreBacon);
scriptb.js
----------
print(typeof workspace.moreBacon);
In scripta.js, the output would be ‘function’, whereas in scriptb.js, the output would be ‘undefined’. This behaviour is quite important and hence each singleton needs to have a seperate wrapper object for each script that is loaded.
KWinScripting’s basic job is to load and run scripts and also manage a WorkspaceProxy object. Unlike other singletons, the WorkspaceProxy is declared xactly once and a single instance is enough for all scripts that are executed. More on WorkspaceProxy later, let’s take a look at what happens inside a WorkspaceProxy object.
script->workspace = new SWrapper::Workspace(script->engine);
(script->workspace)->attach(script->engine);
((script->engine)->globalObject()).setProperty("QTimer", constructTimerClass((script->engine)));
((script->engine)->globalObject()).setProperty("ClientGroup", SWrapper::ClientGroup::publishClientGroupClass((script->engine)));
((script->engine)->globalObject()).setProperty("chelate", KWin::Chelate::publishChelate(script->engine));
((script->engine)->globalObject()).setProperty("ch", KWin::Chelate::publishChelate(script->engine));
This is the code from ‘KWinScripting::runScript(KWin::Script*)’ method, which in essence takes a KWin::Script object and initializes the basic set of objects for the scripting environment. This is what is happening, line-by-line:
- First, we create a new workspace wrapper object and call it’s attach method to attach it to a given QScriptEngine. What attach does is sets the SWrapper::Workspace object as the global object of the given scripting engine.
- Once a global object has been set, we can add more objects to it. All other singletons and instantiables are added here.
- Whenever you are creating an instantiable or a singleton object, make sure that the wrapper has a static method that when passed a QScriptEngine* object, it would return a QScriptValue which is binded to the QScriptEngine object.
- In the above case, we add the QTimer, ClientGroup (instantiable) and the chelate (singleton) objects to the QScriptEngine’s global object.
Again an inspection into these static methods would clearly illustrate how this procedure goes along. KWinScripting also has a slot called ‘sigException’ which is connected to the QScriptEngine’s signalHandlerException, to handle and report users. The current implementation outputs the error name, message and line number to stdout.
QObject::connect((script->engine), SIGNAL(signalHandlerException(const QScriptValue&)), this, SLOT(sigException(const QScriptValue&)));
KWinScripting also has a start method, which is the function that handles loading and running of the scripts. It basically makes a list of scripts that can be run and then calls ‘KWinScripting::runScript’ for each of those scripts. One of the important things this must do is initialize the ‘SWrapper::Workspace’ object by doing ‘SWrapper::Workspace::initialize’. The method needs to be passed a pointer to the ‘KWin::Workspace’ object so that it can wrap it. ‘initialize’ is a static method, so it can be and is done without creating any instance of the ‘SWrapper::Workspace’ object yet.
The ‘SWrapper::Workspace’ object
The ‘SWrapper::Workspace’ object contains a member called ‘centralObject’, which is the very pointer to the KWin::Workspace object we previously initialized using the ‘SWrapper::Workspace::initialize’ method. A look at the declaration of SWrapper::Workspace would show a huge list of slots and signals. All the signals that are present are accessible from the scritping interface and they are what provide events to the script. The slots that are listed receive events from the main objects and then convert the parameters to QScriptValues and then pass them on to the corresponding signal. Let’s inspect the ‘clientMaximizeSet’ event:
public slots:
void sl_clientMaximizeSet(KWin::Client*, QPair);
signals:
void clientMaximizeSet(QScriptValue, QScriptValue);
And the slot is defined as:
void SWrapper::Workspace::sl_clientMaximizeSet(KWin::Client* client, QPair<bool, bool> param)
{
if(centralEngine == 0)
{
return;
}
else
{
// LABEL 1
QScriptValue temp = centralEngine->newObject();
temp.setProperty("v", centralEngine->toScriptValue(param.first));
temp.setProperty("h", centralEngine->toScriptValue(param.second));
emit clientMaximizeSet(centralEngine->toScriptValue(client), temp);
}
}
Here, ‘centralEngine’ is a pointer to a QScriptEngine object that is unique to every SWrapper::Workspace object, i.e., it provides the class with the QScriptEngine it is wrapping an object for. In this example, in the first 3 lines it creates an object which creates a JavaScript object with two members ‘v’ and ‘h’ which essentially tell which directions the window was maximized in. And finally, when the associated slot is emitted, it wraps the corresponding KWin::Client, i.e., the client which was maximized in a QScriptValue and emits the event, which can then be caught by the scripting interface. And important thing to not here is:
centralEngine->toScriptValue(client) or more accurately, centralEngine->toScriptValue<KWin::Client*>(client);
The conversion functions and code is provided in meta.(h|cpp) and we shall get to the scripting meta-system in the later sections.
Now, if we were to look inside the constructor of the ‘SWrapper::Workspace’ object, once could notice a lot of connections that are being made. Let me take two examples here:
SWrapper::WorkspaceProxy* proxy = SWrapper::WorkspaceProxy::instance();
QObject::connect(centralObject, SIGNAL(desktopPresenceChanged(KWin::Client*, int)),
this, SLOT(sl_desktopPresenceChanged(KWin::Client*, int))
);
QObject::connect(proxy, SIGNAL(clientFullScreenSet(KWin::Client*, bool, bool)),
this, SLOT(sl_clientFullScreenSet(KWin::Client*, bool, bool))
);
In the first line, it asks for an instance of a WorkspaceProxy object, which can be used to make multiple-client-to-multiple-workspace connections. There are two ways of connections that are done here:
- Connecting to signals emitted by the ‘KWin::Workspace’ object itself.
- Connecting to signals emitted by other classes, like ‘KWin::Client’. When doing so, it is no big deal for singletons, but it becomes a different isse for floating and instantiable classes. To overcome this problem, the WorkspaceProxy was introduced.
Whenever connections of the second type are required, the corresponding signal of the proxy object is connected with the slot in the workspace object. In connections of the first type, the signals are emitted by the workspace object and are connected appropriately.
The attach function: The ‘SWrapper::Workspace::attach’ function is what finally exports the workspace object as a QScriptValue to the QScriptEngine.
The exporting of objects
All the objects in the ‘SWrapper’ namespace have a corresponding exporting function, or a publishing function, which basically returns a QScriptValue that is the wrapped object. SWrapper::Workspace acheives this through the attach method. A few other objects, like KWin::Client for example, achieves this through the SWrapper::Client::generate object. These methods are static for singleton, floating and instantaible objects an instantiated object is needed to call this method.
Within this exporting function, the wrapper is supposed to add the various methods needed. Also, the entire SWrapper object is also exported (i.e. it’s signals and slots) using the QScriptEngine::newQObject method. As an example, let’s take the case of the SWrapper::Client object. To make a QScriptValue of a KWin::Client object, one can simply call QScriptEngine::toScriptValue
- Given the object, it first looks into the scriptCache to see if a value like this has already been generated. If it has been, the previously generated value is returned. In essence, a KWin::Client is wrapped exactly once and only a single QScriptValue exists for a given KWin::Client (for a script).
- If no such QScriptValue is found, it makes a call to SWrapper::Client::newWrapper, requesting that a KWin::Client object be wrapped. The newWrapper functions goes through three steps to return a QScriptValue, the details of which are documented in the file itself. Keeping that in mind, I would like to just skip to the 3rd part, where all the functions are added:
value.setProperty("caption", eng->newFunction(caption, 0), QScriptValue::Undeletable);
value.setProperty("close", eng->newFunction(close, 0), QScriptValue::Undeletable);
value.setProperty("resize", eng->newFunction(resize, 1), QScriptValue::Undeletable);
value.setProperty("move", eng->newFunction(move, 1), QScriptValue::Undeletable);
Simply put, this is where all the magic is added to the javascript objects. Let’s dissect a line here:
value.setProperty("caption", eng->newFunction(caption, 0), QScriptValue::Undeletable)
The first argument gives the name of the property that is being set, in this case ‘caption’. Then, it makes a call to QScriptEngine::newFunction(function, length). The function provided here is always a static function having the QScriptEngine::FunctionSignature signature. Please note that although even a global function would do, always make sure these functions are within the namespace of the SWrapper::Object. Let’s take the first function in question, i.e. ‘client.caption’.
Given a script, which does:
var x = workspace.getAllClients()[1]; print(x.caption());
- The getAllClients() method is called on the workspace object.
- The SWrapper::Workspace::getAllClients makes a list of all available clients and converts it to a list of QScriptValue’s which are wrapped Client objects.
- Then the variable ‘x’ is set to the second client in the list
- Then when x.caption() is called, the native function SWrapper::Client::caption is called, which again returns a QScriptValue which contains the caption of the given window i.e. ‘x’.
For further enquiry, let’s go into the caption function [native]:
QScriptValue SWrapper::Client::caption(QScriptContext* ctx, QScriptEngine* eng)
{
KWin::Client* central = eng->fromScriptValue<KWin::Client*>(ctx->thisObject());
if(central == 0)
{
return QScriptValue();
}
else
{
return eng->toScriptValue(central->caption());
}
}
Firstly, we extract the ‘thisObject’, which is nothing but a QScriptValue of the very object that SWrapper::Client::generate returned, and then we convert it to a KWin::Client object using the QScriptEngine::fromScriptValue<KWin::Client*>(client) method. This method also is facilitated in meta.(h|cpp). Once we have the object, we can simply read it’s caption and pass it back to the calling function. The return value of this function is a QScriptValue which is what the calling script gets on invocation.
Adding properties is also simple, you can read up the docs on Qt Assistant for that one.
The WorkspaceProxy object
Finally, we get to the mysterious WorkspaceProxy object. First, let me present the scenario:
- Every script results in a new QScriptEngine. Every QScriptEngine needs a unique SWrapper::Workspace object for it.
- There are 10 clients on the workspace, and every client (i.e. a KWin::Client) has a minimized signal.
- Now every, client’s minimized signal must cause the clientMinimized in every workspace object to be emitted.
This brings about two problems:
- Whenever a new client is added, all its slots must be connected to all existing SWrapper::Workspace object.
- Whenever a new script is run during runtime, all existing clients must be established connections to.
Hence, the WorkspaceProxy object was introduced. What it does is,
- Every client that is instantiated, connects it’s signals to the slots of WorkspaceProxy.
- The WorkspaceProxy re-emits a similar signal.
- All Workspace objects connects to the WorkspaceProxy’s signals.
Creating a WorkspaceProxy instance is simple. Let’s say there is a signal emitted from a client saying ‘sl_ateBacon’ and the signal to be re-emitted is ‘ateBacon’. Just add the declaration in the workspaceproxy.h file:
public slots:
sl_ateBacon(KWin::Bacon*);
signals:
ateBacon(KWin::Bacon*);
and in the workspaceproxy.cpp file, just add the following line in the constructor:
PROXYPASS1(ateBacon, KWin::Bacon*);
if ateBacon takes 2 or 3 parameters, one can use the macros PROXYPASS2 and PROXYPASS3. Also note that, since WorkspaceProxy is used to connect clients to a workspace, it will always carry a KWin::Client* parameter. This is not a hard and fast rule, but since there have been no functions without it, there is no macro definition for a signal-slot pair without parameters. Do feel free to add one on your own if the need arises.
Also do note that, the ‘sl_’ in front of the slot name is prefixed in front of the slot name automatically. So, whenever you are making slots, do follow the convention.
Coming in the next iteration: Handling inheritance (the KWin::Toplevel and the KWin::Client case), the scripting meta-system, publishing functions for instantiable classes.







