2

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:

  1. 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.
  2. Rules for udev: I added two udev rules that call matchbox-remote when a mouse is plugged in or unplugged, with my toggle argument.
  3. 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 the udev 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:

  1. 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.
  2. 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:

  1. 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 the DISPLAY despite me manually setting it as an environment variable.
  2. 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:

  1. 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?)
  2. 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
Micrified
  • 3,338
  • 4
  • 33
  • 59

0 Answers0