Tag Archives: kwin

A mighty congratulations to all the KWin developers

KWin, being my “gate” project to KDE and to the open source world, has a special place in my heart. I haven’t worked with the people who have been there with the project since it’s inception, but I have worked with the code they have written. I have had the privilege of working with Lubos Lunak and Martin Graesslin. After reading this article on Phoronix, which clearly shows the advanced and mature stage KWin is at, and the bleeding-edge performance it offers.

In recent times, Martin has been extremely active in including performance improvements, driver support, cleaning and coding the KWin effects and the KWin effects API. The amount of work he has put in has paid off which has clearly shown off in the Phoronix benchmarks. This goes out to… amazing stuff Martin.

At this point, I would also like to congratulate Thomas Lubking (Sorry, I could not find a relevant URL) for his countless patches to KWin. Every developer, Matthias Ettrich, Cristian Tibirna, Lubos Lunak, Daniel M. Duley and the contributors, amazing stuff people. My heart goes out to you :)

P.S: I sincerely apologize for any omissions in here, which are purely incidental.

Adding to KWin::Scripting

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:

  1. 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 workspace object 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 ClientGroup can be instantiated, and it also manifests into an actual KWin object i.e. KWin::ClientGroup. On the other hand, a QTimer is also instantiable, but it manifests into a scripting object only (also in an object on the C++ side), but not a KWin object.
  2. 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.
  3. Despite KWin::Client being a QObject, QObjects are never directly exposed to the scripting interface. This is largely in part due to the fact that not all methods are defined as Q_INVOKABLE and 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.
  4. All wrappers are available under the SWrapper namespace. For example, the wrapper of KWin::Client is SWrapper::Client present in scripting/client.(h|cpp).
  5. For every script that is running, we need a seperate QScriptEngine object. Every singleton object that is present needs to have a seperate instance of every singleton object as a QScriptValue is tightly binded to the QScriptEngine in question. In the beginning, I had a single QScriptEngine which 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 the WorkspaceProxy, 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:

  1. A new signal is created in KWin::ObjectA called s_ateBacon.
  2. A new slot is created in SWrapper::ObjectA called sl_ateBacon.
  3. 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.
  4. 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:

  1. 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.
  2. Once a global object has been set, we can add more objects to it. All other singletons and instantiables are added here.
  3. 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.
  4. 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:

  1. Connecting to signals emitted by the ‘KWin::Workspace’ object itself.
  2. 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(client). Every KWin::Client maintains a QHash called ‘scriptCache’, which maps a given QScriptEngine* to a ClientResolution. A ClientResolution is simply a QPair of a SWrapper::Client* and a QScriptValue. What is essentially happening here, is that it maintains a list of all the wrappers that have been generated for a given QScriptEngine. And this is what SWrapper::KWin::generate does:

  1. 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).
  2. 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());
  1. The getAllClients() method is called on the workspace object.
  2. 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.
  3. Then the variable ‘x’ is set to the second client in the list
  4. 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.

KWin Scripting tutorial

If you’re using KWin compiled from trunk, and if you regularly update it or have atleast updated it once in last 40-50 days your installation of KWin already has scripting support. If you’re reading this, you probably already know what benefits from scripting and I’ll just skip that and get to the real thing. So here comes the KWin Scripting tutorial.

EDIT: Fixed a few errors and a mistake in the code. Thanks to hunt0r for finding it out.

S1. The KWin script loading procedure

When KWin loads it looks in a folder for scripts it should run. This folder is given by: $KDEHOME/share/apps/kwin/scripts/. For me, this expands to: /home/kde-devel/.kde4/share/apps/kwin/scripts/, or in most cases, this should expand to something like: /home/<user>/.kde4/share/apps/kwin/scripts. Let’s call this directory ‘KWINSCRIPTS’ for now. All throughout this tutorial, I’ll be referring to this directory as KWINSCRIPTS.

Now, here is how KWin loads the scripts:

  • Locate the $KWINSCRIPTS directory.
  • Make a list of all the files in the $KWINSCRIPTS directory. For a file to be recognized and executed as a script, it must meet the following conditions:
    • It must have one of the following extensions: .kwinscript, .kws or .kwinqs
    • It must have the executable flag set.
  • For every file with name as ‘scriptfile.kwinscript’, ‘scriptfile.kwinqs’ or ‘scriptfile.kws’, a configuration file ‘scriptfile.kwscfg’ is loaded as its configuration file and all its keys and values are loaded.
  • All files recognized as scripts are executed one after the other.

In this tutorial, configuration files and it’s loading is not covered. Details about configuring your KWin scripts are available here.

S2. Setting up your development environment

For the purposes of this tutorial, we’ll create a script file ‘tutorial.kwinscript’ or anything you may prefer. Save it to the KWINSCRIPTS folder. Also, do set the executable bit for the scripting file using: chmod +x tutorial.kwinscript. In the script ‘tutorial.kwinscript’, enter the following code:

print("Hello World");

