Skip to content

About

Linux configuration tool for Pulsar gaming mice (X2A, Xlite v4, Feinmann 8K, X2H, and more)

Topics

Resources

Stars

30 stars

Watchers

1 watching

Forks

Repository files navigation

Pulsar Mouse Linux

pulsar-mouse-linux

Linux configuration tool for Pulsar gaming mice.

Plugin architecture — each mouse model has its own protocol driver. Currently supports the Pulsar Xlite Wired, Pulsar X2 Wired, Pulsar X2H Wired Medium, Pulsar X2A Medium Wired, Pulsar Xlite v4, Pulsar Feinmann 8K / FO1, Pulsar wireless mice on the Nordic chipset (21 models), Pulsar X2 CrazyLight, Pulsar X2 Wireless (Areson), and the Pulsar Feinmann F01 Noctua Edition — see Supported Mice below.

Reverse-engineered from USB HID captures of Pulsar Fusion on Windows 11. Wireless (Nordic) protocol based on python-pulsar-mouse-tool by andrewrabert.

Screenshots

Home Performance
Home Performance
Colour Picker Customize
Colour Picker Customize
Button Remap Power
Remap Button Power
Tools Input Test
Tools Input Test
System Tray
Tray

These are the real GUI reading a real mouse, so they only show what that mouse can do — a wired one has no Power page, and only some models report link quality or have a fan. If yours shows more, tools/screenshots.py regenerates the whole set in one run; see docs/screenshots.md.

Supported Mice

