What’s next for Aurorae?

Aurorae is a decoration engine that allows you easily using third party decoration themes from KDE Store.

Aurorae decoration themes at KDE Store (although there are some decoration themes that don’t use Aurorae, e.g. Klassy, but a good chunk of themes found in the Plasma 6 Window Decorations category still use it)

It’s been around for quite a while and it has a plenty long history. However, in the recent years, it has been somewhat neglected. The UI trends changed, e.g. rounded corners are all the rage now, but there has been no changes in Aurorae to allow theme creators to follow those trends more easily. There are also performance issues. In comparison to the default Breeze decoration theme, it performs quite poorly, unfortunately.

What is Aurorae anyway?

In Plasma, we have a C++ library that’s used to implement window decorations called KDecoration. Both Breeze and Aurorae use KDecoration, but the main difference between the two is that the former directly implements a window decoration that follows Breeze style, while the latter is just a very themeable window decoration.

Aurorae supports both QML and SVG themes. With a QML theme, you need to write some QML code to define how the window decoration should look and behave. With an SVG theme, you need to provide a bunch of SVG files that specify how the window frame and various buttons look. Under the hood, SVG themes are effectively built as QML themes.

QML is pretty cool because with a few lines of code, you can get something that works and looks very decent. But for our usecase, it’s also a heavy tool, and due to the way how QtQuick works, it’s very challenging to have proper fractional scaling support. To be fair, normal applications that use QtQuick are mostly fine, it’s just that we have a pretty unique usecase where we need full control where every individual pixel gets painted.

Aurorae V2

Recently, I started a rewrite of Aurorae with a few goals in mind. The first goal is to improve performance so KWin doesn’t struggle when you resize a window (just to be clear, no, this doesn’t mean that there are performance issues in QtQuick. The way QML decorations are rendered is inefficient. We render a QtQuick scene in an offscreen texture, then download its contents, and then upload its contents in another texture. These roundtrips kill performance, and they are necessary because of KDecoration API constraints). The second goal is to open up the road for fractional scaling support improvements, like fixing misaligned window border edges or gaps between the decoration and the window contents. The third goal is to prepare the foundation for future improvements and to provide more tools so artists can create themes that reflect “modern” stylistic tendencies.

In this rewrite, raw KDecoration and KSvg APIs are used. This makes aurorae decorations quite lightweight and it significantly improves performance. Some fractional scaling issues have already been fixed, while others still need more work. Window decorations are rendered on the CPU side. While, yes, it will be nice to have everything done on the GPU side, doing things on the CPU side is also not that of a big bottleneck right now. In the future, we may follow up on doing more things on the GPU, but for now, it is not high priority.

The main focus has been on improving performance issues and preserving compatibility with the previous implementation of SVG decoration themes so decorations look more or less the same way. There may be some (unintentional) differences, but they should be very minimal.

What about QML decoration themes then? The main idea behind them is brilliant, but after so many years, there are not that many window decoration themes that use QML. In either case, the original (V1) and rewritten (V2) aurorae engines live side-by-side, but the future of V1 is unclear. QML and SVG decoration themes will use V1 and V2, respectively.

Future plans

At the moment, the goal is to continue polishing and optimizing V2. There are certain limits how far we can push things due to the theme format. Some parts of the theme force us to do some things, which ideally we shouldn’t do.

It’s highly likely (not saying this for sure though!) that there will be a V3, which is going to address some theme limitations and add support for new features, for example rounding bottom window corners or outlines.

