This blog post has been in my drafts folder for weeks and I just thought it’s time to publish it – ready for Akademy 🙂 And if somebody is really interested we could have a BOF session at Akademy. I think Plasma devs want us to write a slide effect to replace their custom popup animation. That would be a perfect example to get your hands dirty.
I wrote a new Kwin effect and thought this is the ideal effect for writing a small howto. It is an effect which helps resizing windows by colouring the changed geometry. I was told that resizing is not optimal in KWin, that is if the window content is shown while resizing it is slow and if the window content is not shown it is ugly. This effect should fill the gap. Unfortunately the current code will only work with the slow show window content while resizing (the API has to be changed). Nevertheless I decided to show the code in this tutorial.
The effect has been committed to trunk. So you can have a look at it and if you’re running trunk you can even try it. The effect which we will write has the name “Resize”. It will support both backends: XRender and OpenGL. Each effect has an own directory in kwin/effects so we create a new directory resize. There we create the following files:
- CMakeLists.txt
- resize.h
- resize.cpp
- resize.desktop
We have to include this directory into the build, so we edit the CMakeLists.txt in the effects directory. We just add the following line to the section marked as “Common effects”:
include( resize/CMakeLists.txt )
If it were an OpenGL only effect we would place this line in the section marked as “OpenGL-specific effects”.
So at this point we are finished with the preparation. So let’s start looking at the files. First the desktop file:
[Desktop Entry]
Name=Resize Window
Icon=preferences-system-windows-effect-resize
Comment=Effect to outline geometry while resizing a window
Type=Service
X-KDE-ServiceTypes=KWin/Effect
X-KDE-PluginInfo-Author=Martin Gräßlin
X-KDE-PluginInfo-Email=kde@martin-graesslin.com
X-KDE-PluginInfo-Name=kwin4_effect_resize
X-KDE-PluginInfo-Version=0.1.0
X-KDE-PluginInfo-Category=Window Management
X-KDE-PluginInfo-Depends=
X-KDE-PluginInfo-License=GPL
X-KDE-PluginInfo-EnabledByDefault=false
X-KDE-Library=kwin4_effect_builtins
X-KDE-Ordering=60
Most of it is self explaining and just needed for the “All effects” tab in the compositing kcm. The most important value is the “X-KDE-PluginInfo-Name”. This is the name used to load the effect and has to start with “kwin4_effect_” followed by your custom effect name. This last part will be needed in the source code.
Each effect is a subclass of class “Effect“ defined in kwineffects.h and implements some of the virtual methods provided by Effect. There are methods for almost everything the window manager does. So by implementing those methods you can react on change of desktop or on opened/closed windows. In this effect we are interested in resize events so we have to implement method “windowUserMovedResized( EffectWindow *w, bool first, bool last )”. This method is called whenever a user moves or resizes the given window. The two boolean values indicate if it is the first, last or an intermediate resize event.
But there are more methods we have to implement. The effect should paint the changed geometry while resizing. So we have to implement the methods required for custom painting. KWin’s painting pass consists of three stages:
- pre paint
- paint
- post paint
These stages are executed once for the complete screen and once for every window. All effects are chained and each effect calls the stage for the next effect. How this works we will see when looking at the implementation. You can find a good documentation in the comments of scene.cpp
Now it’s time to have a look at the header file:
#ifndef KWIN_RESIZE_H
#define KWIN_RESIZE_H
#include <kwineffects.h>
namespace KWin
{
class ResizeEffect
: public Effect
{
public:
ResizeEffect();
~ResizeEffect();
virtual void prePaintScreen( ScreenPrePaintData& data, int time );
virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data );
virtual void windowUserMovedResized( EffectWindow *w, bool first, bool last );
private:
bool m_active;
EffectWindow* m_resizeWindow;
QRegion m_originalWindowRect;
};
}
#endif
We see that there are three member variables. The boolean is used to indicate if there is a window being resized, that is if we have to do some painting. The EffectWindow is a pointer on the window being resized and the QRegion stores the windows’s geometry before the start of resizing.
So now we can have a look at the implementation. I will split the code in small parts and explain the code. So first let’s look at the includes:
#include "resize.h"
#ifdef KWIN_HAVE_OPENGL_COMPOSITING
#include <GL/gl.h>
#endif
#ifdef KWIN_HAVE_XRENDER_COMPOSITING
#include <X11/Xlib.h>
#include <X11/extensions/Xrender.h>
#endif
#include <KColorScheme>
As our effect should support both XRender and OpenGL we have to include the headers for both. As it is possible that the effect is compiled on a system which does not support one of both we use ifdef. We can be sure that at least one of both is available or the effects wouldn’t be compiled at all. If you write an OpenGL only effect you do not have to bother about such things. Also if you only use KWin’s high level API you don’t need to include those headers. But we want to paint on the screen using OpenGL or XRender directly.
So let’s have a look at the next part:
namespace KWin
{
KWIN_EFFECT( resize, ResizeEffect )
ResizeEffect::ResizeEffect()
: m_active( false )
, m_resizeWindow( 0 )
{
reconfigure( ReconfigureAll );
}
ResizeEffect::~ResizeEffect()
{
}
Here we see the use of a macro. This has to be included or your effect will not load (it took me ten minutes to notice I forgot to add this line). The first value is the second part of X-KDE-PluginInfo-Name – I told you we will need it again. The second value is the class name. Following is constructor and deconstructor.
So let’s look at the pre paint screen stage:
void ResizeEffect::prePaintScreen( ScreenPrePaintData& data, int time )
{
if( m_active )
{
data.mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS;
}
effects->prePaintScreen( data, time );
}
Here we extend the mask to say that we paint the screen with transformed windows when the effect is active. That’s not completely true – we don’t transform a window. But this flag indicates that the complete screen will be repainted, so we eliminate the risk of artefacts. We could also track the parts which have to be repainted manually but this would probably be more work for the CPU than the complete repaint for the GPU. At this point we see the chaining for the first time. The effects->prePaintScreen( data, time ); will call the next effect in the chain. effects is a pointer on the EffectsHandler and a very useful helper.
So now we start looking at the heart of the effect:
void ResizeEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data )
{
effects->paintWindow( w, mask, region, data );
if( m_active && w == m_resizeWindow )
{
QRegion intersection = m_originalWindowRect.intersected( w->geometry() );
QRegion paintRegion = m_originalWindowRect.united( w->geometry() ).subtracted( intersection );
float alpha = 0.8f;
QColor color = KColorScheme( QPalette::Normal, KColorScheme::Selection ).background().color();
We first continue the paint window effect chain – this will paint the window on the screen. Now we check if we are in resizing mode (m_active) and if the currently painted window is the window which is repainted. In that case we calculate the region which has to be painted. We just subtract the intersection of current geometry with saved geometry from the union of those two. The next two lines are for the color definition. We use the background color of a selection with 80 % opacity.
Now we have to do a little bit OpenGL. In most effects where you just transform windows you don’t have to write OpenGL at all. There is a nice high level API which allows you to translate, scale and rotate windows or the complete screen. Also transforming single quads can be completely done without knowing anything about OpenGL.
#ifdef KWIN_HAVE_OPENGL_COMPOSITING
if( effects->compositingType() == OpenGLCompositing)
{
glPushAttrib( GL_CURRENT_BIT | GL_ENABLE_BIT );
glEnable( GL_BLEND );
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glColor4f( color.red() / 255.0f, color.green() / 255.0f, color.blue() / 255.0f, alpha );
glBegin( GL_QUADS );
foreach( const QRect &r, paintRegion.rects() )
{
glVertex2i( r.x(), r.y() );
glVertex2i( r.x() + r.width(), r.y() );
glVertex2i( r.x() + r.width(), r.y() + r.height() );
glVertex2i( r.x(), r.y() + r.height() );
}
glEnd();
glPopAttrib();
}
#endif
We check if KWin uses OpenGL as a backend. We enable blending in the OpenGL state machine (needed to have translucent colors) and set the color for our rects. OpenGL clamps colors in the range [0,1] that’s why we can’t use the values from QColor directly. Last but not least we just paint one quads for each rect of our regin.
Now just the XRender part is missing. This part is taken from show paint effect – I don’t know anything about XRender 😉
#ifdef KWIN_HAVE_XRENDER_COMPOSITING
if( effects->compositingType() == XRenderCompositing)
{
XRenderColor col;
col.alpha = int( alpha * 0xffff );
col.red = int( alpha * 0xffff * color.red() / 255 );
col.green = int( alpha * 0xffff * color.green() / 255 );
col.blue= int( alpha * 0xffff * color.blue() / 255 );
foreach( const QRect &r, paintRegion.rects() )
XRenderFillRectangle( display(), PictOpOver, effects->xrenderBufferPicture(),
&col, r.x(), r.y(), r.width(), r.height());
}
#endif
}
}
This does the same as the OpenGL part just with XRender.
Last but not least we have to track the window resizing:
void ResizeEffect::windowUserMovedResized( EffectWindow* w, bool first, bool last )
{
if( first && last )
{
// not interested in maximized
return;
}
if( first && w->isUserResize() && !w->isUserMove() )
{
m_active = true;
m_resizeWindow = w;
m_originalWindowRect = w->geometry();
w->addRepaintFull();
}
if( m_active && w == m_resizeWindow && last )
{
m_active = false;
m_resizeWindow = NULL;
effects->addRepaintFull();
}
}
} // namespace
So and that’s all. When a resize event is started we activate the effect and trigger a repaint of the window (probably not needed, but doesn’t hurt). And when the resizing is finished we deactivate the effect and trigger another repaint of the complete screen just to make sure that there are no artefacts left.
The CMakeLists.txt could just be taken from any other effect and be adjusted. So here’s the example:
#######################################
# Effect
# Source files
set( kwin4_effect_builtins_sources ${kwin4_effect_builtins_sources}
resize/resize.cpp
)
# .desktop files
install( FILES
resize/resize.desktop
DESTINATION ${SERVICES_INSTALL_DIR}/kwin )
#######################################
# Config
Now you can compile and try your new effect.