Difference between revisions of "Development/Tutorials/KWin/Scripting"

Jump to: navigation, search
(Adapted from http://rohanprabhu.com/?p=116)
 
m (Simplified code in examples a little.)
 
(9 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
= KWin Scripting Tutorial =
 
= KWin Scripting Tutorial =
  
== The KWin script loading procedure ==
+
{{warning|KWin's Scripting functionality has been reworked for KDE Plasma Workspaces 4.9. This tutorial does not cover the previous API. In case you had an existing script, please consult [[Projects/KWin/Scripting_Update_Notes|Scripting Update Notes]] for migration note.}}
  
When KWin loads it looks in a folder for scripts it should run. This folder is given by: ''$KDEHOME/share/apps/kwin/scripts/''. Let’s call this directory ''KWINSCRIPTS'' for now.
+
== Quick Start: Desktop Console ==
 +
The easiest way to test KWin scripts is to use the Plasma Desktop Scripting Console which can be opened via the KRunner window (Alt+F2, by default, or via the "Run Command" entry in various desktop menus) by entering "wm console" as the search term It can be triggered directly via dbus with <syntaxhighlight lang="bash">qdbus org.kde.plasma-desktop /MainApplication showInteractiveKWinConsole</syntaxhighlight>
  
Now, here is how KWin loads the scripts:
+
{{note|This method is not available for plasma-netbook.}}
  
*Locate the ''$KWINSCRIPTS'' directory.
+
The interactive console allows to send a script to the window manager which is directly loaded and executed. All debug output is displayed in the scripting console. This provides a very easy way to develop and test the script to be written. It is important to know that scripts executed from the scripting console are only used by the window manager as long as the window manager is running. In a new session the script has to be sent to the window manager again.
*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 [http://rohanprabhu.com/?p=85 here].
+
== Packaging KWin Scripts ==
 +
In order to have KWin load a script on each session start the script has to be packaged. KWin Scripts use the [[Development/Tutorials/Plasma/PackageOverview|Plasma Package]] format. The recommended file ending for KWin Script packages is '''.kwinscript'''. In the metadata.desktop file of the package the value for "X-KDE-ServiceTypes" has to be '''KWin/Script''', as "X-Plasma-API" only '''javascript''' and '''declarativescript''' are supported.
  
== Setting up your development environment ==
+
A packaged KWin Script can either be installed via the KWin Script KCM (note: the list does not yet reload after installing a script) or with the plasmapkg tool: <syntaxhighlight lang="bash">plasmapkg --type kwinscript -i /path/to/myscript.kwinscript</syntaxhighlight>
  
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 <code>chmod +x tutorial.kwinscript</code>. In the script ''tutorial.kwinscript'', enter the following code:
+
After installation the script has to be enabled in the KWin Script KCM. The window manager will load the newly installed script directly at runtime as well as in new sessions.
  
<syntaxhighlight lang="javascript">
+
== Where can I find example scripts? ==
print("Hello World");
+
A few KWin Scripts are shipped directly with the window manager. You can find those in your system installation. Just use plasmapkg to get a list of the available scripts:
 +
<syntaxhighlight lang="bash">
 +
plasmapkg -t kwinscript -l
 
</syntaxhighlight>
 
</syntaxhighlight>
  
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 [http://techbase.kde.org/Getting_Started/Build/KDE4.x here]. 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:
+
The scripts can be found in the data install path of your local KDE installation under "kwin/scripts". E.g. in '''~/.kde/share/apps/kwin/scripts/'''.
<code>kwin --replace &</code>
+
 
+
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 line:
+
 
+
<syntaxhighlight lang="javascript">
+
print("Hello World");
+
</syntaxhighlight>
+
 
+
*Save the file, and set executable permissions on the file using <code>chmod +x tutorial.kwinscript</code>.
+
*Open up '''Konsole''' (or switch to the user account you develop on and then start up '''Konsole''') and type in the following line: <code>kwin --replace &</code>
+
*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:
+
 
+
[[File:Kwintut1.png]]
+
  
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 <code>kwin --replace &</code> 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 [http://community.kde.org/KWin/Hacking here].
+
Additionally you can find the scripts in the [https://projects.kde.org/projects/kde/kde-workspace/repository/revisions/master/show/kwin/scripts KDE-Workspace repository] and some example scripts in the [https://projects.kde.org/projects/kde/kdeexamples/repository/revisions/master/show/kwin/scripts KDE Examples repository].
  
 
== KWin scripting basics ==
 
== KWin scripting basics ==
Line 50: Line 31:
 
To follow this tutorial, you must have some idea about [http://en.wikipedia.org/wiki/ECMAScript ECMAScript] (or [http://en.wikipedia.org/wiki/JavaScript JavaScript]. A quick introduction can be found in the [http://techbase.kde.org/Development/Tutorials/Plasma#JavaScript Plasma scripting tutorial].
 
To follow this tutorial, you must have some idea about [http://en.wikipedia.org/wiki/ECMAScript ECMAScript] (or [http://en.wikipedia.org/wiki/JavaScript JavaScript]. A quick introduction can be found in the [http://techbase.kde.org/Development/Tutorials/Plasma#JavaScript Plasma scripting tutorial].
  
===Types===
+
KWin Scripts can either be written in [https://qt-project.org/doc/qt-4.8/scripting.html QtScript] (service type "javascript") or [https://qt-project.org/doc/qt-4.8/qdeclarativeintroduction.html QML] (service type "declarativescript"). In order to develop KWin Scripts you should know the basic concepts of signals and properties.
In KWin scripting, there are three kinds of variables:
+
  
*Floating
+
=== Global Objects and Functions ===
*Singleton
+
*Instantiable
+
  
 +
KWin Scripts can access two global properties '''workspace''' and '''options'''. The workspace object provides the interface to the core of the window manager, the options object provides read access to the current configuration options set on the window manager. To get an overview of what is available, please refer to the [[Development/Tutorials/KWin/Scripting/API_4.9|API documentation]].
  
'''Instantiable''' objects are your regular objects, instances of which you can create using the <syntaxhighlight lang="javascript">var x = new X();</syntaxhighlight> syntax. Examples of instantiable objects would be ''QTimer'' and ''ClientGroup''.
+
The following global functions are available to both QML and QtScript:
 +
* '''print(QVariant...)''': prints the provided arguments to stdout. Takes an arbitrary number of arguments. Comparable to console.log() which should be preferred in QML scripts.
 +
* '''readConfig(QString key, QVariant defaultValue=QVariant())''': reads a config option of the KWin Script. First argument is the config key, second argument is an optional default value in case the config key does not exist in the config file.
  
'''Singletong''' objects are those objects who have only one instance of their type. For example, there is only one ''‘workspace’'' objects.
+
=== Clients ===
 +
The window manager calls a window it manages a "Client". Most methods of workspace operating on windows either return a Client or require a Client. Internally the window manager supports more types of windows, which are not clients. Those windows are not available for KWin Scripts, but for KWin Effects. To have a common set of properties some properties and signals are defined on the parent class of Client called '''Toplevel'''. Be sure to check the documentation of that class, too when looking for properties. Be aware that some properties are defined as read-only on Toplevel, but as read-write on Client.
  
'''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.
+
The following examples illustrates how to get hold of all clients managed by the window manager and prints the clients' caption:
 
+
<syntaxhighlight lang="javascript">
===Events===
+
var clients = workspace.clientList();
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:
+
for (var i=0; i<clients.length; i++) {
*'''workspace.clientMinimized''' is emitted system-wide i.e. whenever any client is minimized throughout the workspace.
+
  print(clients[i].caption);
*'''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 <syntaxhighlight lang=javascript>object.event.connect()</syntaxhighlight> syntax. Let’s say, whenever I minimize a client, I want to print it’s caption. Here’s the code:
+
 
+
<syntaxhighlight lang=javascript>
+
workspace.clientMinimized.connect(function(client) {
+
    print(client.caption());
+
});
+
 
</syntaxhighlight>
 
</syntaxhighlight>
  
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 '''workspace.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:
+
The following example illustrates how to get informed about newly managed clients and prints out the window id of the new client:
 
+
<syntaxhighlight lang="javascript">
<syntaxhighlight lang=javascript>
+
workspace.clientAdded.connect(function(client) {
myclient.minimized.connect(function() {
+
  print(client.windowId);
    print("Minimize a client!!");
+
 
});
 
});
 
</syntaxhighlight>
 
</syntaxhighlight>
 
+
To understand which parameters are passed to the event handlers (i.e. the functions we connect to), one can always refer the [[Development/Tutorials/KWin/Scripting/API_4.9]].
To understand which parameters are passed to the event handlers (i.e. the functions we connect to), one can always refer the [[Development/Tutorials/KWin/Scripting/API]].
+
  
 
== Your first (useful) script ==
 
== Your first (useful) script ==
Line 99: Line 72:
  
 
To do so, this is how we’ll proceed:
 
To do so, this is how we’ll proceed:
#Create an array of clients whose '''‘Keep Above’''' property is set.
+
#Create an array of clients whose '''‘Keep Above’''' property has been removed for maximized windows
#Whenever a script loads, make a list of all clients whose '''‘Keep Above’''' property is set and add it to the array.
+
 
#Whenever a client is maximized, if it’s ''''‘Keep Above’''' property is set, remove the '''‘Keep Above’''' property.
 
#Whenever a client is maximized, if it’s ''''‘Keep Above’''' property is set, remove the '''‘Keep Above’''' property.
 
#Whenever a client is restored, if it is in the ‘array’, set it’s '''‘Keep Above’''' property.
 
#Whenever a client is restored, if it is in the ‘array’, set it’s '''‘Keep Above’''' property.
#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).
 
  
 
==== The basic framework ====
 
==== The basic framework ====
 
So, for first steps, let us just create an array:
 
So, for first steps, let us just create an array:
 
<syntaxhighlight lang=javascript>
 
<syntaxhighlight lang=javascript>
var ka_clients = new Array();
+
var keepAboveMaximized = [];
 
</syntaxhighlight>
 
</syntaxhighlight>
Now, we need to make a list of all available clients whose '''‘Keep Above’''' property is set. From the [[Development/Tutorials/KWin/Scripting/API|KWin API docs]] you can lookup the '''workspace.getAllClients()''' method:
 
  
[[File:Kwintut2.png]]
+
Now we need to know whenever a window got maximized. There are two approaches to achieve that: either connect to a signal emitted on the workspace object or to a signal of the client. As we need to track all Clients it is easier to just use the signal '''clientMaximizeSet'' on the workspace. This signal is emitted whenever the maximization state of a Client changes and passes the client and two boolean flags to the callback. The flags indicate whether the Client is maximized horizontally and/or vertically. If a client is maximized both horizontally and vertically it is considered as fully maximized. Let's try it:
 
+
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:
+
  
 
<syntaxhighlight lang=javascript>
 
<syntaxhighlight lang=javascript>
var c = workspace.getAllClients();
+
workspace.clientMaximizeSet.connect(function(client, h, v) {
 
+
  if (h && v) {
for(var i=0; i<c.length; i++) {
+
     print(client.caption + " is fully maximized");
     if(c[i].keepAbove()) {
+
  } else {
        print(client.caption() + " (yes)");
+
    print(client.caption + " is not maximized");
    }
+
  }
}
+
});
 
</syntaxhighlight>
 
</syntaxhighlight>
  
'''Explanation:'''
+
Best give the script a try in the desktop scripting console and play with your windows. Remember right and middle clicking the maximize button changes the horizontal/vertical state of the window.
*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:
+
 
+
[[File:Kwintut3.png]]
+
  
Now, what we need to do, is add every such client to the array '''ka_clients'''. So this is how our code will look:
+
==== Checking keep above ====
 +
Now we actually want to do something with the maximized Client. We need to check whether the window is set as keep above. If it is so, we need to remove the keep above state and remember that we modified the Client. For better readability the callback is moved into an own method:
  
 
<syntaxhighlight lang=javascript>
 
<syntaxhighlight lang=javascript>
var ka_clients = new Array();
+
function manageKeepAbove(client, h, v) {
var c = workspace.getAllClients();
+
  if (h && v) {
 
+
    // maximized
for(var i=0; i<c.length; i++) {
+
    if (client.keepAbove) {
    if(c[i].keepAbove()) {
+
      keepAboveMaximized.push(client);
        ka_clients[ka_clients.length] = c[i];
+
      client.keepAbove = false;
 
     }
 
     }
 +
  }
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
This code checks whether the window is maximized, if that is the case we access the Client's '''keepAbove''' property which is a boolean. If the Client is keep above we append the Client to our global array '''keepAboveMaximized''' of Clients we modified. This is important to be able to reset the keep above state when the window gets restored again.
  
What '''ka_clients[ka_clients.length]''' does is append an element to that array.
+
Last but not least we have to remove keep above which is a simple assignment to the Client's property. If you want to test it in the desktop scripting console remember to adjust the signal connection:
 
+
==== 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:
+
  
 
<syntaxhighlight lang=javascript>
 
<syntaxhighlight lang=javascript>
function manageKeepAbove(type) {
+
workspace.clientMaximizeSet.connect(manageKeepAbove);
    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);
+
    }
+
}
+
 
</syntaxhighlight>
 
</syntaxhighlight>
 
For all events, there are two ways to connect it:
 
 
<syntaxhighlight lang=javascript>
 
object.event.connect(h_function);
 
object.event.connect(f_this, h_function);
 
</syntaxhighlight>
 
 
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.
 
 
==== 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:
 
 
<syntaxhighlight lang=javascript>
 
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);
 
    }
 
});
 
</syntaxhighlight>
 
 
'''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 [http://rohanprabhu.com/?p=14 here]. This is why the ‘==’ and the ‘!=’ operator can be used safely to check if two variables actually wrap the same object or not.
 
  
 
==== Restoring it all ====
 
==== 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 vertically 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:
+
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 vertically and horizontally, we check if the client is in our '''keepAboveMaximized''' arrray and if it is, we set its '''‘Keep Above’''' property, otherwise we don’t bother:
  
  
 
<syntaxhighlight lang=javascript>
 
<syntaxhighlight lang=javascript>
function manageKeepAbove(type) {
+
function manageKeepAbove(client, h, v) {
    if((type.h == 1) && (type.v == 1)) {
+
  if (h && v) {
        this.setKeepAbove(0);
+
    // maximized
     } else {
+
     if (client.keepAbove) {
        if(ka_clients.indexOf(this)) {
+
      keepAboveMaximized[keepAboveMaximized.length] = client;
            this.setKeepAbove(1);
+
      client.keepAbove = false;
        }
+
 
     }
 
     }
 +
  } else {
 +
    // no longer maximized
 +
    var found = keepAboveMaximized.indexOf(client);
 +
    if (found != -1) {
 +
      client.keepAbove = true;
 +
      keepAboveMaximized.splice(found, 1);
 +
    }
 +
  }
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Line 228: Line 145:
  
 
<syntaxhighlight lang=javascript>
 
<syntaxhighlight lang=javascript>
var ka_clients = new Array();
+
var keepAboveMaximized = new Array();
var c = workspace.getAllClients();
+
  
workspace.clientSetKeepAbove.connect(function(client, set) {
+
function manageKeepAbove(client, h, v) {
    var found = ka_clients.indexOf(client);
+
  if (h && v) {
 
+
    // maximized
     if(set == 1) {
+
     if (client.keepAbove) {
        if(found == -1) {
+
      keepAboveMaximized[keepAboveMaximized.length] = client;
            ka_clients[ka_clients.length] = client;
+
      client.keepAbove = false;
            client.maximizeSet.connect(client, manageKeepAbove);
+
        }
+
    } else if(set == 0) {
+
        ka_clients.splice(found, 1);
+
        client.maximizeSet.disconnect(manageKeepAbove);
+
 
     }
 
     }
});
+
  } else {
 
+
    // no longer maximized
function manageKeepAbove(type) {
+
    var found = keepAboveMaximized.indexOf(client);
     if((type.h == 1) && (type.v == 1)) {
+
     if (found != -1) {
        this.setKeepAbove(0);
+
      client.keepAbove = true;
    } else {
+
      keepAboveMaximized.splice(found, 1);
        if(ka_clients.indexOf(this)) {
+
            this.setKeepAbove(1);
+
        }
+
 
     }
 
     }
 +
  }
 
}
 
}
  
for(var i=0; i<c.length; i++) {
+
workspace.clientMaximizeSet.connect(manageKeepAbove);
    if(c[i].keepAbove()) {
+
        ka_clients[ka_clients.length] = c[i];
+
        c[i].maximizeSet.connect(c[i], manageKeepAbove);
+
    }
+
}
+
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Try out this script and check if it works according to your wishes. 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.
+
The script in Plasma Package structure can be found in the [https://projects.kde.org/projects/kde/kdeexamples/repository/revisions/master/show/kwin/scripts/keepaboverestored kde-examples repository].
 +
 
 +
=== What next? ===
 +
The script is of course very simple. It does not take care of windows which are already present when the window manager starts. It might be an idea to restrict the script to some window classes (e.g. video players). It's up to you.

Latest revision as of 19:03, 14 April 2013

Contents

[edit] KWin Scripting Tutorial

noframe
 
Warning
KWin's Scripting functionality has been reworked for KDE Plasma Workspaces 4.9. This tutorial does not cover the previous API. In case you had an existing script, please consult Scripting Update Notes for migration note.


[edit] Quick Start: Desktop Console

The easiest way to test KWin scripts is to use the Plasma Desktop Scripting Console which can be opened via the KRunner window (Alt+F2, by default, or via the "Run Command" entry in various desktop menus) by entering "wm console" as the search term It can be triggered directly via dbus with
qdbus org.kde.plasma-desktop /MainApplication showInteractiveKWinConsole
noframe
 
Note
This method is not available for plasma-netbook.

The interactive console allows to send a script to the window manager which is directly loaded and executed. All debug output is displayed in the scripting console. This provides a very easy way to develop and test the script to be written. It is important to know that scripts executed from the scripting console are only used by the window manager as long as the window manager is running. In a new session the script has to be sent to the window manager again.

[edit] Packaging KWin Scripts

In order to have KWin load a script on each session start the script has to be packaged. KWin Scripts use the Plasma Package format. The recommended file ending for KWin Script packages is .kwinscript. In the metadata.desktop file of the package the value for "X-KDE-ServiceTypes" has to be KWin/Script, as "X-Plasma-API" only javascript and declarativescript are supported.

A packaged KWin Script can either be installed via the KWin Script KCM (note: the list does not yet reload after installing a script) or with the plasmapkg tool:
plasmapkg --type kwinscript -i /path/to/myscript.kwinscript

After installation the script has to be enabled in the KWin Script KCM. The window manager will load the newly installed script directly at runtime as well as in new sessions.

[edit] Where can I find example scripts?

A few KWin Scripts are shipped directly with the window manager. You can find those in your system installation. Just use plasmapkg to get a list of the available scripts:

plasmapkg -t kwinscript -l

The scripts can be found in the data install path of your local KDE installation under "kwin/scripts". E.g. in ~/.kde/share/apps/kwin/scripts/.

Additionally you can find the scripts in the KDE-Workspace repository and some example scripts in the KDE Examples repository.

[edit] KWin scripting basics

To follow this tutorial, you must have some idea about ECMAScript (or JavaScript. A quick introduction can be found in the Plasma scripting tutorial.

KWin Scripts can either be written in QtScript (service type "javascript") or QML (service type "declarativescript"). In order to develop KWin Scripts you should know the basic concepts of signals and properties.

[edit] Global Objects and Functions

KWin Scripts can access two global properties workspace and options. The workspace object provides the interface to the core of the window manager, the options object provides read access to the current configuration options set on the window manager. To get an overview of what is available, please refer to the API documentation.

The following global functions are available to both QML and QtScript:

  • print(QVariant...): prints the provided arguments to stdout. Takes an arbitrary number of arguments. Comparable to console.log() which should be preferred in QML scripts.
  • readConfig(QString key, QVariant defaultValue=QVariant()): reads a config option of the KWin Script. First argument is the config key, second argument is an optional default value in case the config key does not exist in the config file.

[edit] Clients

The window manager calls a window it manages a "Client". Most methods of workspace operating on windows either return a Client or require a Client. Internally the window manager supports more types of windows, which are not clients. Those windows are not available for KWin Scripts, but for KWin Effects. To have a common set of properties some properties and signals are defined on the parent class of Client called Toplevel. Be sure to check the documentation of that class, too when looking for properties. Be aware that some properties are defined as read-only on Toplevel, but as read-write on Client.

The following examples illustrates how to get hold of all clients managed by the window manager and prints the clients' caption:

var clients = workspace.clientList(); 
for (var i=0; i<clients.length; i++) {
  print(clients[i].caption);
}

The following example illustrates how to get informed about newly managed clients and prints out the window id of the new client:

workspace.clientAdded.connect(function(client) {
  print(client.windowId);
});

To understand which parameters are passed to the event handlers (i.e. the functions we connect to), one can always refer the Development/Tutorials/KWin/Scripting/API_4.9.

[edit] Your first (useful) script

In this tutorial we will be creating a script based on a suggestion by Eike Hein. 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 something useful.

So let’s get on with it…

[edit] 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 has been removed for maximized windows
  2. Whenever a client is maximized, if it’s '‘Keep Above’ property is set, remove the ‘Keep Above’ property.
  3. Whenever a client is restored, if it is in the ‘array’, set it’s ‘Keep Above’ property.

[edit] The basic framework

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

var keepAboveMaximized = [];

Now we need to know whenever a window got maximized. There are two approaches to achieve that: either connect to a signal emitted on the workspace object or to a signal of the client. As we need to track all Clients it is easier to just use the signal 'clientMaximizeSet on the workspace. This signal is emitted whenever the maximization state of a Client changes and passes the client and two boolean flags to the callback. The flags indicate whether the Client is maximized horizontally and/or vertically. If a client is maximized both horizontally and vertically it is considered as fully maximized. Let's try it:

workspace.clientMaximizeSet.connect(function(client, h, v) {
  if (h && v) {
    print(client.caption + " is fully maximized");
  } else {
    print(client.caption + " is not maximized");
  }
});

Best give the script a try in the desktop scripting console and play with your windows. Remember right and middle clicking the maximize button changes the horizontal/vertical state of the window.

[edit] Checking keep above

Now we actually want to do something with the maximized Client. We need to check whether the window is set as keep above. If it is so, we need to remove the keep above state and remember that we modified the Client. For better readability the callback is moved into an own method:

function manageKeepAbove(client, h, v) {
  if (h && v) {
    // maximized
    if (client.keepAbove) {
      keepAboveMaximized.push(client);
      client.keepAbove = false;
    }
  }
}

This code checks whether the window is maximized, if that is the case we access the Client's keepAbove property which is a boolean. If the Client is keep above we append the Client to our global array keepAboveMaximized of Clients we modified. This is important to be able to reset the keep above state when the window gets restored again.

Last but not least we have to remove keep above which is a simple assignment to the Client's property. If you want to test it in the desktop scripting console remember to adjust the signal connection:

workspace.clientMaximizeSet.connect(manageKeepAbove);

[edit] 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 vertically and horizontally, we check if the client is in our keepAboveMaximized arrray and if it is, we set its ‘Keep Above’ property, otherwise we don’t bother:


function manageKeepAbove(client, h, v) {
  if (h && v) {
    // maximized
    if (client.keepAbove) {
      keepAboveMaximized[keepAboveMaximized.length] = client;
      client.keepAbove = false;
    }
  } else {
    // no longer maximized
    var found = keepAboveMaximized.indexOf(client);
    if (found != -1) {
      client.keepAbove = true;
      keepAboveMaximized.splice(found, 1);
    }
  }
}

In the end, our entire script looks like:

var keepAboveMaximized = new Array();
 
function manageKeepAbove(client, h, v) {
  if (h && v) {
    // maximized
    if (client.keepAbove) {
      keepAboveMaximized[keepAboveMaximized.length] = client;
      client.keepAbove = false;
    }
  } else {
    // no longer maximized
    var found = keepAboveMaximized.indexOf(client);
    if (found != -1) {
      client.keepAbove = true;
      keepAboveMaximized.splice(found, 1);
    }
  }
}
 
workspace.clientMaximizeSet.connect(manageKeepAbove);

The script in Plasma Package structure can be found in the kde-examples repository.

[edit] What next?

The script is of course very simple. It does not take care of windows which are already present when the window manager starts. It might be an idea to restrict the script to some window classes (e.g. video players). It's up to you.


This page was last modified on 14 April 2013, at 19:03. This page has been accessed 13,411 times. Content is available under Creative Commons License SA 3.0 as well as the GNU Free Documentation License 1.2.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V.Legal