https://blog.vladzahorodnii.com/2024/03/18/how-to-write-a-qml-effect-for-kwin/
Skip to content
Vlad Zahorodnii's Blog
Menu and widgets
Recent Posts
* How to write a QML effect for KWin
* How Cursor is Rendered
* Xinerama becomes hard requirement of KWin
* What's cooking in KWin? Plasma 5.25
* Geometry handling in KWin/Wayland
Archives
* March 2024 (1)
* January 2023 (1)
* July 2022 (1)
* May 2022 (1)
* January 2022 (1)
* December 2021 (1)
* April 2021 (1)
* March 2021 (1)
* December 2020 (2)
* July 2020 (1)
Categories
* Arch Linux (1)
* Firefox (1)
* Fonts (1)
* KDE (9)
* PackageKit (1)
* Uncategorized (1)
* Wayland (2)
How to write a QML effect for KWin
Since the dawn of the times, the only way to implement any effect
that has fancy user interface used to be in C++. It would be rather
an understatement to say that the C++ API is difficult to use or get
it right so there are no glitches. On the other hand, it would be
really nice to be able to implement dashboard-like effects while not
compromise on code simplicity and maintainability, especially given
the rise of popularity of overview effects a few years ago, which
indicated that there is demand for such effects.
In order solve that problem, we started looking for some options and
the most obvious one was QtQuick. It's a quite powerful framework and
it's already used extensively in Plasma. So, in Plasma 5.24, we
introduced basic support for implementing kwin effects written in QML
and even added a new overview effect. Unfortunately, if you wanted to
implement a QtQuick-based effect yourself, you would still have to
write a bit of C++ glue yourself. This is not great because effects
that use C++ are a distribution nightmare. They can't be just
uploaded to the KDE Store and then installed by clicking "Get New
Effects...". Furthermore, libkwin is a fast moving target with lots of
ABI and API incompatible changes in every release. That's not good if
you're an effect developer because it means you will need to invest a
bit of time after every plasma release to port the effects to the new
APIs or at least rebuild the effects to resolve ABI
incompatibilities.
This has been changed in Plasma 6.0.
In Plasma 6, we've had the opportunity to address that pesky problem
of requiring some C++ code and also improve the declarative effect
API after learning some lessons while working on the overview and
other effects. So, enough of history and let's jump to the good
stuff, shall we?
Project Structure
Declarative effects require some particular project structure that we
need to learn first before writing any code
+-- package
+-- contents
| +-- ui
| +-- main.qml
+-- metadata.json
The package directory is a toplevel directory, it should contain two
things: a metadata.json file and a contents directory. The
metadata.json file contains information about the name of the effect,
what API it uses, the author, etc.
{
"KPackageStructure": "KWin/Effect",
"KPlugin": {
"Authors": [
{
"Email": "user@example.com",
"Name": "Name"
}
],
"Category": "Appearance",
"Description": "Yo",
"EnabledByDefault": false,
"Id": "hello-world",
"License": "MIT",
"Name": "Hello World"
},
"X-KDE-Ordering": 60,
"X-Plasma-API": "declarativescript"
}
The contents directory contains the rest of QML code, config files,
assets, etc. Keep in mind that ui/main.qml is a "magical" file, it
acts as an entry point, every effect must have it.
In order to install the effect and make it visible in Desktop Effects
settings, you will need to run the following command
kpackagetool6 --type KWin/Effect --install package/
This is quite a lot to memorize. That's why kwin provides an example
qtquick effect that you can grab, tweak some metadata and you're good
to go. You can find the example project at https://invent.kde.org/
plasma/kwin/-/tree/master/examples/quick-effect?ref_type=heads. Note
that the example project also contains a CMakeLists.txt file, which
provides an alternative way to install the effect by the means of
cmake, i.e. make install or cmake --install builddir.
Hello World
Let's start with an effect that simply shows a hello world message on
the screen:
import QtQuick
import org.kde.kwin
SceneEffect {
id: effect
delegate: Rectangle {
color: "blue"
Text {
anchors.centerIn: parent
color: "white"
text: "Hello world!"
}
}
ScreenEdgeHandler {
enabled: true
edge: ScreenEdgeHandler.TopEdge
onActivated: effect.visible = !effect.visible
}
ShortcutHandler {
name: "Toggle Hello World Effect"
text: "Toggle Hello World Effect"
sequence: "Meta+H"
onActivated: effect.visible = !effect.visible
}
PinchGestureHandler {
direction: PinchGestureHandler.Direction.Contracting
fingerCount: 3
onActivated: effect.visible = !effect.visible
}
}
import QtQuick is needed to use basic QtQuick components such as
Rectangle. import org.kde.kwin imports kwin specific components.
The SceneEffect is a special type that every declarative effect must
use. Its delegate property specifies the content for every screen. In
this case, it's a blue rectangle with a "Hello World!" label in the
center.
The ShortcutHandler is a helper that's used to register global
shortcuts. ShortcutHandler.name is the key of the global shortcut,
it's going to be used to store the shortcut in the config and other
similar purposes. ShortcutHandler.text is a human readable
description of the global shortcut, it's going to be visible in the
Shortcuts settings.
The ScreenEdgeHandler allows to reserve a screen edge. When the
pointer hits that screen edge, some code can be executed by listening
to the activated signal.
The PinchGestureHandler and SwipeGestureHandler allow to execute some
code when the user makes a pinch or a swipe gesture, respectively.
effect.visible = !effect.visible toggles the visibility of the
effect. When effect.visible is true, the effect is active and visible
on the screen; otherwise it's hidden. You need to set effect.visible
to true in order to show the effect.
If you press Meta+H or make a pinch gesture or move the pointer to
the top screen edge, you're going to see something like this
[Screenshot_20240317_170123]
Note that there are no windows visible anymore, it is the
responsibility of the effect to decide what should be displayed on
the screen now.
Displaying Windows
Being able to display text is great, but it's not useful. Usually,
effects need to display some windows, so let's display the active
window
delegate: Rectangle {
color: "blue"
WindowThumbnail {
anchors.centerIn: parent
client: Workspace.activeWindow
}
}
The change is quite simple. Instead of displaying a Text component,
there's a WindowThumbnail component now. The WindowThumbnail type is
provided by the org.kde.kwin module. WindowThumbnail.client indicates
what window the thumbnail item should display.
[Screenshot_20240317_172450]
Input
Input processing contains no kwin specific APIs. TapHandler,
MouseArea, Keys and other stuff available in QtQuick should just
work. For example, let's implement an effect that arranges windows in
a grid and if somebody middle clicks a window, it will be closed
delegate: Rectangle {
color: "pink"
GridView {
id: grid
anchors.fill: parent
cellWidth: 300
cellHeight: 300
model: WindowModel {}
delegate: WindowThumbnail {
client: model.window
width: grid.cellWidth
height: grid.cellHeight
TapHandler {
acceptedButtons: Qt.MiddleButton
onTapped: client.closeWindow()
}
}
}
}
The code looks pretty straightforward except maybe the model of the
GridView. WindowModel is a helper provided by org.kde.kwin module
that lists all the windows. It can be passed to various views,
Repeater, and so on.
The result can be seen here
Delegates are Per Screen
One thing to keep in mind is that the delegates are instantiated per
screen. For example,
delegate: Rectangle {
color: "yellow"
Text {
anchors.centerIn: parent
color: "black"
text: SceneView.screen.name
}
}
When you activate the effect on a setup with several outputs, each
output will be filled with yellow color and the output name in the
center
[Screenshot_20240317_181544]
Usually, the output is irrelevant, but if you need to know what
output particular delegate is displayed on, you could use the
SceneView.screen attached property.
Configuration
As your effect grows, you will probably face the need to provide an
option or two. Let's say that we want the background color in our
hello world effect to be configurable. How do we achieve that? The
first step, is to add a main.xml file in package/contents/config
directory, i.e.
package/
+-- contents
| +-- config
| | +-- main.xml
| +-- ui
| +-- main.qml
+-- metadata.json
The main.xml file lists all options
#ff00ff
In our case, only one option is needed: BackgroundColor, which has
Color type and #ff00ff default value. You can refer to the KConfigXT
documentation to learn more what other entry types are supported.
The next step is to actually use the BackgroundColor option
delegate: Rectangle {
color: effect.configuration.BackgroundColor
Text {
anchors.centerIn: parent
color: "white"
text: "Hello world!"
}
}
effect.configuration is a map object that contains all the options
listed in the main.xml.
Now, if you toggle the hello world effect, you're going to see
[Screenshot_20240318_000709]
There are a few more thing left to do though. If you navigate to
Desktop Effects settings, you're not going a configure button next to
the hello world effect
[Screenshot_20240318_001125]
Besides providing a main.xml file, the effect also needs to provide a
config.ui file containing a configuration ui
package/
+-- contents
| +-- config
| | +-- main.xml
| +-- ui
| +-- config.ui
| +-- main.qml
+-- metadata.json
The config.ui file is a regular Qt Designer UI file. The only special
thing about it is that the controls that represent options should
have special name format: kcfg_ + OptionName. For example,
kcfg_BackgroundColor
QuickEffectConfig
0
0
455
177
-
Background color:
-
false
KColorButton
QPushButton
The last final piece in order to expose the configuration ui is to
add the following line in the metadata.json file
"X-KDE-ConfigModule": "kcm_kwin4_genericscripted"
With all of that, the effect is finally displayed as configurable in
the system settings and the background color can be changed
[Screenshot_20240318_002244]
Sharing Your Effect With Other People
The preferred method to distribute third party extensions is via the
KDE Store. Both JS and QML effects can be uploaded to the same "KWin
Effects" category.
Documentation and Other Useful Resources
Documentation is still something that needs a lot of work (and
writing it is no fun ). KWin scripting API documentation can be
found here https://develop.kde.org/docs/plasma/kwin/api/.
Besides the link above, it's worth having a look at the examples
https://invent.kde.org/plasma/kwin/-/tree/master/examples?ref_type=
heads in kwin git repository.
Window Heap
If you need to pack or arrange windows like how the overview effect
does, you could use the WindowHeap component from
org.kde.kwin.private.effects module. BUT you need to keep in mind
that that helper is private and has no stable API yet, so use it on
your own risk (or copy paste the relevant code in kwin). Eventually,
the WindowHeap will be stabilized once we are confident about its
API.
The WindowHeap source code can be found at https://invent.kde.org/
plasma/kwin/-/tree/2a13a330404c8d8a95f6264512aa06b0a560f55b/src/
plugins/private.
More Examples
If you need more examples, I suggest to have a look at the desktop
cube effect in kdeplasma-addons. It's implemented using the same QML
effect API in kwin + QtQuick3D. The source code can be found at
https://invent.kde.org/plasma/kdeplasma-addons/-/tree/master/kwin/
effects/cube?ref_type=heads
[Screenshot_20240317_182738]
Conclusion
I hope that some people find this quick introduction to QML-based
effects in KWin useful. Despite being a couple years old, declarative
effects can be still considered being in the infancy stage and
there's a lot of untapped potential in them yet. The QML effect API
is not perfect, and that's why we are interested in feedback from
third party extension developers. If you have some questions or
feedback, feel free to reach out us at https://webchat.kde.org/#/room
/#kwin:kde.org. Happy hacking!
Share this:
* Twitter
* Facebook
*
Like this:
Like Loading...
Related
Posted on March 18, 2024March 18, 2024Author Vlad Zahorodnii
Categories KDE
3 thoughts on "How to write a QML effect for KWin"
1. [a7f5d7] Megaguy32 says:
March 19, 2024 at 3:04 AM
$ tree
.
+-- package
+-- contents
| +-- ui
| +-- main.qml
+-- metadata.json
4 directories, 2 files
$ kpackagetool6 -type KWin/Effect -install package/
kf.package: Could not find required file "mainscript" for package
"/home/ethan/.local/share/kwin/effects/desktop-effect/package/"
should be QList("code/main.js")
kf.package: Could not find required file "mainscript" for package
"/home/ethan/.local/share/kwin/effects/desktop-effect/package/"
should be QList("code/main.js")
Error: Installation of /home/ethan/.local/share/kwin/effects/
desktop-effect/package failed: Package is not considered valid
Loading...
Reply
1. [542471] Vlad Zahorodnii says:
March 19, 2024 at 11:55 AM
Hmm, is the contents/ directory inside the package/
directory?
What if you copy the example effect from kwin git repo and
use cmake?
Loading...
Reply
1. [542471] Vlad Zahorodnii says:
March 19, 2024 at 12:07 PM
Oh, whoops, I see why it can fail. https://invent.kde.org
/plasma/kwin/-/merge_requests/5471 should fix it. It
should be in 6.0.3
Loading...
Leave a ReplyCancel reply
Post navigation
Previous Previous post: How Cursor is Rendered
Powered by WordPress.com.
Discover more from Vlad Zahorodnii's Blog
Subscribe now to keep reading and get access to the full archive.
Type your email... [ ]
Subscribe
Continue reading
Loading Comments...
Write a Comment... [ ]
Email (Required) [ ] Name (Required)
[ ] Website [ ]
[Post Comment]
%d