Monthly Archives: July 2010

[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: