Context
I am toggling the presence of the mouse cursor depending on whether or not a mouse is plugged into my Linux system. I have created a solution that works, although sub optimally. My current solution is the following:
- Modified WM: I extended a program supplied with my WM,
matchbox-remote.c
(see original source here), to allow a remote command that changes the cursor in real time. My change is based on this SO answer to a similar question. - Rules for udev: I added two
udev
rules that callmatchbox-remote
when a mouse is plugged in or unplugged, with my toggle argument. - Systemd service: I added a
oneshot
systemd service enables or disables the cursor on boot (depending on whether a mouse is plugged in or not). This addresses an edge-case where X is not yet ready when theudev
rules first run.
If you would like to see how I have accomplished each step, move to the bottom where I have numbered sections with the relevant files
Problem
My solution works, but is suboptimal for two reasons:
- The mouse cursor is present onscreen for a few seconds after the desktop loads if no mouse is plugged in, because the systemd rule runs after
multi-user.target
. - The mouse cursor remains present onscreen sometimes after being disabled. It disappears as soon as I interact with a button or other UI element. However, this can be quite annoying.
Attempted fixes
I have tried to do the following to improve my solution:
- Reduce latency on boot: I tried to adjust my service file to start earlier in the boot progress. I specifically targeted it to start after
xserver-nodm.service
. However, this still fails to find theDISPLAY
despite me manually setting it as an environment variable. - Disappear mouse pointer on event: I attempted to restart the WM, hoping that this would more seamlessly make the cursor disappear when it was supposed to. Unfortunately, this is a regressive solution as it makes the screen go black for several seconds and is more disruptive.
Conclusion
I would like help in solving the following:
- How can I change the cursor in a timely fashion at boot time (Do I need to modify X server or somehow queue events to send once it's reachable?)
- How can I ensure a change to the cursor is reflected immediately (as opposed to sometimes waiting until I interact with a UI element)?
More info
- Kernel: Linux 5.4.3
- Xorg: Version 1.20.5
(1): matchbox-remote.c
(in /usr/bin/
)
Display *dpy;
...
#include <X11/cursorfont.h>
#include <X11/extensions/Xfixes.h>
...
void set_show_cursor (int show)
{
Window root;
Cursor cursor;
Pixmap bitmap;
XColor color;
static char data[8] = {0};
root = DefaultRootWindow(dpy);
if (!show) {
color.red = color.green = color.blue = 0;
bitmap = xCreateBitmapFromData(dpy, root, data, 8, 8);
cursor = XCreatePixmapCursor(dpy, bitmap, bitmap, &color, &color, 0, 0);
XDefineCursor(dpy, root, cursor);
XFreeCursor(dpy, cursor);
XFreePixmap(dpy, bitmap);
} else {
cursor = XCreateFontCursor(dpy, XC_left_ptr);
XDefineCursor(dpy, root, cursor);
XFreeCursor(dpy, cursor);
}
}
...
static void usage(char *progname) {
...
printf(" -show-cursor [1|0] Enable or disable the cursor\n");
...
}
...
int main (int argc, char* argv[])
{
...
for (i=1; argv[i]; i++) {
...
switch (arg[1])
{
....
case 's':
if (NULL != argv[i+1]) {
set_show_cursor(atoi(argv[i+1]));
}
break;
...
}
}
XSync(dpy, False);
XCloseDisplay(dpy);
}
(2): 98-cursor-toggle.rules
(in /etc/udev/rules.d
)
SUBSYSTEMS="usb", ACTION=="add", ENV{ID_INPUT_MOUSE}=="1", RUN+="/bin/sh -c 'DISPLAY=:0 /usr/bin/matchbox-remote -show-cursor 1'"
SUBSYSTEMS="usb", ACTION=="remove", ENV{ID_INPUT_MOUSE}=="1", RUN+="/bin/sh -c 'DISPLAY=:0 /usr/bin/matchbox-remote -show-cursor 0'"
(3) cursor-init.service
(in /lib/systemd/system
)
[Unit]
Description=X11 cursor initialisation
After=multi-user.target
Requires=multi-user.target
[Service]
Type=simple
ExecStart=/bin/sh -c 'DISPLAY=:0 /usr/bin/matchbox-remote -show-cursor $(ls -1 /dev/input/by-*/*-mouse 2>/dev/null | wc -l)'
RemainAfterExit=true
StandardOutput=journal
Restart=on-failure
RestartUSec=500000
[Install]
WantedBy=graphical.target