The phrase ‘Hello World’ may have become too old and repetitive, but we may break laws, we may break promises, but we never ever break tradition. So, Hello World it is. You may either have another account for KDE-development or you may be a KWin user having a single KDE installation. If you have a single KDE installation, I would suggest that you create another account for KDE-development, the details for which can be found here: http://techbase.kde.org/Getting_Started/Build/KDE4.x. This is so because KWin scripting is still in development and some nasty scripts may cause a crash or some other similarly crippling behaviour. Whenever testing a script or changes to a script, use a terminal to reload kwin. The reason to do so is so that you can read the output from a script on the terminal. Any scripting errors are also reported to the stdout, so it is helpful if you run it within a terminal. To load kwin, open Konsole and just type in:

kwin --replace &

This will reload the scripts and execute them again. For your first script, let us follow these steps:

  • First create a file in KWINSCRIPTS called ‘tutorial.kwinscript’.
  • Edit tutorial.kwinscript and add the following lines:
      print("Hello World");
      
  • Save the file, and set executable permissions on the file using chmod +x tutorial.kwinscript.
  • Open up Konsole (or switch to the user account you develop on and then start up Konsole) and type in the following line:
      kwin --replace &
      
  • In the terminal, you should see the line ‘Hello World’ printed. If you see it, Congrats! You’ve just written and run your first KWin script.

Output from the terminal is pretty useful when it comes to debugging your script. Here is how my terminal looks after attaching an event which prints a line everytime a client is minimized alongwith its caption:

This is the development environment you’ve setup and this is what we’ll be using for the rest of this tutorial. If KWin crashes, you can try moving your mouse over the Konsole window which launched KWin. This will most probably return focus to the window and then you can try entering kwin --replace & again. Since focusing is handled by the Window manager, when KWin crashes focus issues are common and you need a little bit of experience and luck at times :) . I suggest you read the KWin/HACKING file located here: http://tinyurl.com/kwinhacking.

S2/1. KWin scripting basics

To follow this tutorial, you must have some idea about ECMAScript [or JavaScript]. A quick introduction can be found on the KDE wikis under Plasma scripting tutorial.

Types: In KWin scripting, there are three kinds of variables:

  • Floating
  • Singleton
  • Instantiable

Instantiable objects are your regular objects, instances of which you can create using the var x = new X(); syntax. Examples of instantiable objects would be QTimer and ClientGroup.

Singletong objects are those objects who have only one instance of their type. For example, there is only one ‘workspace’ objects.

Floating objects, quite unique to the KWin scripting itself are objects that although have many instances, but none of them can be created. These can only be obtained as the return values of various methods.

Events: KWin scripting is completely event driven. Now there are quite a number of events within KWin scripting, and a lot more to be added. The thing to note here is that some events are availabe both system-wide and specific only. The previous sentence might seem a little confusing. Allow me to explain. Take the case of an event ‘A client is minimized’. KWin scripting provides two kinds of events:

  • workspace.clientMinimized is emitted system-wide i.e. whenever any client is minimized throughout the workspace.
  • myclient.minimized is specific to the object it is called from, in this case ‘myclient’. It is emitted only and only when ‘myclient’ is minimized.

To handle events, one must simply use the object.event.connect(..) syntax. Let’s say, whenever I minimize a client, I want to print it’s caption. Here’s the code:

workspace.clientMinimized.connect(function(client) {
    print(client.caption());
});

The print function prints to your terminal and is extremely useful for debugging. In the code snippet above, an anonymous function is connected to the event worksapce.clientMinimized, which is emitted whenever a client is minimized. The function which is called is passed as a parameter the client that was minimized. In case of the other event, no such parameter is passed, as we obviously know the specific client which is being called:

myclient.minimized.connect(function() {
    print("Minimize a client!!");
});

However, even if we, as programmers do know which client was minimized, the function cannot access that client’s object. This is because the function when it is called does not have the same scope as the connecting function. To overcome this, we can specify a ‘this’ object for the event handler as following:

myclient.minimized.connect(myclient, function() {
    print("Minimized: [" + myclient.caption() + "]");
});

To understand which parameters are passed to the event handlers (i.e. the functions we connect to), one can always refer the KWin API docs.

S3. Your first (useful) script

In this tutorial we will be creating a script based on a suggestion by Eike Hein (thanks Eike!). In Eike’s words: “A quick use case question: For many years I’ve desired the behavior of disabling keep-above on a window with keep-above enabled when it is maximized, and re-enabling keep-above when it is restored. Is that be possible with kwin scripting? It’ll need the ability to trigger methods on state changes and store information above a specific window across those state changes, I guess.”

Other than the really function and useful script idea, what is really great about this is that it makes for a perfect tutorial example. I get to cover most of the important aspects of KWin scripting while at the same time creating soemthing useful.

In this tutorial, we will cover the script in two sections: first, we will make the script using plain KWin scripting and in the 2nd part, we will implement the same functionality using Chelate. So let’s get on with it…

NOTE: To follow this tutorial, you must update your kwin from trunk very recently (minimum revision 1191158) as the primary events/methods used in this tutorial have been committed recently.

S3/1. The basic outline

Design statement: For every window that is set to ‘Keep Above’ others, the window should not be above all windows when it is maximized.

To do so, this is how we’ll proceed:

  1. Create an array of clients whose ‘Keep Above’ property is set.
  2. Whenever a script loads, make a list of all clients whose ‘Keep Above’ property is set and add it to the array.
  3. Whenever a client is maximized, if it’s ‘Keep Above’ property is set, remove the keepAbove property.
  4. Whenever a client is restored, if it is in the ‘array’, set it’s keep above property.
  5. Whenever a new client is added, check and see if it needs to be added to the array (depending on whether it’s ‘Keep Above’ property is set or not).

