Skip to content

kcursorgen and SVG cursors

Sunday, 12 January 2025  |  Jin Liu

In the latest Plasma 6.3 Beta, you will find a new executable named kcursorgen in /usr/bin. It can convert an SVG cursor theme to the XCursor format, in any sizes you like. Although this tool is intended for internal use in future Plasma versions, there are a few tricks you can play now with it and an SVG cursor theme.

(Unfortunately, the only theme with the support that I know, besides Breeze, is Catppuccin. I have this little script that might help you convert more cursor themes.)

Requirements

  1. The qt6-svg library.
  2. The xcursorgen command, usually found in xorg-xcursorgen package.

Trick 1: Cursors at any size you like

You should be able to set any cursor size with SVG cursors, right? Well, not at the moment, because:

  1. Only those apps using the Wayland cursor shape protocol would be using SVG cursors. Other apps still use the XCursor format, with a limited list of sizes.
  2. Plasma's cursor setting UI hasn't been updated to allow arbitrary sizes.

But we can do it manually with kcursorgen. Take Breeze for example:

Step 1: Make a copy of the theme

First, copy the cursor theme to your home directory. And let's change the directory name, so the original one is not overriden:

mkdir -p ~/.local/share/icons
cp -r /usr/share/icons/breeze_cursors ~/.local/share/icons/breeze_cursors.my

Then open ~/.local/share/icons/breeze_cursors.my/index.theme in the editor. Change the name in Name[_insert your locale_]= so you can tell it from the original in the cursor settings.

Step 2: Regenerate the XCursor files

For example, if we want a size 36 cursor, and the display scale is 250%:

cd ~/.local/share/icons/breeze_cursors.my
rm -r cursors/
kcursorgen --svg-theme-to-xcursor --svg-dir=cursors_scalable --xcursor-dir=cursors --sizes=36 --scales=1,2.5,3

Some Wayland apps don't support fractional scaling, so they will round the scale up. So we need to include both 2.5 and 3 in the scale list.

The above command generates XCursor at size 36, 90 and 108. Note that the max size of the original Breeze theme is 72, so this is something not possible with the original theme.

(kcursorgen also adds paddings when necessary, to satisfy alignment requirements of some apps / toolkits. E.g., GTK3 requires cursor image sizes to be multiple of 3 when the display scale is 3. So please use --sizes=36 --scales=1,2.5,3, not --sizes=36,90,108 --scales=1, because only the former would consider alignments.)

Then you can go to systemsettings - cursor themes, select your new theme, and choose size 36 in the dropdown.

(Yes, you can have HUGE cursors without shaking. Size 240.)

Yes, you can have HUGE cursors without shaking

Trick 2: Workaround for the huge cursor problem in GTK4

As explained before, Breeze theme triggers a bug in GTK4 when global scaling is used, resulting in huge cursors. It's because Breeze's "nominal size" (24) is different from the image size (32).

We can work around this problem by changing the nominal size to 32.

Step 1 is same as above. Then we modify the metadata:

cd ~/.local/share/icons/breeze_cursors.my
find cursors_scalable/ -name 'metadata.json' -exec sed -i 's/"nominal_size": 24/"nominal_size": 32/g' '{}' \;
rm -r cursors/
kcursorgen --svg-theme-to-xcursor --svg-dir=cursors_scalable --xcursor-dir=cursors --sizes=32 --scales=1,1.5,2,2.5,3

Then you can go to systemsettings - cursor themes, select your new theme, and choose size 32 in the dropdown. Cursors in GTK4 apps should be fixed now.

Extra idea: (For distro maintainers) reduce cursor theme package size to 1/10

It might be possible to only package the index.theme file and cursors_scalable directory for the Breeze cursor theme (and other SVG cursors themes), then in an postinstall script, use kcursorgen to generate the cursors directory on the user's machine.

This would greatly reduce the package size. And also you can generate more sizes without worrying about blown package size.

But the fact that kcursorgen is in the breeze package might make some dependency problems. I have an standalone Python script that does the same. (But it requires Python and PySide6.)

Comments