Skip to content

Installation

JuhRadial MX builds from source on every supported distribution. The fastest path is the one-line installer, which detects your distro, pulls the right packages, compiles the Rust daemon, and wires up autostart and device permissions for you. Manual and NixOS paths are documented below for people who prefer to drive each step themselves.

JuhRadial MX

Note

JuhRadial MX targets Wayland first (KDE Plasma 6, GNOME, Hyprland, COSMIC, Sway, niri) and also runs on X11. See Compositor-Support for per-compositor behaviour, and Features for what you get once it is running.

Contents

Quick start: the one-line installer

curl -fsSL https://raw.githubusercontent.com/JuhLabs/juhradial-mx/master/install.sh | bash

The script is interactive: it prints a summary of your system (distro, kernel, session, desktop, whether a Logitech receiver is detected) and asks you to confirm before it changes anything.

Warning

Do not run the installer as root or under sudo. It refuses to run as root and asks for sudo only on the individual steps that need it (package install, copying files into /usr/local and /usr/share, installing udev rules). The systemd service and config live in your user account, so it must run as your normal user.

What the installer does

The installer (install.sh) runs as a fresh install or an in-place upgrade, depending on whether /opt/juhradial-mx already exists. A full run has six visible steps plus a preflight phase.

Phase What happens
Detect Reads /etc/os-release to resolve a distro family (arch, fedora, debian, opensuse), falling back to ID_LIKE and then to the available package manager (pacman, apt-get, dnf, zypper). Detects Wayland vs X11, the desktop/compositor, an existing install, and whether a Logitech device (USB vendor 046d) is present.
1. Dependencies Installs the build and runtime packages for your distro family (Rust/Cargo, Python + PyQt6, GTK4/libadwaita, gtk4-layer-shell, HID and evdev dev headers, D-Bus/systemd dev headers, ydotool, git, make).
2. Fetch source Downloads this version's release tarball (a source snapshot plus a prebuilt daemon) and checks it against the SHA256SUMS published with the release. Without a release, or when the tarball does not match, it clones https://github.com/JuhLabs/juhradial-mx into /opt/juhradial-mx instead (on upgrade: git fetch + git reset --hard origin/master + git clean -fd).
3. Build daemon Uses the release's prebuilt daemon when it runs on this system; otherwise ensures a usable Rust toolchain (see the rustup note below) and runs cargo build --release in daemon/.
4. Install files Copies the daemon binary, overlay scripts, theme/device assets, launcher scripts, desktop entries, icons, the systemd user unit, and udev rules into place (see layout table).
5. Desktop integration Applies compositor-specific setup (Hyprland window rules, GNOME cursor-helper extension). KDE, Sway, and COSMIC need no extra config.
6. Enable service Reloads the systemd user manager, enables juhradialmx-daemon, sets up ydotoold autostart, then starts (or restarts) the daemon.

Install layout

Path Contents
/opt/juhradial-mx Cloned source tree (used for updates)
/usr/local/bin/juhradiald Compiled Rust daemon
/usr/local/bin/juhradial-mx, /usr/local/bin/juhradial-settings Launcher scripts
/usr/share/juhradial/ Overlay Python, Flow module, locales, theme and device assets
/usr/share/applications/ .desktop entries for the app and settings
~/.config/systemd/user/juhradialmx-daemon.service systemd user service
~/.config/juhradial/ Your config.json (see Configuration)

udev rules, uinput, and the input group