If you’re a decoration theme creator and would like to see some particular feature in Aurorae, feel free to reach out to me (@zzag) on Matrix (e.g. in #kwin) or file a feature request at https://bugs.kde.org (product: kwin; component: aurorae).

Dark mode improvements in Plasma

Dark mode has been available in Plasma for quite a while now but the next release explores new areas where it can be taken one step further, beginning from some behind the scenes infrastructure to more user facing features such as dynamic wallpapers and automatic day/night global theme switching.

My journey to improving dark mode capabilities started at the recent Plasma Sprint. A while ago, I developed a wallpaper plugin that’s capable of synchronizing images to the position of the Sun, which I wanted to upstream but one of the biggest issues was the sheer size of the project. It supports a bunch of modes, it requires special tooling, and the wallpaper format is too cumbersome for upstream (although it’s amazing for 5K or 8K images!). So, I decided to make a pitch for adding one of the modes available in that plugin to other Plasma developers, which should be very easy to integrate with the existing infrastructure in Plasma. The idea was well-received and it led to a discussion about other related things, like better integration of geolocation services in Plasma, etc.

Plasma Sprint 2025

Day/night wallpapers

Some wallpaper packages can contain light and dark images. Currently, the light image will be used with a light color scheme, and the dark image will be used with a dark color scheme. The main idea behind the new day/night dynamic wallpapers was to exploit this design of the wallpaper packages: switch between light and dark images depending on the elevation of the Sun rather than the color scheme.

New day/night mode

The plasma5-wallpapers-dynamic served as the basis for the new dynamic wallpaper mode in Plasma. It works exactly like we want. If geolocation data is available, use it to compute the position of the Sun. The daylight duration varies depending on your location throughout the year; by taking the geolocation into account, the dynamic wallpapers can closely match the dark/light cycle. If the geolocation is unknown, fall back to some pre-configured morning and evening times.

From functional point of view, it doesn’t have any purpose, but it’s a lovely touch that helps you to personalize your computer.

While on this, we also looked into optimizing the wallpaper configuration dialog. Specifically, fixing GridView in QtQuick so it is possible to reuse wallpaper preview items for smoother scrolling and re-working the wallpaper preview generation process for better performance and to make it less error prone.

How to create a day/night wallpaper

A day/night wallpaper is a regular wallpaper package but with light and dark images, Plasma does the rest to figure out what images should be displayed.

For example, imagine that you want to create a wallpaper with a lake. Then the wallpaper would look something like this

Lake/
├── contents
│   ├── images
│   │   ├── 1080x1920.png
│   │   └── 5120x2880.png
│   └── images_dark
│       ├── 1080x1920.png
│       └── 5120x2880.png
└── metadata.json

The Lake folder contains two things: a contents folder with images and a metadata.json file. The metadata.json file provides extra information about the wallpaper, e.g. the author, etc.

{
   "KPlugin": {
       "Authors": [
           {
               "Email": "foobar@example.com",
               "Name": "Name of the author"
           }
       ],
       "Id": "Next",
       "License": "CC-BY-SA-4.0",
       "Name": "Lake"
   },
"X-KDE-CrossFade": true
}

The Id specifies the unique id of the wallpaper, the Name specifies the human readable name of the wallpaper. The metadata may also contain an X-KDE-CrossFade key, this is used to enable or disable long cross-fade. For example, if the transition from day to night lasts 30 minutes, then Plasma will slowly cross-fade from the light to the dark image for 30 minutes. Such a long cross-fade transition can be undesired if the images are too distinct.

images and images_dark folders contain light and dark images, respectively. Note that image file names must match the image resolution, i.e. 5120x2880.png instead of light.png, etc.

The wallpaper should be installed in /usr/share/wallpapers or ~/.local/share/wallpapers.

knighttime(d)

In addition to day/night wallpapers, it would also be nice if more things could be more dynamic. For example, switching the global theme at night. However, this creates a bit of a problem. If you want to configure the times when wallpapers or themes need to be changed, you will need to set settings for every feature individually, which is not great.

To solve that problem, a new desktop component was introduced that orchestrates when morning and evening transitions occurs – KNightTime. It’s a super duper tiny project with a client library and a daemon (knighttimed) that provides the schedule for day/night cycle transitions.

Since all features that synchronize to the day/night cycle subscribe to KNightTime, you only need to configure it to use specific morning and evening times. Night Light, day/night wallpapers and so on will be all synchronized.

knighttimed runs in background and it is started on demand, so in most cases, you don’t even need to worry about its existence.

Day/night themes

Besides the wallpaper, one can also switch between light and dark global themes depending on time of day.

Perhaps the hardest part about this was figuring out how to retrofit our system settings to accommodate for this new mode rather than actually implementing the logic that changes the active theme. Many thanks to Felix Ernst and Nate Graham for providing mock-ups and feedback about the UI.

Global theme options
Landing page

It is worth noting that Plasma will switch between the themes only if the computer has been idle for 5 seconds. The main reason behind this is to avoid interruptions while actively using the computer. In case you don’t like it, you can turn it off in system settings or increase the default idle interval if you think it’s too low for your needs.

With the automatic mode, the global theme switching code is going to be stress-tested quite a bit, so we’ve also started (and continue) looking into various paper cut issues regarding theme switching, e.g. some buttons not adapting to the new color scheme correctly, etc.

Future improvements

There are other things that can be synchronized to the day-night cycle, for example screen brightness. Although, there is nothing clear on the radar yet.

Conclusion

The next release is going to feature a few dark mode improvements. Some are pretty technical, some are very user facing. In either case, I hope that things like this will help to make Plasma users happier and make Plasma stand out more among other desktop environments.

kwin_x11 and kwin_wayland split

One of the biggest behind-the-scenes changes in the upcoming Plasma 6.4 release is the split of kwin_x11 and kwin_wayland codebases. With this blog post, I would like to delve in what led us to making such a decision and what it means for the future of kwin_x11.

Background

KWin started as an X11 window manager almost two and a half decades ago. Over the course of the years, it transformed drastically. It gained support for compositing on X, and it became a Wayland compositor.

Sharing the same codebase was critical in the early days of kwin_wayland. We already had working window management abstractions, which had been tested for many years, so we could reuse them on Wayland instead of writing new from scratch. Also, if kwin_x11 gained a new feature, then kwin_wayland would likely gain it for free too.

As time went by, kwin_wayland outgrew kwin_x11. They still shared code but they became quite distinct projects with different mental models how things operate, e.g. how pixels get on the screen or how input works. It also didn’t help that many Plasma developers jumped the X11 ship and turned to the Wayland side as part of the “eating your own dog food” practice, which eventually led to the feature freeze in KWin/X11 back in 2018 due to the lack of sufficient testing and various breakages.

Some time around 2020, we started taking a more bold and aggressive approach to Wayland session development because we saw that Plasma Wayland was trailing behind other desktop environments and something had to be changed in order to catch up. Such a policy produced great results, and Plasma is now one of the leading Wayland desktop environments. Unfortunately, it also greatly contributed to the number of regressions in the X11 session.

Another issue was that there were some features that we couldn’t make work as expected on Wayland so we had to drop them for everyone, which understandably made X11 users unhappy.

Goals

A few years ago, we started contemplating the idea of splitting the X11 and Wayland codebases because of the growing list of regressions affecting the X11 session, and architecture restrictions imposed on KWin/Wayland by the way KWin/X11 works.

That would allows us to keep KWin/X11 working as is without it breaking too often and freely change KWin/Wayland in ways that we think are best suited to make the Plasma Wayland session even better. Of course, it is not a silver bullet solution: we replace one problem with another problem (mainly related to maintenance and ensuring interface compatibility between two projects).

Details

After various discussions online and at Akademy and also seeing (impressive) Plasma Wayland usage statistics, we decided that it’s the right time to do such a split. The main kwin repository is going to host KWin/Wayland, while the kwin-x11 repository is going to host KWin/X11.

KWin/X11 and KWin/Wayland are co-installable so users can freely switch between the X11 and Wayland sessions back and forth and also make sure that updating to 6.4 is not a big hassle for distributions. You’ll be able to have only KWin/X11 or only KWin/Wayland on your computer, or both.

The codebase split doesn’t affect Xwayland support in KWin/Wayland. In other words, X11 applications will continue running on Plasma Wayland.

Extensions

Like any other Plasma component, KWin’s functionality can be extended using plugins. There’s good and bad news. The good news is that extensions written in JavaScript and QML (for example, fancy effects that are available at the KDE Store) will continue working both with kwin_x11 and kwin_wayland as expected, so extension developers don’t need to do anything about it. The bad news is that C++ extensions should be specifically targeted for kwin_x11 and kwin_wayland because neither provides API and ABI compatibility guarantees for its C++ API.

As Wayland progress moves forward, it is likely that the scripting API of KWin/Wayland will be further extended.

Future of KWin/X11

KWin/X11 will be still maintained for the foreseeable future. But that maintenance work will boil down to fixing build errors, adapting to new KDE Frameworks and Plasma APIs, and backporting window-related fixes from KWin/Wayland. There are no plans to drop KWin/X11 in the Plasma 6 lifecycle, although it’s highly possible that it will happen in Plasma 7.

KWin/X11 won’t receive new features anymore; until recently, it received new features that had been developed against KWin/Wayland passively (because both lived in the same repository). However, it might be actually a good thing because the X11 session doesn’t receive that much testing nowadays.

cursor-shape-v1 support landed in GTK3

Just a quick update: Recently, you might have heard that GTK 4 added support for the cursor-shape-v1 protocol on Wayland. The main advantage of the cursor-shape-v1 protocol is that it ensures consistent cursor look between apps. In Plasma, additional perks come with it, for example the cursor can look more crisp with fractional scale factors because SVG cursors are used. We (KDE) took a shot at backporting the cursor shape protocol support to the GTK 3 branch and, as of this moment, it’s already merged 🎉. This means that you should experience fewer cursor issues in applications running on Wayland that still use GTK 3, mainly Firefox.

I would like to express many thanks to Max Qian for starting the work on adding support for the cursor-shape-v1 protocol, and Matthias Clasen for pushing it over the finish line and reviewing our backport MR.

Zoom improvements

Screen magnification is an accessibility feature that enlarges the screen to make text, images, and other user interface components easier to see or read. It is not something that requires constant developer attention, however, in Plasma 6.3, the zoom plugin received some improvements that I’d like to go over quickly.

Pixel grid

Arguably, it will be too hard to read text if the screen is “too” zoomed in. There are several ways how this case can be handled. For example, the magnification factor can be capped (e.g. to x8 or x10), or do nothing and just display blurry upscaled screen contents… or display something else.

With the old behavior, the zoom plugin used not to do anything special when the magnification factor reaches a high value, but with the new behavior, it is going to display the individual pixels on the screen. This can be very useful to developers, designers, etc.

System settings

In addition to the new pixel grid mode, the system settings for the zoom plugin received minor polishing to look more consistent with other config modules.

Future improvements

Keyboard shortcuts are not the only way how the zoom plugin can be triggered. For example, it can be also triggered by pressing Meta and Control keys and scrolling the mouse wheel to zoom. However, it is not exposed anywhere in the user interface and some people may prefer zooming with just the Meta key pressed. In order to address the discoverability issue of the mouse wheel gesture and allow using a different combination of modifier keys, there is already a patch to add the corresponding system setting, but it’s 6.4 material. It would be also nice to move screen magnifier settings from the desktop effects config module to the accessibility config module.

Last but not least, the zoom effect currently uses the bi-linear magnification filter, which produces okay-ish visual results, but it’s worth looking for alternative upscaling algorithms that handle edges better so zoomed in text looks less blurry.

Improving Xwayland window resizing

One of the quickest ways to determine whether particular application runs using Xwayland is to resize one of its windows and see how it behaves, for example

While it can be handy for the debugging purposes, overall, it makes the Plasma Wayland session look less polished. So, one of the goals for 6.3 was to fix this visual glitch.

This article will provide some background behind what caused the glitch and how we addressed it. Just in case, here’s the same application, which was shown in a screen cast above, but with the corresponding resizing fixes in:

X11 frame synchronization protocol(s)

On X11, all window changes typically take place immediately, including resizing. This can lead to some issues. For example, if a window is resized, it can take a while until the application repaints the window with the new size. What if the compositing manager decides to compose the screen in meanwhile? You’re likely going to see some sort of visual glitches, e.g. the window contents getting cropped or seeing parts of the window that have not been repainted yet.

In order to address this issue, there exists an X11 protocol to synchronize window repaints during interactive resize. An application/client wishing to participate in this protocol needs to list _NET_WM_SYNC_REQUEST in the WM_PROTOCOLS property of the client window and also set the XID of the XSync counter in the _NET_WM_SYNC_REQUEST_COUNTER property. When the WM wants to resize the window, the following will happen:

  1. The window manager sends a _NET_WM_SYNC_REQUEST client message containing a serial that the client will need to put in the XSync counter after processing a ConfigureNotify event that will be generated after the window is resized. The compositing manager and the window manager will block window updates until the XSync request acknowledgement is received;
  2. The WM resizes the client window, for example by calling the xcb_configure_window() function;
  3. The client would then repaint the window with the new size and update the XSync counter with the serial that it had received in step 1;
  4. The window manager and the compositing manager unblock window updates after receiving receiving the XSync request acknowledgement. For example, now, the window can be repainted by the compositing manager and there shouldn’t be glitches as long as the client behaves well.

Note that the window manager and the compositing manager are often the same. For example, both KWin and Mutter are compositing managers and window managers.

The frame synchronization protocol described above is called basic frame synchronization protocol. There is also an extended frame synchronization protocol, but it is not standardized and it is implemented only by a few compositing managers.

_NET_WM_SYNC_REQUEST and Xwayland

KWin supports the basic frame synchronization protocol, so there should be no visual glitches when resizing X11 windows in the Plasma Wayland session, right? At quick glance, yes, but we forget about the most important detail: Wayland compositors don’t use XCompositeNameWindowPixmap() or xcb_composite_name_window_pixmap() to grab the contents of X11 windows, instead they rely on Xwayland attaching graphics buffers to wl_surface objects, so there is no strict order between the Wayland compositor receiving an XSync request acknowledgement and graphics buffers for the new window size.

In order to help better understand the issue, let’s consider a concrete example. Assume that a window with geometry 0,0 100x100 is being resized by dragging its left edge. If the left edge is dragged 10px to the right, the following will happen:

  1. A _NET_WM_SYNC_REQUEST client message will be sent to the client containing the XSync counter serial that must be set after processing the ConfigureNotify event that will be generated after the Wayland compositor calls xcb_configure_window() with the new window size;
  2. The Wayland compositor calls xcb_configure_window() to actually resize the window;
  3. The client receives the sync request client message and the ConfigureNotify event, repaints the window, and acknowledges the sync request;
  4. The Wayland compositor receives the sync request acknowledgement and updates the window position to 10,0.

But here is the problem, when the window position is updated to 10,0, it’s not guaranteed that the wl_surface associated with the X11 window has a buffer with the new window size, i.e. 90x100. It can take a while until Xwayland commits a graphics buffer with the right size. In meanwhile, the compositor could compose the next frame with the new window position, i.e. 10,0, but old surface size, i.e. 100x100. It would look as if the right window edge sticks out of the window decoration. After Xwayland attaches a buffer with the right size, the right window edge will correct itself.

So, ideally, the Wayland compositor should update the window position after receiving the XSync request acknowledgement and Xwayland attaching a new graphics buffer to the wl_surface.

With that in mind, the frame synchronization procedure looks as follows:

  1. The compositor blocks wl_surface commits by setting the _XWAYLAND_ALLOW_COMMITS property to 0 for the toplevel X11 window. This is needed to ensure the consistent order between XSync request acknowledgements and wl_surface commits. As long as the _XWAYLAND_ALLOW_COMMITS property is set to 0, Xwayland will not attempt to commit the wayland surface, for example attach a new graphics buffer after the client repaints the window;
  2. The compositor sends a _NET_WM_SYNC_REQUEST client message as before;
  3. The compositor resizes the client window as before;
  4. The client repaints the window and acknowledges the XSync request as before;
  5. After receiving the XSync acknowledgement, the compositor unblocks surface commits by setting the _XWAYLAND_ALLOW_COMMITS property to 1. Note that the window updates are still blocked, i.e. the window position is not updated yet;
  6. After Xwayland commits the wl_surface with a new graphics buffer, the window updates are unblocked, e.g. the window position is updated.

The frame synchronization process looks more involved with Xwayland, but it is still manageable.

_NET_WM_SYNC_REQUEST support in applications

Most applications that use GTK and Qt support _NET_WM_SYNC_REQUEST, but there are applications that don’t participate in the frame synchronization protocol. If you use one of those apps, you will observe visual glitches during interactive resize.

Closing words

Frame synchronization is a difficult problem, and requires some very intricate code both on the compositor and the client side. But with the changes that we’ve made, I’m proud to say that KWin is one of the few compositors that properly handles frame synchronization for X11 windows on Wayland.

I would also like to express many thanks to the Xwayland developers (Michel Dänzer and Olivier Fourdan) for helping and assisting us with fixing the glitch.

SVG cursors: everything that you need to know about them

SVG cursor themes is a new feature in Plasma 6.2, which we are really excited about. In this blog post, I would like to provide more background behind what motivated us to add support for them, what they are, and how to build them.

(Classic) cursor theme format

A cursor theme is a collection of images defining the contents of various cursor shapes and additional metadata (for example, the human readable name of the theme, whether the cursor theme inherits/extends another cursor theme, etc). On disk, it looks as follows

The cursors/ directory contains a list of Xcursor files and symbolic links to represent cursor shape aliases, e.g. the arrow being an alias for default. The XCursor format has been in use for a very long time now and it has a pretty simple structure

The layout of an XCursor file

An XCursor file consists of a header that includes a magic number to determine whether particular file is actually an XCursor file, the size of the header in bytes, the file version, and the number of ToC entries. Every ToC entry provides the information about the corresponding chunk, for example the chunk type and where the chunk can be found in the file. Lastly, a chunk contains some useful data. A chunk may contain image data or text data, etc.

For example, here’s the image data that can be found in the “default” cursor shape file in the Adwaita cursor theme

As you can see, the Adwaita cursor theme provides the following sizes: 24, 32, 48, 64, and 96.

The index.theme files looks as follows

[Icon Theme]
Inherits=breeze_cursors
Name=Cool Cursor
Comment=That is a cool cursor theme

Cursor themes can be found in $DATADIR/icons directories. For example, /usr/share/icons or ~/.local/share/icons.

X11 vs Wayland cursors

Xcursor cursors are used both on X11 and Wayland, but the way how the cursor size is interpreted is different on the two platforms. X11 assumes that the cursor size is specified in the device pixels, while Wayland assumes that it’s in the logical pixels. Logical pixels have the same visual size across various devices, while physical pixels are specific to particular device. For example, 24 logical pixels on an output with a scale factor of 2 corresponds to 48 physical pixels.

Cursor sizes in Xcursor files are specified in the device pixels.

Another very important detail is that the XCURSOR_SIZE environment variable is treated differently by X11 and Wayland native applications. For example, if XCURSOR_SIZE is set to 24 and the output scale is 2, an X11 application would load a cursor with the size 24, but a Wayland application would effectively load a cursor with the size 48 (24 * 2) because it would see that the output is scaled so the provided cursor needs to be scaled accordingly as well.

“XCURSOR_SIZE=24 dolphin -platform xcb” (left) vs “XCURSOR_SIZE=24 dolphin -platform wayland” (right). Note that “Apply scaling themselves” has been selected in the display settings in Plasma Wayland

Limitations of Xcursor

The most painful thing about Xcursor is its lack of the proper HiDPI support. As it was said in the previous chapter, the cursor size in Xcursor files is specified in the device pixels. On X, it’s not a problem because all geometries are specified in the device pixels. It also means that if you change the scaling factor on X, you need to change the cursor size manually so the cursor is not too small. On Wayland, the cursor size is specified in the logical pixels so the compositor and the clients have to scale the cursor size in order to match the output scale. For example, if the configured cursor size is 24 and the window is on an output with a scale factor of 2, the application needs to load an Xcursor cursor with the size 48. If the cursor theme provides cursors with such a size, perfect! But what if it doesn’t? At the moment, every compositor and client applies its own policies. Some find the cursor with the closest size and use that, some find the cursor with the closest size and then scale it to match the requested size at the cost of adding some blurriness, and so on. It’s a mess. Because neither compositors nor clients can agree how to handle such a case, you could easily observe the cursor changing its size when moving between windows owned by different applications or when moving the cursor between the window and its decoration, e.g.

It’s worth noting though that this issue can be worked around by using the cursor-shape protocol because with it, the application can delegate the compositor the task of loading and displaying cursors. But the bottom line is that the Xcursor format is unsuitable for the HiDPI model that we have present on Wayland.

Another issue with the Xcursor format is that the image data is stored in an uncompressed format. It is okay if you need to provide cursors with small sizes, for example up to 72, but there are cases when you need to display a cursor at a very large size. For example, one such a case is the shake cursor accessibility feature in the Plasma Wayland session.

With the shake cursor feature enabled, the cursor will be inflated when it’s shaken. In order to operate, it needs to load the default cursor shape with a size around 250. If cursor themes provided images for such sizes, their package sizes would easily blow up beyond the 100MiB mark. That’s not good. And as a workaround, in Plasma 6.1, the shake cursor uses its own high resolution images of the Breeze cursor themes.

Shake cursor without any workarounds in 6.1
Shake cursor with workarounds in 6.1

The XCursor format was perfectly suitable for the use cases that existed back in the late 90s and early 2000s, but things have changed over the years and its current raster nature can’t keep up with the use cases that we have now (2024). We’ve got fractional scaling, we’ve got accent colors, we’ve got features that enlarge the cursor, and so on.

SVG cursor format

First of all, let’s build a list of requirements that the svg cursor format must satisfy:

  • obviously, it must support the ability to define cursor contents using svg files so we can fix HiDPI issues, etc
  • easy porting process for existing clients and compositors
  • it should be easy to develop and analyze svg cursor themes. Xcursor is a binary file format, which requires a special tool to create Xcursor files, we would like to avoid that with svg cursors
  • last and the most important requirement is that there must be some compatibility with the existing cursor theme format. We must not be required to write a new system settings module to handle the new cursor format, and the apps that don’t support svg cursors should easily fallback to the Xcursor format.

Here’s how a cursor theme providing svg cursors would look like

index.theme has the same format both for XCursor and SVG cursors. cursors/ directory contains the XCursor cursors, and cursors_scalable/ contains the SVG cursors.

In cursors_scalable/, every cursor shape must have its own directory, or if it’s an alias, then it must be a symlink. Every cursor shape directory must contain the cursor image and a metadata.json file providing the information about the cursor.

For a static cursor, the metadata.json file looks as follows

[
   {
       "filename": "default.svg",
       "hotspot_x": 4,
       "hotspot_y": 4,
       "nominal_size": 24
   }
]

The filename property specifies the filename of the svg file. The hotspot_x and the hotspot_y properties specify the coordinates of the hot spot. The hot spot in the cursor determines the point where interaction with other elements on the screen occurs, e.g. clicks. The nominal_size property specifies the cursor size that the svg file represents. The nominal size is used to decide how much the svg image and the hotspot coordinates need to be scaled in order to get a cursor with the requested size. Note that the nominal size can’t be determined based on the <svg>‘s width and height attributes because there exist themes such as Breeze whose canvas is bigger than the represented cursor size. As an example, in the Breeze cursor theme, the canvas size is 32x32 even though the represented cursor size is 24 in order to accommodate for additional elements that can be attached to the arrow cursor, e.g. a little circle with a plus sign or a question mark.

For an animated cursor, the metadata.json file looks as follows

[
    {
        "filename": "wait-01.svg",
        "delay": 30,
        "hotspot_x": 16,
        "hotspot_y": 15,
        "nominal_size": 24
    },
    {
        "filename": "wait-02.svg",
        "delay": 30,
        "hotspot_x": 16,
        "hotspot_y": 15,
        "nominal_size": 24
    },
    ...
    {
        "filename": "wait-42.svg",
        "delay": 30,
        "hotspot_x": 16,
        "hotspot_y": 15,
        "nominal_size": 24
    }
]

The only new thing is the delay property. The delay property indicates the animation delay to the next frame.

A cursor theme that ships SVG cursors is required to have XCursor cursors too. This is needed to provide fallback for legacy applications that are unaware of the cursor-shape-v1 protocol or simply too old applications that are unlikely to be changed anymore. This restriction might be lifted in the future.

It is worth mentioning that SVG supports animations natively. However, that approach was not chosen for cursor animations for two reasons: to allow caching svg render results more easily and require fewer changes in the compositors and the apps to adapt the svg cursor format.

You can find the json schema for metadata.json over here.

Accent colors

Since the cursor contents is specified using the SVG format, it should be possible to re-color the cursor based on the currently configured accent color. As of now, it is not implemented, but, in general, this is doable and perhaps such a feature will be added to Plasma some day.

Standardization

This cursor format is not officially standardized. We are looking forward to making it upstream, but for now, the main focus is on confirming that the new format lives up to our and cursor creator needs.

To cursor theme creators

Breeze and Breeze Light are the only two cursor themes that support SVG cursors at the moment, but we would love to see custom themes adapting them too so users experience fewer issues with fractional scaling or other features in Plasma when using their favorite 3rdparty cursor themes. We would also like to hear feedback from the cursor theme creators regarding whether it’s easy to adapt this cursor format or whether some additional features are needed. You can reach out to us at Matrix in the #kwin room https://webchat.kde.org/#/room/#kwin:kde.org or in the kwin mailing list.

Examples

If you need an example of a cursor theme that supports SVG cursors, please check the Breeze cursor theme.

Closing words

The new SVG cursor format is amazing. Please try it!

Upcoming tablet input changes in Plasma Wayland

This blog post provides the heads-up about planned tablet input changes that are brewing for Plasma 6.3. KWin provides support for the tablet input protocol, but things are different on the client side. Some apps support it, some do not. If an application supports the tablet input protocol, great, it will receive tablet input events as is. On the other hand, if the application does not support the tablet input protocol, then KWin will fake tablet input as pointer input. In Plasma 6.3, KWin will stop doing that and I think that we should briefly talk what led us to such a decision and what impact it will have.

Originally, when the tablet input protocol support had landed in KWin, there were still pretty few applications and toolkits that supported it. Emulating tablet input was a fairly reasonable decision, otherwise you would have likely not been able to use tablet in the Plasma Wayland session at all. As time went by, more and more clients gained native support for the tablet protocol. Unfortunately, in meanwhile, we had also started noticing various issues with tablet emulation.

So, what’s the reasonable thing to do about it? Fix the bugs of course. And we did. But there is still a set of issues that cannot be addressed without bringing more complexity in already too complex code that we are struggling to keep afloat. Enough is enough.

Q: What’s new in 6.3?

A: Starting since 6.3, tablet input emulation will be deprecated and disabled by default. Note that you can enable it back by setting the KWIN_WAYLAND_EMULATE_TABLET=1 environment variable.

Q: When will tablet input emulation be dropped?

A: There is no concrete milestone at the moment.

Q: What does it mean to you? (as a user)

A: Hopefully, nothing. The major toolkits such as GTK, Qt, and SDL already provide support for the tablet protocol, so does Xwayland. So, you should be able to use tablet without any issues in X11 applications or Wayland native applications that use the aforementioned toolkits. Chromium/Electron still does not provide native support for tablet input on Wayland, but it’s also worth noting that most of those applications run through Xwayland by default unless the user sets some command-line arguments.

If your favorite application does not work with tablets, please tell it to the developers of that application so they know that there’s demand for such an operation mode.

Q: What should I do? (as a toolkit developer)

A: Please add support for tablets! If your toolkit already supports the tablet input protocol, wonderful, no work to do. \o/

Q: Is KWin alone by stopping emulating tablet input?

A: No, it is not. Mutter (the Wayland compositor in GNOME Shell) doesn’t emulate tablet input either.

Closing words

Deprecating tablet emulation is disappointing but the options that we have are not great either. It’s either bring in more complexity in order to fix the existing issues (plus even more code to ensure that the pointer focus is managed correctly when using both pointer and tablet) into an already too complex codebase or just do nothing special about applications that don’t opt in into tablet input. Hopefully, the remaining applications and toolkits that still miss tablet support add it in the near future. If you have more thoughts about it, please reach out to us at our matrix room.

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

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.

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

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

<?xml version="1.0" encoding="UTF-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
<kcfgfile name=""/>
<group name="">
<entry name="BackgroundColor" type="Color">
<default>#ff00ff</default>
</entry>
</group>
</kcfg>

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

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

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

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QuickEffectConfig</class>
<widget class="QWidget" name="QuickEffectConfig">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>455</width>
<height>177</height>
</rect>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Background color:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="KColorButton" name="kcfg_BackgroundColor">
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KColorButton</class>
<extends>QPushButton</extends>
<header>kcolorbutton.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

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

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

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!

How Cursor is Rendered

Wayland has a unique way to let clients specify the contents of the cursor. After receiving a wl_pointer.enter event, the client must call wl_pointer.set_cursor request

    <request name="set_cursor">
      <arg name="serial" type="uint" summary="serial number of the enter event"/>
      <arg name="surface" type="object" interface="wl_surface" allow-null="true"
	   summary="pointer surface"/>
      <arg name="hotspot_x" type="int" summary="surface-local x coordinate"/>
      <arg name="hotspot_y" type="int" summary="surface-local y coordinate"/>
    </request>

The wl_pointer.set_cursor request takes an optional wl_surface object that represents the actual contents of the cursor. That’s it, the wl_surface interface is used both by windows and cursors! It opens a whole lot of features that you could use with the cursor, for example use the wp_viewport protocol for fractional scaling or create cursor surface trees using the wl_subcompositor interface. On the other hand, many compositors view the cursor as a simple image. So, let’s see how we improved kwin in this regard.

Cursor source

Currently (as of 5.26.x), kwin assumes that the cursor can show only a QImage. It’s okay for simple cases, but it falls apart once we need to show a wl_surface, e.g. we will hit problems with getting a QImage from a linux dmabuf client buffer.

So the first thing that we need to do in order to move anywhere forward is to choose proper abstractions to represent what is actually in the cursor. For example, if the cursor hovers a window, it obviously needs to present what the corresponding wl_surface contains. But sometimes the mouse cursor is not above any window, for example if the pointer is above a server-side decoration. Server-side decoration can be considered part of the window, but we cannot use client’s wl_surface anymore, the compositor may choose to show a different cursor, which is a QImage.

class KWIN_EXPORT CursorSource : public QObject
{
    Q_OBJECT

public:
    explicit CursorSource(QObject *parent = nullptr);

    QImage image() const;
    QSize size() const;
    QPoint hotspot() const;

Q_SIGNALS:
    void changed();
};

The CursorSource class is the base class for all other “source” classes that can be attached to the cursor. It contains generic properties, e.g. the hotspot, and it also contains the CursorSource::changed() signal to tell the world when the image has changed. CursorSource::image() exists for compatibility and to make the transition to new abstractions easier.

Sometimes, we need to show a static image in the cursor, so let’s add an ImageCursorSource to serve that purpose

class ImageCursorSource : public CursorSource
{
public:
    explicit ImageCursorSource(QObject *parent = nullptr);

public Q_SLOTS:
    void update(const QImage &image, const QPoint &hotspot);
};

On the other hand, some cursors are not static. For example, the loading cursor usually contains some animation, e.g. spinning wheel

class ShapeCursorSource : public CursorSource
{
public:
    explicit ShapeCursorSource(QObject *parent = nullptr);

    QByteArray shape() const;
    void setShape(const QByteArray &shape);
    void setShape(Qt::CursorShape shape);

    KXcursorTheme theme() const;
    void setTheme(const KXcursorTheme &theme);

private:
    void refresh();
    void selectNextSprite();
    void selectSprite(int index);

    KXcursorTheme m_theme;
    QByteArray m_shape;
    QVector<KXcursorSprite> m_sprites;
    QTimer m_delayTimer;
    int m_currentSprite = -1;
};

The ShapeCursorSource class represents a cursor shape from an Xcursor theme. It can be used to show both animated and static cursors. If the given cursor shape is animated, i.e. it has more than one sprite, ShapeCursorSource will start a timer with the timeout as indicated by the cursor theme. When the timer expires, ShapeCursorSource will switch to the next sprite and emit the CursorSource::changed() signal. If it’s the last sprite, it will wrap around to the first sprite.

And last but not least, we need something to represent wl_surface attached to the cursor

class SurfaceCursorSource : public CursorSource
{
public:
    explicit SurfaceCursorSource(QObject *parent = nullptr);

    KWaylandServer::SurfaceInterface *surface() const;

public Q_SLOTS:
    void update(KWaylandServer::SurfaceInterface *surface, const QPoint &hotspot);
};

ImageCursorSource, ShapeCursorSource, and SurfaceCursorSource are the main types that indicate what the cursor shows.

Scene

The CursorSource classes act as data sources, they don’t actually paint anything on the screen. It’s the responsibility of the scene. I’ve already written a little bit about the scene abstraction in kwin, I recommend you to read my earlier blog post about it https://blog.vladzahorodnii.com/2021/04/12/scene-items-in-kwin/

But as a quick recap: kwin breaks up a window in smaller building blocks called items. The DecorationItem corresponds to the server-side decoration if there’s one. The ShadowItem is a server-side drop shadow, for example a drop shadow cast by the decoration or the panel. The SurfaceItem represents the actual window contents. That’s the same strategy that we will follow here. The cursor will be broken in smaller pieces.

If we look closely at our cursor sources, the painting code should handle only two cases – paint a QImage and paint an arbitrary wl_surface tree. The wl_surface case is already taken care by SurfaceItem \o/, so we just need a new type of item to present an image in the scene graph, e.g. ImageItem

The CursorItem type ties both cases together. It monitors what source is attached to the cursor and creates either a SurfaceItem or an ImageItem according to the type of the cursor source

  • If a SurfaceCursorSource is attached to the cursor, the CursorItem is going to destroy its child ImageItem (if there’s one) and create a SurfaceItem tree
  • If an ImageCursorSource or a ShapeCursorSource is attached to the cursor, the CursorItem is going to destroy its child SurfaceItem (if there’s one) and create an ImageItem child item

Note that SurfaceItems are rendered the same way regardless of their role.

With all of this, kwin can easily handle simple cases such as displaying an image in the cursor and more esoteric things that you can do with a wl_surface.

Red square is the cursor surface, the green square is its subsurface. No idea why you would want to do this, but kwin should handle this fine now 🙂

Cursor layer

The last thing that’s needed to make cursor handling perfect is putting the cursor on its own hardware plane. Imagine that you move the mouse cursor. Ideally, you should not repaint any windows below the cursor, only update the position of the cursor. Unless the windows need to repaint their content because of new pointer position, e.g. add or remove button outlines, etc. It can be achieved by painting the cursor in its own plane.

KWin already attempts to put the cursor on hardware planes, but we would like to clean things up and unify cursor and overlay planes. It is still work in progress. TBA.

Summary

The new cursor design is more powerful and should make it easier to add new fancy features. For example, it would be amazing if you could wire the velocity of the cursor to its scale so you could shake the pointer in order to find the cursor more easily. The new design also fixes longstanding limitations that prevented kwin from displaying cursors rendered by OpenGL or Vulkan.