S3/1/1: The basic framework

So, for first steps, let us just create an array:

var ka_clients = new Array();

Now, we need to make a list of all available clients whose keep above property is set. From the KWin API docs you can lookup the workspace.getAllClients() method:

Allow me to present a small primer on how to read the KWin API docs. Here, the first line mentions the method, which is ‘workspace.getAllClients’. The ‘ret’ specifies the type of value that will be returned by the method. Here, it says ret: Array(client), which means that an array of client objects will be returned. It takes one parameter desktop_no, which specifies which desktop to fetch the clients from. The rest is clear from the description itself. So, to check all the clients whose ‘Keep Above’ property is set, we will use the following piece of code:

var c = workspace.getAllClients();

for(var i=0; i<c.length; i++) {
    if(c[i].keepAbove()) {
        print(client.caption() + " (yes)");
    }
}

Explanation:

  • First, it gets a list of all clients available from all the desktops and stores it in a variable ‘c’.
  • Then it loops over the entire array and checks for clients whose ‘keepAbove’ property is set. To do this, one can use the client.keepAbove method. It returns true if the given clients ‘Keep Above’ property is set.
  • Then it simply prints the clients caption.

Here, every element of ‘c’ is an object of type ‘client’. Every ‘client’ object represents a window. Here is how my terminal looks after writing this script:

Now, what we need to do, is add every such client to the array ka_clients. So this is how our code will look:

var ka_clients = new Array();
var c = workspace.getAllClients();

for(var i=0; i<c.length; i++) {
    if(c[i].keepAbove()) {
        ka_clients[ka_clients.length] = c[i];
    }
}

What ka_clients[ka_clients.length] does is append an element to that array.

S3/1/2: Manage maximization for clients Now that we have added it to an array, we need to make a provision that whenever the client is maximized, it’s ‘Keep Above’ property does not stay. So, we’ll modify the previous code a little:

function manageKeepAbove(type) {
    if((type.h == 1) && (type.v == 1)) {
        this.setKeepAbove(0);
    }
}

for(var i=0; i<c.length; i++) {
    if(c[i].keepAbove()) {
        ka_clients[ka_clients.length] = c[i];
        ka_clients.onMaximizeSet.connect(c[i], manageKeepAbove);
    }
}

For all events, there are two ways to connect it:

object.event.connect(h_function);
object.event.connect(f_this, h_function);

In either case, h_function is called whenever the event occurs, but in the second case, the function’s ‘this’ object is set to f_this. We use the second way because as you can see, multiple clients connect to the same function. So whenever a function is called, how does it know which client it must handle? Hence, we must specify the object it must ‘act on’, or in the other words, it’s ‘this object’.

client.onMaximizeSet is an event whenever a client is maximized. There are two parameters while specifying a maximization: horizontal maximization and vertical maximization. The function is passed only one value which has two properties ‘h’ and ‘v’. It specifies in which direction the client occupied the workspace dimension. We want the property to be triggered only when the client is maximized in both the directions. Note here that it is not necessary to write a function of our own in the global scope since we can use anonymous functions also. However, we are writing a function in the global scope so that we can even disconnect this slot later if needed.

Save and run this script and see if it works as one would expect it to.

S3/1/3. Managing clients whose properties which are dynamically set At this point however, if you set the ‘Keep Above’ property for a client, its property won’t be removed on maximization. This is because currently we are only covering the clients which are already present. To cover clients whose property is set after a script has been loaded, we’ll use the event workspace.clientSetKeepAbove. What we’ll do is, for every client whose ‘Keep Above’ property is changed, we’ll add it to the array if it is set, and we’ll remove it from the array if it is unset. This is how we’ll proceed:

workspace.clientSetKeepAbove.connect(function(client, set) {
    var found = ka_clients.indexOf(client);
    
    if(set == 1) {
        if(found == -1) {
            ka_clients[ka_clients.length] = client;
            client.maximizeSet.connect(client, manageKeepAbove);
        }
    } else if(set == 0) {
        ka_clients.splice(found, 1);
        client.maximizeSet.disconnect(manageKeepAbove);
    }
});

Explanation:

  • In the event handler for workspace.clientSetKeepAbove, we’ve used an anonymous function, as I mentioned was also possible previously. This is because we want this event handler to be alive throughout the execution of the script i.e. we don’t want to ever disconnect it. In such cases, anonymous functions can be used.
  • Here, we simply check for a client. If its property was set, we add it to the array (if it isn’t already there) and it was unset, we remove it from the array (if it is there in the array).
  • Also, whenever it is added to the array, we need to connect it’s maximizeSet event as we did previously.
  • Do note that we also disconnect the function if the ‘Keep Above’ property was unset. To disconnect, we need to provide a function name. This is the reason why we wrote a named function in the global scope instead of an anonymous function.