Model Driver VID:PID Status
Pulsar Xlite Wired xlite_wired 3710:1401 Supported (Sonix, 50 DPI step)
Pulsar X2 Wired x2_wired 3710:1402 Supported (Sonix)
Pulsar X2H Wired Medium x2h 3710:1403 Fully supported
Pulsar X2A Medium Wired x2a 3710:1404 Fully supported
Pulsar Xlite v4 xlite_v4 3710:3401 Mostly untested (same Sonix protocol); its lift-off register and profile list were read on both connections — it keeps the X3's mode-and-tenths lift-off and its three settings (0.7/1/2 mm, the shortest written and read back off the mouse), has six profiles, and carries the XS-1's DPI range like the rest of the Dongle Kit
Pulsar X3 Wired x3 3710:3409 Supported (same Sonix protocol as the Xlite v4; battery while charging, sleep timer and Low Power Mode confirmed on hardware on its cable; six profiles, DPI to 32000 in steps of 10, and its own lift-off encoding - 0.7/1/2 mm, as Fusion2 writes it)
Pulsar 1K Wireless Dongle xlite_v4_wireless 3710:5402 Supported (1K wireless dongle shared by the Fusion2 line, same Sonix protocol; battery, charging, sleep timer, Low Power Mode, link quality and which mouse is on the dongle confirmed on hardware with a Pulsar X3; asks which mouse it carries and then describes that one — its profiles, DPI range and lift-off distances, including the X3's 0.7 mm)
Pulsar X3 LHD x3_lhd 3710:3410 Supported (the X3's driver under the left-handed model's own ID and name; settings, stage tables, lift-off and battery while charging confirmed on hardware on its cable)
Pulsar 8K Wireless Dongle fusion2_8k_dongle 3710:5403 Supported (the 1K dongle's driver under its own ID; battery, profiles, DPI stages, lift-off, polling rate up to 8000 Hz and which mouse is on the dongle confirmed on hardware with a Pulsar X3 LHD; it answers that last one under the previous request's header)
Pulsar Feinmann 8K / FO1 feinmann8k 3710:5404 Fully supported (wireless dongle, 8K Hz polling, 6 onboard profiles)
Pulsar wireless (Nordic): Xlite, Xlite V3, X2, X2A, X2H, X2 V2 and their Mini/Large/Custom variants nordic 3554:f507 3554:f508 3554:f509 Supported (Nordic chipset, battery status, sleep timer 10 s–10 min; 21 models share these IDs and the protocol, so one driver serves them all and asks the mouse which it is — only the X2A Wireless, X2A Mini and the two KM=14 X2 Customs have the right-hand thumb pair, and on the rest the driver refuses those two rather than writing to slots their firmware uses for something else)
Pulsar X2 CrazyLight Wireless x2_crazylight 3710:5406 Fully supported, reads and writes confirmed on hardware (Nordic protocol under the Sonix VID, 8K Hz polling, 4 profiles)
Pulsar X2 CrazyLight Wired x2_crazylight_wired 3710:3414 Fully supported, reads and writes confirmed on hardware (same protocol as the dongle, 1K Hz max)
Pulsar X2 Wireless (Areson dongle) x2_areson_wireless 25a7:fa7c Reads and writes confirmed on hardware (Nordic layout; GUI and button remapping tested)
Pulsar Feinmann F01 Noctua Edition (dongle) feinmann_noctua 3710:5504 Fully supported, reads and writes confirmed on hardware (Nordic protocol despite the Feinmann name; fan control; no LEDs; DPI to 42000 at 1 DPI steps)
Pulsar Feinmann F01 Noctua Edition (wired) feinmann_noctua_wired 3710:7507 Fully supported, reads and writes confirmed on hardware (same protocol as the dongle; 8K Hz over the cable)
Pulsar X2 Wireless (Areson cable) x2_areson_wired 25a7:fa7b Reads and writes confirmed on hardware (Nordic layout; 1K Hz max; GUI tested; charging LED overrides RGB effects while cabled)

Want to add support for your mouse? See Adding a new driver below.

Requirements

Debian / Ubuntu:

sudo apt install python3-usb python3-gi gir1.2-gtk-4.0 gir1.2-adw-1 gir1.2-dbusmenu-glib-0.4

Fedora:

sudo dnf install python3-pyusb python3-gobject gtk4 libadwaita libdbusmenu

Arch Linux:

sudo pacman -S python-pyusb python-gobject gtk4 libadwaita libdbusmenu-glib

On GNOME you also need the AppIndicator shell extension for the tray icon to appear:

sudo apt install gnome-shell-extension-appindicator   # Debian/Ubuntu
# then enable it (Ubuntu):
gnome-extensions enable ubuntu-appindicators@ubuntu.com
# or on other distros:
gnome-extensions enable appindicatorsupport@rgcjonas.gmail.com

Then restart GNOME Shell (log out/in, or Alt+F2 → r → Enter on X11).

KDE Plasma & Hyprland support the system tray natively — no extra extensions needed.

Installation

Option 1: Package (.deb / .rpm)

Download from the latest release:

# Debian / Ubuntu
sudo dpkg -i pulsar-mouse-linux_*.deb

# Fedora
sudo dnf install ./pulsar-mouse-linux-*.noarch.rpm

Option 2: AppImage (any distro, no install)

chmod +x pulsar-mouse-linux-*-x86_64.AppImage
./pulsar-mouse-linux-*-x86_64.AppImage          # GUI
./pulsar-mouse-linux-*-x86_64.AppImage --cli     # CLI

Bundles pyusb. Requires system Python 3, PyGObject, GTK4, and libadwaita (the same packages listed in Requirements above). udev rules must be installed separately (see below).

Option 3: Tarball (any distro)

tar xzf pulsar-mouse-linux-*.tar.gz
cd pulsar-mouse-linux-*
sudo ./install.sh

Option 4: From git

git clone https://github.com/packerlschupfer/pulsar-mouse-linux
cd pulsar-mouse-linux
pip install --user -e .

Option 5: Arch Linux

A PKGBUILD lives in packaging/, so no AUR account or helper is needed:

git clone https://github.com/packerlschupfer/pulsar-mouse-linux
cd pulsar-mouse-linux/packaging
makepkg -si

It pulls its dependencies from the official repositories, installs the entry points to /usr/bin and the udev rules to /usr/lib/udev/rules.d, and upgrades cleanly with pacman -U on later releases.

To track main instead — worth it if you're testing something from an issue, since --version reports the last release either way — use makepkg -p PKGBUILD-git -si, whose package version names the commit. See packaging/README.md for what changes per release.

(Not on the AUR yet: registration there is paused while they deal with a wave of automated sign-ups. The PKGBUILD is ready to publish the moment it reopens — or sooner, if someone with an existing account would like to maintain it.)

Option 6: Nix / NixOS

Ships a flake. Run it directly without installing:

nix run github:packerlschupfer/pulsar-mouse-linux          # GUI
nix run github:packerlschupfer/pulsar-mouse-linux#cli      # CLI

Or add it as a flake input:

inputs.pulsar-mouse-linux = {
  url = "github:packerlschupfer/pulsar-mouse-linux";
  inputs.nixpkgs.follows = "nixpkgs";
};

then either reference inputs.pulsar-mouse-linux.packages.${system}.default directly, or pull in inputs.pulsar-mouse-linux.overlays.default and use pkgs.pulsar-mouse-linux as normal. The package ships the udev rules under lib/udev/rules.d/ — on NixOS, add it to services.udev.packages = [ pkgs.pulsar-mouse-linux ]; and they'll be picked up automatically, no manual udevadm steps needed.

Also published as a rolling release on FlakeHub — use this instead of the github: reference if you'd rather resolve through FlakeHub's CDN/registry than GitHub directly:

nix run "https://flakehub.com/f/packerlschupfer/pulsar-mouse-linux/*.tar.gz"
inputs.pulsar-mouse-linux = {
  url = "https://flakehub.com/f/packerlschupfer/pulsar-mouse-linux/*.tar.gz";
  inputs.nixpkgs.follows = "nixpkgs";
};

udev rules (run without sudo)

Packages install udev rules automatically. For git installs:

sudo cp udev/50-pulsar-mouse.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules && sudo udevadm trigger

Access is granted via TAG+="uaccess" (systemd-logind ACL) — no group membership or re-login needed; just re-plug the mouse.

GUI + System Tray

pulsar-mouse-gui    # if installed via .deb
# or: PYTHONPATH=src python3 -m pulsar_mouse.gui

GTK4 + libadwaita settings window with integrated system tray:

  • Auto-detects connected Pulsar mouse and adapts UI to its capabilities
  • Reads all settings from the mouse on startup, writes on Apply
  • System tray via D-Bus StatusNotifierItem (no GTK3 conflict)
  • Tray shows DPI/polling rate on change, quick DPI presets, polling rate radio buttons
  • X hides the window (tray stays alive), Quit from tray menu exits
  • "Start on Login" toggle on the Tools page for autostart
  • "Start in Tray", under Start on Login on the Tools page, leaves the window closed at login. The tray icon or a later launch still opens it. If nothing is hosting a tray, the window opens after a short wait
  • Input test dialog with mouse diagram and event log
  • Battery level and charging state (on supported wireless mice) shown on the Home page and as a tray icon overlay - polled every 60s, so plugging/unplugging the charging cable can take up to a minute to be reflected. This is deliberate, not a bug: polling more often would mean more frequent USB interface claims and a higher chance of interfering with the mouse's own wireless power-saving sleep.
  • Desktop mouse settings (GNOME acceleration profile, pointer speed)

CLI usage

# Show all settings (auto-detects mouse)
sudo pulsar-mouse

# Show one profile
sudo pulsar-mouse --profile 1

# Polling rate (global)
sudo pulsar-mouse --poll 1000      # 125 / 250 / 500 / 1000

# Debounce (global, ms)
sudo pulsar-mouse --debounce 3

# Switches (global: on/off)
sudo pulsar-mouse --angle-snap off
sudo pulsar-mouse --ripple off
sudo pulsar-mouse --motion-sync off

# DPI stages for profile 1 (up to 6 stages)
sudo pulsar-mouse --profile 1 --dpi 400,800,1600,3200
sudo pulsar-mouse --profile 1 --dpi 400,800,1600,3200 --active-stage 2

# Lift-off distance (per-profile)
sudo pulsar-mouse --profile 1 --lod 1    # 1 mm or 2 mm

# LED brightness and effect (per-profile)
sudo pulsar-mouse --profile 1 --brightness 200
sudo pulsar-mouse --profile 1 --led steady
sudo pulsar-mouse --profile 1 --led breathe --breathe-speed 50

# Per-stage LED colour (per-profile, RGB 0-255)
sudo pulsar-mouse --profile 1 --stage-color 1 255 0 0    # stage 1 = red

# Button remapping (per-profile)
sudo pulsar-mouse --profile 1 --button thumb1 dpi+
sudo pulsar-mouse --profile 1 --button thumb1 ctrl+c
sudo pulsar-mouse --profile 1 --button thumb1 vol+       # media keys
sudo pulsar-mouse --profile 1 --button thumb1 'media(0x0192)'  # any HID usage (quote it)

# Profile backup/restore (per-profile)
sudo pulsar-mouse --profile 1 --export profile1.json
sudo pulsar-mouse --profile 2 --import profile1.json

# Battery/charging status as JSON, for scripts (wireless mice only)
pulsar-mouse --battery-json

Noctalia Plugin

There's a Noctalia shell plugin backed by this CLI — a bar widget, a desktop/lock-screen widget showing battery percentage and charging status, and a quick-controls panel (DPI, polling rate, debounce, angle snap/ripple/motion sync, lift-off distance, LED, and on wireless mice power management, plus a profile switcher on multi-profile mice). Works with just the pulsar-mouse CLI installed; more efficient if pulsar-mouse-gui is also running, since the widgets read its periodic reading instead of polling the mouse themselves.

It used to live in this repo at pulsar-mouse/, but moved to its own repo, pulsar-mouse-noctalia, since noctalia-dev/community-plugins (submitted here) expects each plugin as its own directory with no unrelated code alongside it. See that repo's README for settings and details.

Once the submission is merged, installing it will just be a search away in Noctalia's plugin store. Until then (or for local development), add it as a path source:

noctalia msg plugins source add pulsar-mouse path ~/dev/pulsar-mouse-noctalia   # or wherever you cloned that repo
noctalia msg plugins enable harveywuk/pulsar-mouse

Then add the bar or battery widget from Noctalia's widget editor.

Low-battery warning

Off by default. Switch it on under Tools and the app sends one desktop notification when the battery falls to your threshold — once per discharge, not once a minute. Charging clears it, as does recovering five points above the threshold, so a reading sitting on the line doesn't warn twice. "Keep the warning until I dismiss it" asks the desktop never to expire it, at normal urgency, so it still respects Do Not Disturb.

It uses the reading the tray already takes every minute, so nothing polls the mouse more often, and it only runs while pulsar-mouse-gui does. The warning's settings live in ~/.config/pulsar-mouse/config.json, written only when you change one. Start in Tray is stored in that file too.

Battery data for widgets

Two ways to read it, both meant for exactly that, so a widget never has to talk to the mouse itself — which matters because only one program can hold a mouse's config interface at a time:

pulsar-mouse --battery-json
{"wireless": true, "battery_percent": 86, "power_connected": false, "battery_mv": 4201}

and, while pulsar-mouse-gui is running (the window or just the tray), the same reading lands in ~/.cache/pulsar-mouse/battery.json on every poll, plus signal_percent and updated_at. It is written to a temporary file and renamed into place, so a reader always sees a complete file.

battery_mv is null on most mice: of the drivers here, only the Nordic-protocol ones report millivolts, and they are worth having where they exist — a percentage often moves in steps of 5 where the voltage moves continuously and dips under sensor load. wireless is false, with nothing else, on a mouse that has no battery.

Other projects are welcome to emit the same shape; espulse already does, so one widget can read either.

Adding a new driver

Each Pulsar mouse model uses a different USB protocol. To add support for a new model:

  1. Create src/pulsar_mouse/drivers/yourmodel.py — one driver class per module. Built-in discovery, which the .deb and AppImage rely on, keys drivers by module name, so a second class in the same file silently replaces the first. If the mouse shows up under different PIDs on the cable and the dongle, give each connection its own module (see x2_crazylight.py / x2_crazylight_wired.py)
  2. Subclass PulsarDevice from pulsar_mouse.base
  3. Define capabilities as a class variable (a DeviceCapabilities dataclass)
  4. Implement the protocol methods (open, close, get/set_polling_rate, get/set_dpi_stages, etc.)
  5. Add an entry point in pyproject.toml:
    [project.entry-points."pulsar_mouse.drivers"]
    yourmodel = "pulsar_mouse.drivers.yourmodel:YourClass"
    The entry-point name must match the module name; otherwise a pip install registers the driver twice
  6. Add udev rules for the new VID/PID in udev/50-pulsar-mouse.rules
  7. Run PYTHONPATH=src python3 tools/check-drivers.py. CI runs the same checks, which catch mistakes that still compile and import, such as a second driver class in one module, a missing udev rule, or a DPI range the encoder can't express

The CLI and GUI will automatically detect the new driver and adapt their UI.

External driver packages can also register via entry points without modifying this repo.

Need to reverse-engineer the protocol first? See docs/usb-capture-guide.md for step-by-step Wireshark/USBPcap instructions and tools/parse-capture.py to decode the captures.

OS Tweaks for Gaming

The GUI has a "Desktop Mouse Settings" section on the Performance page that auto-detects whether GNOME, Hyprland, or KDE Plasma is running and shows the matching controls.

Disable mouse acceleration (recommended for FPS gaming)

# GNOME (Wayland or X11)
gsettings set org.gnome.desktop.peripherals.mouse accel-profile 'flat'
gsettings set org.gnome.desktop.peripherals.mouse speed 0   # 0 = neutral

# Hyprland - live immediately, but runtime-only; add the input {} block
# below to your hyprland.conf (or a sourced file) to persist it
hyprctl keyword input:accel_profile flat
hyprctl keyword input:sensitivity 0.0
# input {
#     sensitivity = 0.0
#     accel_profile = flat
# }

# KDE Plasma - written to kcminputrc, requires kwriteconfig5/6
kwriteconfig6 --file kcminputrc --group Mouse --key XLbInptAccelProfileFlat true
kwriteconfig6 --file kcminputrc --group Mouse --key XLbInptPointerAcceleration 0.0

USB polling interval — check, don't set

You will find advice around to add usbhid.mousepoll=1 to your kernel command line "for lower latency". Don't, on these mice. This README used to say it too, which is the reason it is spelled out here.

That parameter is in milliseconds and it overrides what the mouse asks for, in both directions. These mice already ask for the fastest interval the bus has — every endpoint on an X2A reports bInterval=1 — so at best the setting changes nothing, and on a mouse running above 1000 Hz it is a cap: 1 ms is 1000 Hz, where an 8K dongle here was measured sending about 7200 reports a second.

What to check, if you suspect something is throttling your mouse:

cat /sys/module/usbhid/parameters/mousepoll     # 0 is what you want

0 means the kernel uses the interval the mouse requests, which is the right answer for every mouse this app supports. Anything else was set by you or by your distribution, and on a high-rate mouse it is worth removing from /etc/default/grub (then sudo update-grub and reboot).

To see what your own mouse asks for, with its bus address from lsusb (Bus 001 Device 009 below is 1-6.4 or similar in the paths — the easiest way is to look at all of them and pick out yours):

lsusb | grep -i pulsar
grep . /sys/bus/usb/devices/*:*/ep_*/bInterval

01 is one interval, the fastest the bus offers. On this maintainer's X2A every endpoint reads 01, while the hubs and keyboards around it read 0c.

Protocol notes (X2A)

Setting Scope cat reg (write/read) sub
Polling rate global 0x01 0x09 / 0x89 0x02
Debounce global 0x04 0x03 / 0x83 0x03
Angle snap global 0x07 0x04 / 0x84 0x02
Ripple control global 0x07 0x03 / 0x83 0x02
Motion sync global 0x07 0x05 / 0x85 0x02
LOD per-profile 0x07 0x02 / 0x82 0x03
DPI stages (bulk) per-profile 0x05 0x04 / 0x84 0x21 / 0x15
DPI active stage per-profile 0x05 0x01 / 0x81 0x02
Stage LED colour per-profile 0x05 0x05 / 0x85 0x05
Brightness per-profile 0x03 0x03 / 0x83 0x03
LED effect per-profile 0x03 0x04 / 0x84 0x0F

Packet: 64 bytes, Interface 3, HID Feature report (wValue=0x0300). Checksum: bytes[62:64] = LE uint16(sum(bytes[0:62])).

Credits

One line each; the detail behind them is in the commits, the issues those link, and the code comments next to whatever each finding explains.

  • @packerlschupfer — Original creator and maintainer of pulsar-mouse-linux
  • @harveywuk — Feinmann 8K/FO1 driver, multi-profile support, button remapping, desktop pointer-settings integration, Nix flake, GUI redesign, protocol reliability fixes (stale-reply guard, LED read-modify-write, RF settle timing)
  • @variasjon — Xlite v4 wireless dongle driver, and the readings on both its connections that gave that mouse its lift-off encoding, its third lift-off setting, its sixth profile and its real DPI range
  • @f-o-o-g-s — Pulsar X3 and 1K dongle support, Fusion2 captures and protocol decoding, the dongle's lost-request recovery, write pacing and DPI range, the GUI's event listeners and read-reliability fixes, the profile-change and button-mask reports, the media usages measured past the eight this app named, the screenshot set, and a great deal of hardware testing
  • @mwsmws22 — Nordic keyboard shortcut fix (shortcut-table writes, Xlite V3 button mapping)
  • @Scout339 — Logo design, wireless mouse testing
  • @iamtherobin — Pulsar Fusion USB captures and screen recordings that the X2 CrazyLight drivers (dongle and wired) were decoded from
  • @dbergPROTAKT — Pulsar X2 Wireless (Areson IDs) driver, with hardware testing over both the dongle and the cable; the hidraw framing checks that keep another program's replies out of ours, and the swap and retry timing behind the sleeping-mouse fixes
  • @martingem — The low-battery warning and Start in Tray, each proposed as a design before any code, plus the fix for a missing session bus leaving the GUI with no first read, no event listener and no battery timer
  • @macieqzz — Pulsar X2A Wireless button layout confirmed on hardware (thumb3/thumb4/DPI addresses and function slots)
  • @jonkristian — Pulsar X3 LHD and 8K dongle support, decoded from hardware including the dongle's stale-header answer, and the log that settled the link-up/down report
  • @bluesceada — Xlite V3: found that it shares the X2A Wireless's USB IDs and differs only in the key matrix, the captures that decoded the CID/MID identity read, the readings that corrected the Nordic firmware string, the Cfg.ini model table, the captures that measured the sleep timer, the two dumps that decoded the media-key record, and the libadwaita 1.5 fallback that fixed the GUI on Ubuntu 24.04
  • @Wyatt-Robinson — Feinmann F01 Noctua Edition: USB capture and screen recording the driver was decoded from, plus hardware testing
  • meniscus- — Found that a wheel turn over a GUI slider silently changed the setting, on an X2 CrazyLight
  • andrewrabert — python-pulsar-mouse-tool, reference implementation for the Nordic wireless protocol

Related Projects

  • python-pulsar-mouse-tool — Linux tool for the Pulsar X2 V2 Mini (wireless, battery support)
  • espulse — Linux tool for the Pulsar eS line, whose dongles speak a third protocol this app does not: report 0x0e with an a5 magic and an opcode byte, documented in that repo

About

Linux configuration tool for Pulsar gaming mice (X2A, Xlite v4, Feinmann 8K, X2H, and more)

Topics

Resources

Stars

30 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages