-
-
Notifications
You must be signed in to change notification settings - Fork 0
Cursor Themes
Super+Shift+P opens the cursor picker. synui-cursor is the command-line
side, and it is the only way to install a theme — the picker selects, it does
not install. That is deliberate, not missing: see
below.
Super+Cis the control panel andSuper+Shift+Cis cat mode, so P for pointer.
synui-cursor list # what's installed (name, path)
synui-cursor current # active theme and size
synui-cursor install ~/Downloads/some-theme.tar.gz
synui-cursor set Some-Theme 32 # theme, size in pxThe change reaches the running compositor immediately (synctl dispatch cursor_reload). Already-running apps keep the old cursor until they restart —
nothing can change that; X11 and GTK clients read the theme once.
synui-cursor list installed themes (name<TAB>path)
synui-cursor current active theme<TAB>size
synui-cursor install <archive|dir> install from .zip/.tar.*/a directory
synui-cursor build <dir> compile a source tree — runs ITS makefile
synui-cursor set <theme> [size] make a theme active everywhere
install accepts .zip, .tar.gz, .tar.xz, .tar.bz2, .tar.zst or a
directory, and installs into ~/.local/share/icons/<name>. A pack shipping
several themes side by side installs all of them.
set refuses a theme that is not installed. Setting a nonexistent one silently
gives you the fallback X cursor everywhere with nothing explaining why.
Cursor themes from opendesktop.org / gnome-look.org come in two forms, and telling them apart is most of what this tool does:
ready-to-use Theme/cursors/left_ptr, Theme/cursors/watch, … -> just move it
source tree makefile, src/*.in, config/*.in -> must be compiled
Extracting a source tree into ~/.local/share/icons — the obvious thing to
do, and an easy mistake to make — leaves something that is not a cursor theme
at all. No cursors/ directory, so nothing will ever see it, and nothing says
why.
synui-cursor install detects that case and tells you, instead of silently
creating a dud:
synui-cursor: 'some-cursors-3.1a.tar.gz' is a cursor theme SOURCE TREE,
not a built theme. It has no cursors/ directory yet — it has to be
compiled first, which usually needs xcursorgen and imagemagick.
…
synui-cursor build ~/.local/share/synui/cursor-src/some-cursors-3.1a
synui-cursor install ~/.local/share/synui/cursor-src/some-cursors-3.1a
The extracted tree is staged somewhere permanent before that message prints, because otherwise the command it tells you to run would name a temp directory the tool is about to delete — advice that cannot be followed.
Building needs xorg-xcursorgen and imagemagick.
A directory with a cursors/ subdirectory that has something in it.
Deliberately not index.theme: plenty of real themes ship without one, and
some source trees ship an index.theme while having no cursors at all — so
testing index.theme gets both cases backwards. An empty cursors/ is a build
that failed half way, and counts as not-a-theme rather than as a theme whose
every cursor is missing.
The same rule is implemented in the C picker and in the shell helper, on purpose, so the two can never disagree about whether something is installed.
install is safe to expose in a GUI; build is not. Building runs the
downloaded archive's own makefile, as you, with your privileges — arbitrary code
execution from an untrusted download. So it is never automatic and never a
button: it is a separate command that prompts for confirmation first.
If the picker ever grows an install button, build stays CLI-only.
These come off the internet, so install treats extraction as hostile.
Path traversal. Nothing is ever used straight out of the extraction
directory. Every theme path is re-resolved with realpath and refused if it no
longer sits inside that directory — which defeats ../../ traversal ("zip slip")
regardless of how the extractor behaves, rather than trusting tar and
unzip to reject hostile member names. An archive member called
../../../.config/autostart/x is otherwise a way to write anywhere you can.
Symlinks. Cursor themes legitimately use relative symlinks inside cursors/
(default -> left_ptr, and most of the hash-named aliases), so those must
survive — copying them as files would multiply a 1 MB animated cursor across a
dozen aliases. But an absolute symlink, or a relative one that climbs out of
the theme, is either a mistake or an attempt to get a later read or write
redirected. Both are dropped, and dropping one costs only that single alias.
(Sound themes have no legitimate internal symlink, so
synui-sound install drops all of them. Same
installer shape, different call on that one question.)
Theme names are attacker-controlled. A theme's name is a directory name from
the archive, and it goes on to be a shell argument (the picker runs
synui-cursor set <name>), a listing row, and an ini value. synui's spawn()
runs /bin/sh -c, so a theme called evil;curl …|sh would execute when
selected. Two independent layers:
- the helper refuses names with shell metacharacters, a leading dash (half the tools downstream would read it as an option), path separators, or control characters — refused outright rather than escaped three different ways;
- the picker single-quotes the name anyway.
Theme names are also third-party text on the render path, so they go through
draw_clipped (→ news_utf8_trim), never bare cairo_show_text — a name with
invalid UTF-8 in it would otherwise poison the cairo context.
This is the part that surprises people. Each toolkit has its own idea of where
the cursor theme lives, and disagreeing between them is exactly how you end up
with one cursor over synui and a different one over an Xwayland game.
synui-cursor set writes all five:
| # | Where | Who reads it |
|---|---|---|
| 1 | ~/.config/synui/cursor.state |
the compositor, at startup and on cursor_reload; the picker writes the same file |
| 2 | ~/.config/synui/cursor.env |
the session wrapper at login (XCURSOR_THEME, XCURSOR_SIZE) |
| 3 | ~/.icons/default/index.theme |
X11 proper — an Xwayland client started without the env vars (a game launched from a .desktop) |
| 4 | gsettings and gtk-3.0/settings.ini, gtk-4.0/settings.ini
|
GTK. gsettings reaches running apps live; settings.ini is what an app started outside the session bus reads |
| 5 |
kcminputrc (kwriteconfig6/5) |
Qt/KDE — Dolphin and friends read kcminputrc for this, not kdeglobals
|
Miss #3 and games get the fallback cursor. Miss the settings.ini half of #4 and
the change appears to work until the next reboot.
With it unset, libXcursor computes a size from the X screen, and the Xwayland virtual screen spanning several monitors is thousands of pixels wide, so an Xwayland client can end up with a huge pointer while synui's own is 24 px.
Session env vars live in three places: the live
/usr/local/bin/synui-sessionand two blocks insyn-install.sh. The wrapper sources~/.config/synui/cursor.envafter the defaults, soXCURSOR_THEMEis no longer hardcoded to Adwaita. The greeter's copy is deliberately left alone — different user, pre-login.
A theme is named after its own directory — except when an archive ships a
bare cursors/ at the top level with nothing wrapping it. Then the theme root
is the extraction directory, whose name came from mktemp, and only in that
case does the archive's filename supply the name. It still goes through the same
validation as any other name.
The installer stops descending the moment a directory is a theme. Archives
almost always wrap everything in one top-level directory, so it descends through
single-child directories — but a theme with no index.theme has cursors/ as
its only child, and descending into that would put the theme "above" the
extraction root and trip the escape guard on a perfectly good theme.
Anything that changes cursor settings reaches outside a sandbox. gsettings
talks to dconf over the session bus, so it ignores a faked $HOME — a
sandboxed test of synui-cursor set will rewrite your real GTK settings unless
gsettings and kwriteconfig* are stubbed. See
Development Notes.
wlr_xcursor_manager_create(NULL, 24) — theme from the environment, size
hardcoded, no setting anywhere. There was no cursor theme configuration at all.
See also: Sound Themes, synui, Keybindings, Commands.
Using it
- Installation
- Updating
- Software
- Files
- Keybindings
- Commands
- Nix
- Gaming
- DaVinci Resolve
- Secure Boot
- Troubleshooting
Customising it
Components
Apps
Hacking on it