One point to note there is that, the client object is merely a ‘wrapper’ for an actual Client object (in C++). So then how does the ‘==’ operator work for the same object that was wrapped separately (the == relation is used to lookup up a value using the Array.indexOf() method)? Even though the object they wrap is the same, shouldn’t their wrapper be different? The answer is no. A scripting object to a kwin scripting object follows a strict 1:1 relation. Just as a wrapper can be mapped to a client, a client can be mapped back to a unique wrapper. This is achieved using script caching, details for which are here. This is why the ‘==’ and the ‘!=’ operator can be used safely to check if two variables actually wrap the same object or not.

S3/1/4. Restoring it all

Now the last and most important part of it all. Whenver the client is restored, we must set it’s ‘Keep Above’ property if it was set earlier. To do this, we must simply extend our manageKeepAbove code to handle this scenario. In case the client is not maximized both veritcally and horizontally, we check if the client is in our ka_clients arrray and if it is, we set its ‘Keep Above’ property, otherwise we don’t bother:

function manageKeepAbove(type) {
    if((type.h == 1) && (type.v == 1)) {
        this.setKeepAbove(0);
    } else {
        if(ka_clients.indexOf(this)) {
            this.setKeepAbove(1);
        }
    }
}

In the end, our entire script looks like:

var ka_clients = new Array();
var c = workspace.getAllClients();

workspace.clientSetKeepAbove.connect(function(client, set) {
    var found = ka_clients.indexOf(client);
    
    if(set == 1) {
        if(found == -1) {
            ka_clients[ka_clients.length] = client;
            client.maximizeSet.connect(client, manageKeepAbove);
        }
    } else if(set == 0) {
        ka_clients.splice(found, 1);
        client.maximizeSet.disconnect(manageKeepAbove);
    }
});

function manageKeepAbove(type) {
    if((type.h == 1) && (type.v == 1)) {
        this.setKeepAbove(0);
    } else {
        if(ka_clients.indexOf(this)) {
            this.setKeepAbove(1);
        }
    }
}

for(var i=0; i<c.length; i++) {
    if(c[i].keepAbove()) {
        ka_clients[ka_clients.length] = c[i];
        c[i].maximizeSet.connect(c[i], manageKeepAbove);
    }
}

Try out this script and check if it works according to your wishes. On my machine atleast it does :) Before going, let me give you a small excercise. Although we’ve covered all the cases, we surely have left one. Whenever I launch a new window, what if its ‘Keep Above’ property is already set? We surely need to manage that also.. HINT: Use the workspace.clientAdded property.

In the 2nd part of this tutorial, we will implement the same functionality using the KWin Chelate framework. I hope to finish the tutorial within a day or two.

For now, I guess this is all. Special thanks go out to Eike Hein. Also, for the KWin docs, the ugly program I wrote is no longer used. Martin Graesslin wrote a nice XSLT stylesheet for handling the XML -> HTML conversion. It is present in the trunk under ‘apidocs.xsl’. Thanks Martin :)

KWin Scripting API documentation [or fun with Qt and XML]

Martin Graesslin was kind enough to remind me of my responsibilities towards the software projects I participate in, most particulary “do NOT forget documentation”. So, I told him, that given 2 days, I’ll finish writing a small tutorial good enough to put devs on a starting track for KWin Scripting. However, as I sat down to write it out, it felt that the tutorial wouldn’t be as effective as it would be with nice API docs in place. So, for the first day of my grace, I began writing the API documentation for KWin Scripting.

IMPLIST although simple, wasn’t really easy-to-parse anymore. Plus I hadn’t provided enough documentation for the parameter lists, count and meanings over there. And without knowing more about arguments, scripting seemed impossible. So today I started out writing the documentation in a custom XML format and I kept on adding to the XML format as I proceeded. In the end I had covered quite an amount of documentation and the format seemed pretty portable and convenient to use. To save time, I also made use of some quick hacks.

The documentation in the XML format is available here. Then I wrote a small program to convert this XML file into a nice little formatted HTML file. I decided to use DOM over SAX, simply because it sounded easy for the task at my hand, however, now that I look at it, SAX could’ve been just as easy (or maybe, easier). The conversion program is *ugly*. And I mean it. There is a lot of code duplication, variable names I’ve completely lost track of and it’s overly unsystematic. But hey, it works, and does stuff under 4ms, so I’m happy :) . Plus I just wanted to get this API docs generated quickly, so I didn’t really care. Maybe, I’ll rewrite it sometime into something more extensible and elegant. The ugly abomination of code can be seen here.

And now, finally coming down to the docs, here you go. It’s just a listing for now, but I’ve added enough meta to add JavaScript filters and better navigation. Also, It’ll be available on techbase. Expect it soon. The tutorial should come tomorrow (I know I’ve said this before), but this time, I’m quite resolute (yes, I’ve said this also before).

The science gets done, and we make a neat gun :)

Well, as of now, the trunk of kwin contains scripting support. Around 2 hours back, I merged the branch to trunk and have committed the changes, so the next time you checkout trunk and compile, you’ll have with yourself, kwin powered with scripting support :)

The commit message (something I had prepared for quite some time) that went with it was:

Trembles the weave as the clock ticks, attain another microstate.
Every disorder causes every duration, which ensures the one that stays.
reality is relative. natural is disorder.
[R]obinhood[P]andey