For non-root access to the mouse and for action injection, the installer:

  • Installs 99-juhradialmx.rules into /etc/udev/rules.d/ and removes the older 99-logitech-hidpp.rules if present.
  • Installs 70-juhradialmx-uaccess.rules, which gives the logged-in user access to Logitech hidraw and input nodes as soon as they appear (systemd-logind uaccess), so the MX devices work right after installing and after every Bluetooth reconnect, without logging out.
  • Installs 60-ydotool-uinput.rules so the logged-in user can open /dev/uinput (used by both ydotool and the daemon's own virtual input device). Stock distros ship /dev/uinput as root:root 0600.
  • Loads the uinput kernel module now (modprobe uinput) and on boot (/etc/modules-load.d/juhradial-uinput.conf).
  • Reloads rules (udevadm control --reload-rules + udevadm trigger).
  • Ensures the input group exists and adds your user to it. The group covers what the uaccess rule does not: devices of other makers, which macro recording and generic-mode mice read, and sessions that are not on the local seat.

Info

Your MX devices work right away through the uaccess rule. If your user was just added to the input group, log out and back in once for that too: until then macro recording and generic-mode mice cannot open devices of other makers. juhradial-mx doctor shows which applies.

ydotoold (Wayland action injection)

Keyboard-shortcut button actions and thumb-wheel actions (volume, zoom, horizontal scroll) are injected through the kernel uinput device via ydotool, which needs its background daemon ydotoold running. The installer prefers a packaged ydotool.service / ydotoold.service if your distro ships one; otherwise it writes a minimal ~/.config/systemd/user/ydotoold.service and enables it so it autostarts on login. If ydotoold is missing, shortcut and thumb-wheel actions may not work on Wayland.

The rustup bootstrap

The committed Cargo.lock is lockfile format v4 (needs Cargo >= 1.78) and the toml_edit dependency needs rustc >= 1.76. Several distros ship an older toolchain (Ubuntu 24.04 ships Rust 1.75), which cannot build the daemon.

The installer checks the active Cargo version and, if it is older than 1.78 or missing entirely, bootstraps the official toolchain non-interactively:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal
. "$HOME/.cargo/env"

Tip

On Ubuntu 24.04 this happens automatically. If you are installing manually there and cargo build fails on the lockfile version, run the two commands above (or rustup update stable) and rebuild.

Checking a release download

Every release lists the SHA-256 of each file in SHA256SUMS, and from 0.4.5 Beta 6 on it carries build provenance: a signed record, made by the release workflow, of the commit and the workflow run that built each file. The installer checks the tarball against SHA256SUMS before it unpacks it. To check a file you downloaded yourself, from the folder it is in:

sha256sum -c SHA256SUMS --ignore-missing
gh attestation verify juhradial-mx-0.4.5-beta.6-linux-x86_64.tar.gz --repo JuhLabs/juhradial-mx

gh attestation verify also takes the release's juhradial-mx-<version>.sigstore.json file with --bundle.

Bazzite, Fedora Atomic and other image-based systems

On image-based systems /usr is read-only, so the installer switches to user mode by itself (it checks /run/ostree-booted). You can also ask for it on any distro:

curl -fsSL https://raw.githubusercontent.com/JuhLabs/juhradial-mx/master/install.sh | bash -s -- --user

User mode puts everything under your home folder and uses sudo only for the udev rules, the uinput module and adding you to the input group (on Atomic systems it first copies the input entry from /usr/lib/group into /etc/group, which usermod needs). Add --yes to skip the questions.

Path Contents
~/.local/share/juhradial-mx Source tree (used for updates)
~/.local/bin/juhradiald, juhradial-mx, juhradial-settings Daemon and launchers
~/.local/share/juhradial/ Overlay, Qt settings app, Flow module, locales, assets
~/.local/share/applications/ .desktop entries (they name the launchers by full path)
~/.config/systemd/user/juhradialmx-daemon.service systemd user service (ExecStart=%h/.local/bin/juhradiald)

Runtime packages (PyQt6, Qt SVG and Declarative, GTK 4, libadwaita, python-cryptography, ydotool) are checked with rpm -q; the installer offers to layer the missing ones with rpm-ostree install (a reboot makes them active). The daemon comes prebuilt with a release; without one it is compiled with the host toolchain, or inside a Fedora toolbox through distrobox when the host has no compiler (Bazzite and Aurora ship distrobox).

To uninstall a user-mode install:

systemctl --user disable --now juhradialmx-daemon.service
rm -f ~/.local/bin/juhradiald ~/.local/bin/juhradial-mx ~/.local/bin/juhradial-settings
rm -rf ~/.local/share/juhradial-mx ~/.local/share/juhradial
rm -f ~/.local/share/applications/juhradial-mx.desktop ~/.local/share/applications/org.kde.juhradialmx.settings.desktop
rm -f ~/.config/systemd/user/juhradialmx-daemon.service ~/.config/autostart/juhradial-mx.desktop
sudo rm -f /etc/udev/rules.d/99-juhradialmx.rules /etc/udev/rules.d/70-juhradialmx-uaccess.rules /etc/udev/rules.d/60-ydotool-uinput.rules && sudo udevadm control --reload-rules

Requirements

  • A Wayland compositor (KDE Plasma 6, GNOME, Hyprland, COSMIC, Sway, niri) or X11.
  • Rust toolchain (Cargo >= 1.78) to build the daemon. The installer provides this if needed.
  • Python 3 with PyQt6 (overlay and the Qt/QML settings app; the Qt settings app needs Qt 6.4 or newer) and GTK4 + libadwaita via PyGObject (the fallback GTK settings app below that).
  • XWayland, used for overlay window positioning on Wayland.
  • A supported mouse (Logitech MX Master 4 / 3S / 3 for full HID++, or any mouse in generic evdev mode). See FAQ for device coverage.

Manual installation per distro

Each manual path is: install dependencies, clone, build the daemon, then run. To get autostart, udev rules, the input group, and ydotoold set up exactly as the one-line installer does, prefer the installer or replicate the steps in What the installer does.

Fedora

Family also covers RHEL, CentOS Stream, Rocky, AlmaLinux, Nobara, Ultramarine.

sudo dnf install -y \
    rust cargo \
    python3 python3-pip \
    python3-pyqt6 qt6-qtsvg qt6-qtdeclarative \
    python3-gobject gtk4 libadwaita \
    gtk4-layer-shell \
    python3-cryptography \
    dbus-devel systemd-devel \
    libevdev-devel hidapi-devel \
    ydotool \
    git make

Ubuntu / Debian

Family also covers Linux Mint, Pop!_OS, elementary, Kali, Zorin, KDE neon, MX Linux.

sudo apt-get update
sudo apt-get install -y \
    rustc cargo \
    python3 python3-pip python3-venv \
    python3-pyqt6 python3-pyqt6.qtsvg python3-pyqt6.qtqml python3-pyqt6.qtquick \
    qml6-module-qtqml qml6-module-qtqml-workerscript \
    qml6-module-qtquick qml6-module-qtquick-window \
    qml6-module-qtquick-controls qml6-module-qtquick-templates \
    qml6-module-qtquick-layouts qml6-module-qtquick-shapes \
    qml6-module-qtquick-dialogs qml6-module-qt-labs-folderlistmodel \
    python3-gi python3-gi-cairo gir1.2-gtk-4.0 gir1.2-adw-1 \
    python3-cryptography \
    libdbus-1-dev libsystemd-dev \
    libevdev-dev libhidapi-dev \
    ydotool \
    git make build-essential

# Optional: layer-shell support where the package exists
sudo apt-get install -y libgtk4-layer-shell0

# Newer Qt only (frosted cards from Qt 6.5, vector illustrations from 6.8):
# skip the ones your release does not have, Settings draws stand-ins
sudo apt-get install -y qml6-module-qtquick-effects qml6-module-qtquick-vectorimage qt6-svg-plugins

Debian and Ubuntu package every QML module on its own. With one of them missing, juhradial-settings names it in the terminal and the page that needs it shows the command to install it.

Note

On Ubuntu 24.04 the distro Rust (1.75) is too old for the committed lockfile. Bootstrap rustup as shown in The rustup bootstrap before building.

Arch Linux

Family also covers Manjaro, EndeavourOS, Garuda, Artix, CachyOS, ArcoLinux, Archcraft.

sudo pacman -S --noconfirm --needed \
    rust \
    python python-pip \
    python-pyqt6 qt6-svg qt6-declarative \
    python-gobject gtk4 libadwaita \
    gtk4-layer-shell \
    python-cryptography \
    dbus systemd-libs \
    libevdev hidapi \
    ydotool \
    git make base-devel

openSUSE Tumbleweed

sudo zypper install -y \
    rust cargo \
    python3 python3-pip \
    python3-PyQt6 qt6-declarative-imports \
    python3-gobject gtk4 libadwaita-devel \
    python3-cryptography \
    dbus-1-devel systemd-devel \
    libevdev-devel libhidapi-devel \
    ydotool \
    git make

# layer-shell may not be in your enabled repos; safe to skip if unavailable
sudo zypper install -y gtk4-layer-shell || true

Note

On openSUSE the PyQt6 package is python3-PyQt6 (capital Q), unlike the lowercase python3-pyqt6 used on Fedora and Debian. gtk4-layer-shell is not always present in the default repositories; the app still runs without it (layer-shell is only required for niri-style compositors that lack cursor IPC).

NixOS (flake)

The repository ships a flake.nix exposing a package, a NixOS module, and a dev shell.

Try it without installing:

nix run github:JuhLabs/juhradial-mx

Enable it declaratively in your system flake. Add the input:

{
  inputs.juhradial-mx.url = "github:JuhLabs/juhradial-mx";
}

Then import the module and enable the service in your host config:

{
  imports = [ juhradial-mx.nixosModules.default ];

  services.juhradial-mx.enable = true;
}

The module installs the package system-wide, applies the bundled 99-juhradialmx.rules udev rules for non-root device access, and ensures the input group exists. Rebuild with sudo nixos-rebuild switch --flake .#<host>.

Contributors can drop into a ready toolchain (Rust, Python with PyQt6 + PyGObject, GTK4/libadwaita, Qt6) with:

nix develop github:JuhLabs/juhradial-mx

Build from source

If you want to build and run without a full system install:

git clone https://github.com/JuhLabs/juhradial-mx.git
cd juhradial-mx

# Build the Rust daemon
cd daemon && cargo build --release && cd ..

# Run the overlay + daemon
./scripts/juhradial-mx.sh

The daemon binary lands at daemon/target/release/juhradiald. You still need the runtime dependencies for your distro (PyQt6, GTK4/libadwaita, ydotool) and, for non-root device access, the udev rules and input group membership described above. For a full system install identical to the one-line path, run install.sh from the cloned tree.

To run the daemon directly with verbose logging while debugging, see Troubleshooting:

./daemon/target/release/juhradiald --verbose

Updating

Re-run the same one-line command. When /opt/juhradial-mx already exists, the installer switches to upgrade mode: it downloads the newest release tarball (betas included; it carries a prebuilt daemon, which is used when it runs on your machine), reinstalls files and udev rules, and restarts the running service. When no release asset can be downloaded it falls back to fetching master and building from source.

curl -fsSL https://raw.githubusercontent.com/JuhLabs/juhradial-mx/master/install.sh | bash

To install the current master instead of the newest release (for a fix that has been merged but not tagged yet):

curl -fsSL https://raw.githubusercontent.com/JuhLabs/juhradial-mx/master/install.sh | JUHRADIAL_FROM_SOURCE=1 bash

On NixOS, update the flake input and rebuild:

nix flake update juhradial-mx   # in your system flake directory
sudo nixos-rebuild switch --flake .#<host>

For a source checkout, pull and rebuild:

cd juhradial-mx
git pull
cd daemon && cargo build --release && cd ..

After installing

  • Launch from your application menu (JuhRadial MX) or run juhradial-mx. Hold the gesture (thumb) button on your MX Master to open the radial menu.
  • Open the settings app (juhradial-settings) to remap buttons, pick a theme, and configure haptics, scroll, Easy-Switch, and Flow. See Configuration and Features.
  • Check the daemon:
systemctl --user status juhradialmx-daemon
journalctl --user -u juhradialmx-daemon -f

Info

Two cases need a session restart (log out and back in): first time your user is added to the input group, and on GNOME for the cursor-helper Shell extension to load. If the menu appears at the top-left corner on GNOME, this is why.

If the menu does not appear or the mouse is not detected, head to Troubleshooting. For how the daemon, overlay, and settings app fit together, see Architecture. Return to Home for the full page index.