Skip to content

Scalable Cursors - #2659

Merged
Drakulix merged 3 commits into
pop-os:masterfrom
hojjatabdollahi:hojjat/svg-cursor
Aug 25, 2026
Merged

Scalable Cursors#2659
Drakulix merged 3 commits into
pop-os:masterfrom
hojjatabdollahi:hojjat/svg-cursor

Conversation

@hojjatabdollahi

@hojjatabdollahi hojjatabdollahi commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

There are two implementations for svg cursors, hyprland and KDE.
xcursor up stream is waiting for other desktops to adopt one, or for KDE to upstream it.

KDE's approach makes more sense. This PR implements that.

This is the first draft, if this is something you want, and you support the approach, I can open a PR to xcursor and work on upstreaming and completing this feature.

Here's how it looks:

recording-2026-07-27_17-53-34-trimmed.mp4

Notes:

  • This branch is based on "shake to locate cursor" branch to test
  • The cursor theme I'm using to test is extracted from svg file in pop-os/icon-theme repo, and is not clean. We need to add proper cursor_scalable to pop icon theme.
  • You can use Breeze for testing:
sudo mkdir -p /usr/share/icons/breeze_cursors
curl -sL "https://invent.kde.org/plasma/breeze/-/archive/master/breeze-master.tar.gz?path=cursors/Breeze/Breeze" \
  | sudo tar xz --strip-components=4 -C /usr/share/icons/breeze_cursors
echo 'XCURSOR_THEME=breeze_cursors' | sudo tee -a /etc/environment
echo 'XCURSOR_SIZE=24'              | sudo tee -a /etc/environment

Closes #1370


  • I have disclosed use of any AI generated code in my commit messages.
    • If you are using an LLM, and do not fully understand the changes it is making to the code base, do not create a PR.
    • In our experience, AI generated code often results in overly complex code that lacks enough context for a proper fix or feature inclusion. This results in considerably longer code reviews. Due to this, AI authored or partially authored PRs may be closed without comment.
  • I understand these changes in full and will be able to respond to review comments.
  • My change is accurately described in the commit message.
  • My contribution is tested and working as described.
  • I have read the Developer Certificate of Origin and certify my contribution under its conditions.

@Drakulix

Copy link
Copy Markdown
Member

On first glance this looks good, but since this is based on #2610 we need wait for that to be approved first before progressing.

@hojjatabdollahi
hojjatabdollahi force-pushed the hojjat/svg-cursor branch 2 times, most recently from e404022 to efe890f Compare July 31, 2026 20:52
@hojjatabdollahi
hojjatabdollahi marked this pull request as ready for review July 31, 2026 20:53
@hojjatabdollahi

hojjatabdollahi commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

I removed the shake-to-locate commit. The upstream xcursor is also merged. So, this is ready to review.

You can use super+scroll to zoom and test.

recording-2026-07-31_15-03-16-trimmed.mp4

@hojjatabdollahi
hojjatabdollahi force-pushed the hojjat/svg-cursor branch 5 times, most recently from 24473ee to 6c63b8b Compare August 21, 2026 16:50

@Drakulix Drakulix left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks mostly good, but I am slightly concerned about the image_cache now. I don't think we have any routine to clean it up, which wasn't a big problem, when - at most - we could end up loading the whole active cursor theme with a bunch of 24x24 icons.

But when we cache arbitrary sized svg rasterizations (which we should), this can grow pretty large, especially since we intentionally don't cap the maximum cursor size.

I think this needs a cleanup function and call from State::cleanup. Easiest would probably be to make the image_cache into a VecDeque, push new entries to the front and then truncate to 10 in cleanup? (Assuming that newest images are likely re-used soon.)

This would also eliminate long searches for cache entries as the vector grows larger.

@hojjatabdollahi

Copy link
Copy Markdown
Contributor Author

That makes sense. I will add some telemetry to the compositor and monitor the cache hit/misses and size to see how bad does it get right now.

My concern is:

  • VecDeque doesn't address the unbounded cache in bytes (small cursors are <10kb, large ones get to 10s of megabytes).
  • Animated cursors would not like a small cache size, our wait cursor has 60 frames.
  • Mixed scale multi monitor or screencasting (since it's harcoded to 1 for scale) would also get messed up with a small cache.
  • In general the cache does very little for shake-to-find. When we're doing shake-to-find, it will miss on the way up, and miss on the way down if the cache is small. Only useful when we keep the size for 2 seconds at the top.

What if we do this:

  • Set a max size for rasterization. After which we're just scaling up, so the max memory usage is limited.
  • Maybe only cache on specific sizes and scale down to the target size? Not sure how noticeable it would look, but that would make the ladder much shorter. (I have to eyeball how it looks mid-tween).

@hojjatabdollahi

Copy link
Copy Markdown
Contributor Author

Here's what I did:

  • Added a max size for cursor rasterization (512px). Anything larger than that takes a noticeable amount of time to render and takes a large amount of memory. With this max size, we just scale up after 512px. If you zoom/shake too much the cursor will get a bit blurry, but I'd say that's acceptable.
  • Added a ladder with base * 2^x for the steps. So if you start with 24px as soon as you start enlarging the cursor (for zoom or shake-to-find), it will render 24*2^1 and down sample to the target size, so the cursor stays sharp.
  • The entries to the cache now are limitted, instead of "all sizes between base size to infinity" now it's "a limited number of x between 24 to 512 based on base*2^x".
  • And the cache gets cleared 10 seconds after the cursor rests (going back to the base size). So, if you're actively shaking the mouse, it doesn't clear the cache. The cache is also small and limited to begin with. And the cache only deletes magnified cursors

The only concern is what's the point of SVG then? We could just use pre-rasterize (bitmap) cursors with different sizes!

@leviport
leviport requested review from a team August 24, 2026 23:24

@leviport leviport left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursors are much sharper while zooming and/or shaking. I definitely see the upper threshold when it starts scaling the 512 version, but I have to shake/zoom pretty extremely to get to it.

@Drakulix

Copy link
Copy Markdown
Member

The only concern is what's the point of SVG then? We could just use pre-rasterize (bitmap) cursors with different sizes!

I mean, the current code should've already done that. But with svg's we don't need to ship every ever increasing size of cursors in a theme anymore. So it is mostly an improvement for theme builders.

To fully leverage the advantages in the compositor, we would have to upload the path instead and rasterize on the gpu. I believe there is some nvidia-specific extension to do that and potentially more generic svg-render pipelines exist, but that is an optimization that definitely can come later.

@Drakulix Drakulix left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM code-wise now. Thanks!

@Drakulix
Drakulix merged commit 31827ed into pop-os:master Aug 25, 2026
5 of 6 checks passed
@jinliu

jinliu commented Aug 26, 2026

Copy link
Copy Markdown

The only concern is what's the point of SVG then? We could just use pre-rasterize (bitmap) cursors with different sizes!

Yes we can. But the installed size would bloat quickly, because the XCursor format is uncompressed.

And there are nice things that SVG could enable. E.g., re-color cursors to the theme color.

But with svg's we don't need to ship every ever increasing size of cursors in a theme anymore.

Yes, but post installation, we still need to generate XCursor files for all cursor sizes we support, for backward compatibility with X11 apps.

E.g., https://github.com/jinliu/svg-cursor/tree/main/svg-theme-to-xcursor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support rendering SVG cursors

4 participants