Expect a small documentation from my side by tomorrow giving a quick primer on as to how to write scripts, but if you’d like to check for yourself, checking a few posts on my blog might be able to get you a headstart. For a quick start, here’s how to go:

  • Your script must have an extension of either .kwinscript, .kwinqs or .kws.
  • Your script MUST have executable permissions. The scripting engine ignores any non-readable and non-executable files
  • The location of your scripts must be in $KDEHOME/share/apps/kwin/scripts. All scripts are read from this location.
  • A quick overview can be found at IMPLIST. The documentation is quite lacking, it doesn’t clearly mention the arguments, but a better documentation is in progress.

Report additions

As far as bugs and performance issues goes, any method is just as fine, including the bug tracker or a simple email to me or the KWin mailing list. However, for scripting support a lot is dependent on the amount of exposure of the internal system to the scripting environment and hence there are times when you wish that there were a function to do something. Let’s say, you would really like to have a function that gets the current desktop [don't worry. such a function does already exist]. In that case, please mention a suggestion in the following format:

Type: function / event / helper [depending on what exactly you'd like to see]
Prototype: getDesktopNumber
Already exists: Yes / No [specify a 'Yes' if you want to suggest a modification or a rename]
Internal function: KWin::Workspace::desktop() [The internal function, if you know it, otherwise just leave it blank]
Description: [Please give a concise description of the required functionality, including the parameters the function takes or the event returns]

I’d be really thankful if you keep such suggestions coming, and one of the only reason there aren’t enough functions is that I couldn’t come up with enough :) . So please, do contribute your ideas and suggestions. If you’d like to help me with documentation, I’m really looking forward to it, just ping me.

[KWin Scripting] End of GSoC, and a few more changes

Firstly, let me just mention, that I have successfully passed my evaluation for GSoC and am awaiting just my certificate and t-shirt :) Before I proceed with the rest of this blog, here are a few people whose help I would like to acknowledge:

  • Lubos Lunak My GSoC mentor
  • Martin Greasslin One of the principal guys at KWin
  • Abhisek Mishra For providing me hosting for my blog for the GSoC tenure :)

After taking a break for almost around a week, I started coding again yesterday, and mostly I looked around the code for memory leaks and tweaks. For this blog post, I have a very small list of changes:

EmitFurther

Let’s take the following piece of code:

client.clientMoved.connect(client, function() {
    client.move(100, 100);
});

client.move(200, 200);

What one would see is that after the second client.move call, the client is actually pinned to (100,100) instead of (200,200). This happens because, whenever I call client.move(200, 200), the corresponding event gets fired, which sets the client position again to (100,100). Also, if such an event was applied to a rapidly moving window (say, some other script is animating it), then not only would the other script NOT want it’s animation disturbed by interfering scripts, but also the following error is a possibility: “Maximum call stack size exceeded”. Hence, the 3 geometry functions now support an additional parameter, called emitJs. If emitJs is true, then the event is fired, otherwise not. If emitJs is not supplied, the parameter defaults to true. For example, the following code:

client.clientMoved.connect(client, function() {
    client.move(100, 100);
});

client.move(200, 200, false);

Will actually pin the client to (200, 200). Also, to avoid making excessive calls, for any functions of the format:

client.clientMoved.connect(client, function() {
    client.moved OR client.setGeometry OR client.resize
});

ALWAYS set the 3rd parameter to false.

The ch.check chelation

Earlier, to check for boolean values using a chelation, one would have to use:

var x = ch.rule({
    on: [
        "clientMaximizeSet",
        "clientFullScreenSet"
    ],

    filter: ch.equiv("isNormal", true),

    action: //Something here
});

Instead of using ch.equiv(“isSomething”, true), one can now use the ch.check function:

ch.check("isSomething")

It will basically return the boolean converted value of whatever “isSomething” returns. Also, to check for “isNotSomething” using “isSomething”:

ch.not(ch.check("isSomething"))

TODO

  • A watchdog timer to check that scripts don’t run for too long
  • Improving the chelate system to be able to incorporate addition of filters, eventstrings etc.
  • [PRIORITY] Add support for keyboard and mouse events

So currrently, I’m working with keyboard and mouse events, as well as I’m working on a small prototype or a base for the DBus scripting interface for both plasma and KWin. Aaron Seigo has very kindly agreed to take me under his wings for this particular job and will be reviewing my code (and he will be the primary coder) :)

No more –stest, configure scripts, many scripts

Well, the deadline of 16th August is coming near and KWin scripting seems to be chugging along at a good enough pace. For the last two weeks I’ve moved away from implementations and spicing up the core and have been mostly involved in providing a pretty interface and making the branch ready to be pushed for production. Most importantly, earlier KWin could run only one script because it had only one QScriptEngine and as it turns out, more than one QScriptEngine wasn’t so easy to add as I had thought. Now the system looks something like this:

  • KWin::Scripting looks for all files ending with either .kws, .kwinscript or .kwinqs and loads them
  • Each script is provided with a QScriptEngine, a configuration system and then runs it
  • Each QScriptEngine uses it’s own objects, namely SWrapper::Workspace, QTimer, SWrapper::ClientGroup etc. All objects are associated strictly with the engines they run on
  • meta.cpp spices up each engine (look for my next post where I plan to talk about meta.cpp)
  • And now, you can provide configuration parameters for scripts from ini files. This is a small overview of how it all works:

    • For every file detected as a script, a corresponding file is opened which has the same name as the script file, but ends with .kwscfg. For example, if I was running pong.kwinqs, then the file pong.kwscfg would be the configuration file
    • Each script has access ONLY to it’s own configuration. The ‘config’ object is provided to each script to easily access the configurations. The objects is as follows:
      • config.loaded : true if a configuration file was found and opened, false otherwise.
      • config.exists(param) : true if the given parameter was found in the config file, false otherwise
      • config.get() : Gets the values of the provided keys. There are 4 ways of using this function. Continue reading for more details.

    And for config.get function, let’s go over an example. Let’s say for the pong game, I make the following configuration file:

    pong.kwscfg

    paddleSpeed=100
    aiDifficulty=10
    timerResolution=0.1
    scoreBoard=true
    playerName=thirtySeven

    And then I run pong.kwinscript. Let’s look at what all ways one can get the configuration objects:

    config.get()Gets all the available keys and values in an associative array:

    var z = config.get();
    print_r(z);


    OUTPUT
    ######
    Array [
    "paddleSpeed" : 100,
    "aiDifficulty" : 10,
    "timerResolution" : 0.1,
    "scoreBoard" : true,
    "playerName" : "thirtySeven"
    ]

    Do note here that, a string object is still a string and a number is a number. This happens according to whichever way QSettings deems appropriate.

    config.get(“aiDifficulty”, “playerName”) will return:

    Array [
    "aiDifficulty" : 10,
    "playerName" : "thirtySeven"
    ]

    config.get(“playerName”) will simply return only ONE value as the value String "thirtySeven"

    config.get(new Array(“playerName”, “timerResolution”)) will again return an associative array.
    config.get(new Array(“playerName”, “timerResolution”), 1) will return an integer indexed array:

    Array [
    "thirtySeven",
    0.1
    ]

    Also for config.exists:


    config.exists("playerName"); //returns bool(1)
    config.exists("computerName"); //returns bool(0)

    Among architectural changes, the caching system had to be changed so that one could use it with multiple QScriptEngine’s. The problem is that a QScriptValue is associated with a QScriptEngine and hence multiple objects must be stored in the cache, one for each engine. The lookup is still fast and performance has not been affected much, definitely not to perceptible levels.

    SWrapper::Workspace is no longer a singleton, but a new object is instantiated for every QScriptEngine. A lot of other changes had to be made for the whole thing to work, but that’s mostly small tweaks and minor changes. If you’re interested, I encourage you to check out the source code.

    The only thing right now is that client events emitted by the workspace object are not working as of now [for example, workspace.clientMinimized] as they used to depend on the fact that only one workspace object exists. This has caused quite a problem and there is a solution as of now, to use a WorkspaceProxy object for creating M:N mappings. I’ll write more on it once I’m done with it, I’ll write more on it.

    Progress seems good and if things go on this way, we’ll be pushing for production in no time :)

    UPDATE: KWin scripting is enabled be default. To disable, run kwin --noscript.

[KWin Scripting] Window tabbing and a new structure

It’s been a very long time since I made a post, mostly because my college started this week which meant I lost around a week in traveling, registration and the whole process of ‘setting in’. Either ways, since my last post, a lot of changes in KWin. Actually, a very important change has been made in the entire structure of how clients are generated and currently things are looking very neat and much more systematic.

Clients and toplevels

In KWin, the Client object inherits from Toplevel. Toplevel is the class for all clients, whereas Client handles managed clients. Now, I could just directly wrap Client and not really care about Toplevel, but doing these things separately enables me to reuse the code for Toplevel for unmanaged clients as well if I plan to. I have always been doing these two things separately, but my previous method was pretty tacky.

Previously: Earlier, the SWrapper::Client object and SWrapper::Toplevel classes were just standalone classes that both used to generate QScriptValues. So, inside the SWrapper::Client::generate function:

  • Firstly, it used to call the SWrapper::Toplevel::generate function and obtain a toplevel scriptobject, let’s call it so_toplevel
  • Then it generates a QScriptValue for the KWin::Client in question.
  • Both the above functions use a pointer to the KWin::Client* object and KWin::Toplevel* respectively, so that process is actually pretty simple.
  • Then both these objects are merged using a helper function defined in scripting/meta.cpp

The merging took some time and there was no reason why I shouldn’t just generate a toplevel object first and then build the client methods on it. The problem is that, since I am generating the script object from a QObject, and I cannot give a previous object to build on. Whichever way it is, it was ugly and things had to change.

Now: The things now work like this:

  • SWrapper::Client now inherits from SWrapper::Toplevel
  • SWrapper::Client first generates an object and then SWrapper::Toplevel appends to it. This is the exact reverse of how inheritance works, but there is a reason to it. Read Rationale below
  • The final object is then returned.

Rationale: First the QObject must be wrapped using QScriptEngine::newQObject and then more methods can be added. Hence, when using a qscriptvalue_cast, the object converted from is the object that the QScriptValue can be converted to. So, if a convert a MyObject* to QScriptValue, then I can ONLY cast it back to MyObject*. So for a QScriptValue generated from KWin::Client* can be converted only KWin::Client*. Now, I must ensure that KWin::Toplevel* is not at all aware of what object inherits it, it must work only on a toplevel. Which is why, if I generate the ScriptValue from KWin::Toplevel*, I cannot convert it to KWin::Client*. However, KWin::Client* can be converted to KWin::Toplevel* since it is an inherited object.

Window Tabbing

I’ve finished adding support for Window tabbing from within scripting and almost all methods from clientgroup.cpp have been implemented with the same parameter sequence other than for a few changes. A list of all the the available methods till now are:

# (m) clientgroup.ClientGroup (ctor) : Constructor for a new ClientGroup object. Must pass a client object, otherwise returns an invalid scriptvalue.

# (m) clientgroup.add : Add a client to a client group. Expects 1 parameter.

# (m) workspace.clientGroups : Returns an array of clientGroups currently present in the workspace.

# (m) clientgroup.remove : Removes the client specified by the parameter which is either a client object or an indexof the client in the clientlist.

# (m) clientgroup.clients : Returns an array of clients which are members of the clientgroup.

# (m) client.clientGroup : Returns the clientgroup a given client belongs to, or an undefined scriptvalue otherwise.

# (m) clientgroup.contains : Accepts a client parameter, and returns true the client is contained within the clientgroup

# (m) clientgroup.indexOf : Returns the index of the parameter 'client' within the clientgroup

# (m) clientgroup.move : Move a client within the group. Accepts move(client, client), move(index, index), move(index, client), move(client, index). All calls except move(client, client) are eventually mapped to move(index, index) using indexOf(client)

# (m) clientgroup.removeAll : Removes all clients from a group and assigns them individual groups.

# (m) clientgroup.closeAll : Closes all the clients in a group.

# (m) clientgroup.minSize : Returns the minimum size cumulative of all the clients in the clientgroup.

# (m) clientgroup.maxSize : Returns the maximum size cumulative of all the clients in the clientgroup.

# (m) clientgroup.visible : Returns the currently visible client in the clientgroup.

A ClientGroup must be initialized with an existing client as a parameter. If the client already belongs to a ClientGroup, that ClientGroup is wrapped, otherwise a new one is made. However, if the new ClientGroup is made with a parameter ‘detach’:

var x = new ClientGroup(myClient, /*detach*/ 1);

then, myClient is removed from the ClientGroup and then added to a new ClientGroup of it’s own.
Also, clientgroup.cpp supports two methods to rearrange tabs: move(client, client) and move(index, index). From scripting one can use:

move(client, index) ; move(client, client) ; move(index, client) ; move(index, index)

Events for the same are still to be added.

BetterMathematica.kwinscript
I wrote a small script to arrange mathematica better when it loads: BetterMathematica.kwinscripting

It is a simple script, which arranges the basic math input palette on your left and sets up the ‘Classroom Assistant’, ‘Writing Assistant’ and all in nice little tabs on the right and the rest of the window takes up the rest of the space.

Here are a few screenshots of how things look before and after the script:

Before Script:

After the Script:

Eventually, Mathematica looks a lot better and is much easier to work with now. Other than that, client.windowInfo now returns a lot more data including windowClassClass, windowClassName, windowRole and so on. Do refer to SCRIPTING/IMPLIST for a list of all implemented events.

Also, ClientGroup marks the first time we’re creating an instantiable object i.e. objects that can be created. Do leave your comments and opinion, as this time I’ve made something which would actually be useful as compared to KWin|PONG, which was well, far from useful :)

[KWin Scripting] KWin|Pong: play your window

EDIT: Sorry for the downtime, but the website is back up again :)

I apologize for the video looking very choppy.. but I couldn’t really help it. After I ran recordmydesktop, it just sucked my CPU into a blinding vortex. And that was after I used a polling interval of 160ms. Either ways, I have also added a video taken from my phone’s camera at the end of this video to show that the performance is actually not all that bad.

Till now whenever I had to test something done with KWin scripting, I just used to add a few little lines to the defaultscript that runs and then just check it out. As such, I was testing everything one thing at a time. I just wanted to write a bigger script, something that used a set of these implemented things to do something.. and what better symbol of nerdism than pong? So, I went ahead and wrote a pong game.. all done in QtScript. The video is for all to see, and a few things to note are:

1. The pong game is written entirely using scripting.
2. I did not and absolutely did not add anything from the C++ side to facilitate anything.
3. The performance was actually close enough to what I would get from native code, even at a polling interval of as low as 30ms.
4. The QTimer class used in this game are linked from plasma, marking the first step in linking of plasma script bindings into KWin scripting. Although it may look a pretty natural thing, it somehow happens to be a big deal to me :)

KWin|Pong from Rohan Prabhu on Vimeo.

What is going on:

1. When loaded, the pong game does all the output to the console.
2. It asks the player to select the various controls in the game. In the game I’ve selected a Firefox browser as my paddle, and a Chrome window as my other paddle. A quite unique scenario of browser wars. And a nice little xterm window plays the ball and a konsole window shows me the score.
3. To ‘select’ a window for a purpose, I just have to bring on the focus to the window.
4. I move the firefox window with my mouse and it is my paddle. The movements are restricted to this window, meaning I can move it only along the y-axis. It always stays snapped to the left edge of the screen.
5. The chrome window uses a very stupid and rudimentary AI which was born to lose.
6. Whenever a player scores, the konsole window shows up the score and the party which scored.

Technically,

1. The selection of different windows is done using the workspace.clientActivated event, which is evoked whenever a client receives focus.
2. The resizing, moving is done using a variety of available geometry functions for client objects.
3. The player paddle is constrained using the client.moved event.

Hope you like it! Again, sorry for the choppy video. The script of the game is available here [kwinpong.txt], or if you prefer syntax highlighted, here [kwinpong.txt (hl)]. Video editing was done in kdenlive and the audio editing was done in Audacity

And a screenshot to wrap it up:

[KWin Scripting] A little profiling and a small debate

In my previous post, I mentioned about caching and how it was implemented. While doing so, with the intention of providing a rationale for using such a system, I made a hypothesis saying that caching of objects would make things run faster with a smaller memory footprint. Well, such a conclusion was pretty intuitive, but a programmer cannot deem it conclusive till we have numbers supporting this hypothesis. So, with the QTime class in my toolkit, I made a very rudimentary setup that would measure the time taken to run the defaultscript. The ‘defaultscript’, as I would call it is the script that currently runs when KWin is launched with the –stest argument. It’s just a for-now provision for making development a little easier. The setup was:

if(sargs->isSet("stest")) {
    stestWindow = new ScriptTesting();
    stestWindow->setEngine(&scripting);
    QTime t;
    t.start();
    stestWindow->runDefault();
    qDebug()<<"~/.kwin/scripting/defaultscript executed in"<<t.elapsed()<<"ms";
}

For the given situation atleast, it provided a good enough benchmark. As for memory consumption and CPU, I just monitored System Activity using KSysGuard. Given below are the results:

Memory

This is the memory used by KWin when scripting wasn’t enabled at all:

Memory footprint without scripting

To actually test the scalability, I used a very gruesome defaultscript:

var clients = new Array();

for(var x=0; x<100; x++) {
    clients[x] = new Array();
    for(var y=0; y<100; y++) {
	clients[x][y] = workspace.getAllClients();
    }
}

In essence, it creates an array of 100 Arrays, with each array containing 100 client objects. So the memory goes into these 10,000 variables and their corresponding 10,000 client objects which again mean 10,000 wrapper objects [if caching is not enabled]. With scripting enabled [with caching], when the script ran, the memory results were:

Memory footprint with scripting enabled, with caching on

With so many objects out there, a 2 mb increase in memory footprint is pretty much acceptable [and understandable]. My biggest interest for implementing caching was to reduce the amount of time a script took to run by bypassing the object generation cycle. As far as memory is concerned, 10,000 objects mean 10,000 QScriptValue’s no matter what and hence I didn’t expect to see a considerable difference in the memory footprint. Caching is pretty much hardcoded right now and I can’t just say ‘caching = false’ and switch it off. Hence, what I did was, for every lookup, just return a value saying that the lookup was unsuccessful i.e. force a new generation. To be fair, the lookup was completely bypassed and hence the time taken between the call and return was negligible. And the results:

OMG

Yes, the numbers are true and no, this isn’t photoshopped [or GIMPed, as I must say in the FOSS world]. During the execution of the script the CPU usage stayed throughout at 85-95%, while with caching enabled, the CPU usage stayed at a mere 5-6%. As far as CPU usage goes, the numbers aren’t very indicative of performace, as a high load-time CPU% does not say anything about how the system is going to perform throughout it’s execution time, but still, you get the idea.

Time for execution

This was the part I was dying to test, and without saying much, I’d just let you in on the details:

[Caching enabled] ~/.kwin/scripting/defaultscript took 105 ms to execute
[Without caching] ~/.kwin/scripting/defaultscript took 26567 ms to execute

Well, I need not give a long paragraph saying what this means, so I’ll just let the numbers speak for themselves.

No tables for caching

My mentor [Lubos Lunak] objected to me using Hashtables for caching. He said rather than associate KWin::Client objects with SWrapper::Client* in a hashtabe, why don’t I just store pointers to the wrapper object from the client object and for objects not yet cached, let the cient objects have a null pointer. We had quite a debate at that time and the resolution of the ‘meeting’, if you will was Status quo ante bellum. However, during the profiling excercise I was involved in, I tried this method also and recorded the changes in memory usage and CPU consumption. As it turns out, the memory usage showed hardly any change, as was obvious, since the amount of data we’re storing is still the same. The only difference is due to the overhead of one QHash object.

However, when it came to time, the method with hash tables took an approximate average 110ms to run [averaged over 8 iterations] whereas the method without hash tables took an approximate average 88ms to run [averaged over 8 iterations]. And it’s not just the time, but it brought about a great deal of simplicity in the entire program. So as of now, and in the current code in the trunk, hash tables are no longer used for caching, rather we are using something like a distributed (key, value) pattern which is amazingly simple and provides quite a performance incentive.

And to overcome the problem of possible segfaults, I am now using the QObject’s parent-child system to ensure that each wrapper is a child of the client object it wraps thereby getting destroyed whenever the client gets destroyed. In this manner, I actually don’t have to worry about deleting the wrapper object. ever.

That’s all I’m writing for now, the rest I’m just keeping in my blog post, which I should be done with by tomorrow. Till then, here’s an interesting tech support story I’d like to share Airtel called me