Merge branch 'master' into feat/pro-x-2-superstrike

This commit is contained in:
Caio Quirino da Silva 2026-03-11 17:13:11 +01:00 committed by GitHub
commit 9b939facd4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 2285 additions and 1641 deletions

175
RHEL.md Normal file
View File

@ -0,0 +1,175 @@
# Solaar installation guide for RHEL, Rocky, AlmaLinux, and CentOS Stream
This guide covers manual installation and an automated install example for
RHEL-family systems using `dnf`.
## Supported distributions
- Red Hat Enterprise Linux (RHEL)
- Rocky Linux
- AlmaLinux
- Oracle Linux
- CentOS Stream
The commands assume a minimal CLI system with `sudo` access.
## 1) Install dependencies
```bash
sudo dnf makecache --refresh
sudo dnf install -y \
git \
gtk3 \
python3 \
python3-devel \
python3-dbus \
python3-gobject \
python3-pip \
python3-psutil \
python3-pyudev \
python3-setuptools \
python3-xlib \
python3-yaml
```
Optional troubleshooting helpers:
```bash
sudo dnf install -y \
evemu \
libinput \
usbutils
```
## 2) Clone Solaar
```bash
git clone https://github.com/pwr-Solaar/Solaar.git
cd Solaar
```
## 3) Install Solaar
Install for the current user:
```bash
python3 -m pip install --user .
```
Or install system-wide:
```bash
sudo python3 -m pip install .
```
## 4) Install udev rules
Install the recommended `uinput` rule:
```bash
sudo make install_udev_uinput
```
Verify rule installation:
```bash
ls -l /etc/udev/rules.d/42-logitech-unify-permissions.rules
```
Rollback udev rule installation:
```bash
sudo make uninstall_udev
```
## 5) Run Solaar
```bash
solaar
```
or:
```bash
python3 -m solaar
```
## 6) Automated install options
Use the guided installer in this repository:
```bash
./tools/install-rhel.sh
```
Minimal non-interactive example script:
```bash
cat > install-rhel-solaar.sh <<'SCRIPT'
#!/usr/bin/env bash
set -euo pipefail
if [[ "${EUID}" -eq 0 ]]; then
echo "Run as a regular user with sudo access, not as root."
exit 1
fi
sudo dnf makecache --refresh
sudo dnf install -y \
git \
gtk3 \
python3 \
python3-devel \
python3-dbus \
python3-gobject \
python3-pip \
python3-psutil \
python3-pyudev \
python3-setuptools \
python3-xlib \
python3-yaml
if [[ ! -d Solaar/.git ]]; then
git clone https://github.com/pwr-Solaar/Solaar.git
fi
cd Solaar
python3 -m pip install --user .
sudo make install_udev_uinput
~/.local/bin/solaar --version
SCRIPT
chmod +x install-rhel-solaar.sh
./install-rhel-solaar.sh
```
## 7) Verification
```bash
command -v solaar
solaar --version
python3 -m pip show solaar
```
If installed with `--user`, ensure `~/.local/bin` is on your `PATH`:
```bash
echo "$PATH" | tr ':' '\n' | grep -Fx "$HOME/.local/bin" >/dev/null || \
echo 'Add ~/.local/bin to PATH'
```
## 8) Troubleshooting
Receiver not detected:
```bash
lsusb | grep -Ei 'logitech|046d'
sudo udevadm trigger
```
Check access to hidraw devices:
```bash
ls -l /dev/hidraw*
getfacl /dev/hidraw* 2>/dev/null | sed -n '1,80p'
```

View File

@ -49,7 +49,7 @@ Some of the languages Solaar has been translated to are listed below. A full lis
- Danish: John Erling Blad
- Dutch: Heimen Stoffels
- Français: [Papoteur][papoteur], [David Geiger][david-geiger], [Damien Lallement][damsweb]
- Finnish: Tomi Leppänen
- Finnish: Tomi Leppänen, [Niko Savola][nikosavola]
- German: Daniel Frost
- Greek: Vangelis Skarmoutsos
- Indonesia: [Ferdina Kusumah][feku]
@ -83,3 +83,4 @@ Some of the languages Solaar has been translated to are listed below. A full lis
[jeblad]: https://github.com/jeblad
[feku]: https://github.com/FerdinaKusumah
[renatoka]: https://github.com/renatoka
[nikosavola]: https://github.com/nikosavola

View File

@ -12,8 +12,6 @@ First install pip, and then run
This will not install the Solaar udev rule, which you will need to install manually by copying
`~/.local/lib/udev/rules.d/42-logitech-unify-permissions.rules`
to `/etc/udev/rules.d` as root.
If you want Solaar rules to simulate input you will have to instead install Solaar's uinput udev rule
from the GitHub repository.
## Installing in macOS
@ -52,8 +50,6 @@ First, install the needed system packages by `make install_apt`
or `make install_dnf` or `make install_brew`.
These might not install all needed packages in older versions of your distribution.
Next, install the Solaar rule via `make install_udev`.
If you are using Wayland instead of X11 you may want to instead `make install_udev_uinput`
so that Solaar rules can simulate input in Wayland.
Finally, install Solaar via `make install_pip` or `make install_pipx`.
Parts of the installation process require sudo privileges so you may be asked for your password.
@ -129,8 +125,6 @@ For more information see [the rules page](https://pwr-solaar.github.io/Solaar/ru
You can install Solaar's udev rule manually by copying the file
`rules.d/42-logitech-unify-permissions.rules`
as root from the Solaar repository to `/etc/udev/rules.d`.
In Wayland you may want to instead copy
`rules.d-uinput/42-logitech-unify-permissions.rules`.
Let udev reload its rules by running `sudo udevadm control --reload-rules`.
# Solaar in other languages

View File

@ -45,7 +45,7 @@ layout: page
in some system tray implementations. Changing to a different theme may help.
The `--battery-icons=symbolic` option can be used to force symbolic icons.
- Solaar will try to use uinput to simulate input from rules under Wayland or if Xtest is not available
- Solaar uses uinput to simulate input
but this needs write permission on /dev/uinput.
For more information see [the rules page](https://pwr-solaar.github.io/Solaar/rules).

View File

@ -15,9 +15,9 @@ although on GNOME desktop under Wayland, you can use those with the Solaar Gnome
You can install it from `https://extensions.gnome.org/extension/6162/solaar-extension`.
Under Wayland using keyboard groups may result in incorrect symbols being input for simulated input.
Under Wayland simulating inputs when modifier keys are pressed may result in incorrect symbols being sent.
Simulated input uses Xtest if available under X11 or uinput if the user has write access to /dev/uinput.
The easiest way to maintain write access to /dev/uinput is to use Solaar's alternative udev rule by downloading
`https://raw.githubusercontent.com/pwr-Solaar/Solaar/master/rules.d-uinput/42-logitech-unify-permissions.rules`
Simulated input uses uinput to simulate input so the user has to have write access to /dev/uinput.
The easiest way to maintain write access to /dev/uinput is to use Solaar's udev rule by downloading
`https://raw.githubusercontent.com/pwr-Solaar/Solaar/master/rules.d/42-logitech-unify-permissions.rules`
and copying it as root into the `/etc/udev/rules.d` directory.
You may have to reboot your system for the write permission to be set up.
Another way to get write access to /dev/uinput is to run `sudo setfacl -m u:${USER}:rw /dev/uinput`

View File

@ -38,6 +38,8 @@ from typing import Callable
from hidapi.common import DeviceInfo
LOGITECH_VENDOR_ID = 0x046D
if typing.TYPE_CHECKING:
import gi
@ -251,6 +253,12 @@ def _match(
logger.info(f"Skipping unlikely device {device['path']} ({bus_id}/{vid:04X}/{pid:04X})")
return None
# Skip Logitech webcams to prevent them from locking up during hidpp checks
# (product IDs range for webcams from docs/usb.ids.txt)
if vid == LOGITECH_VENDOR_ID and 0x0800 <= pid <= 0x09FF:
logger.info(f"Skipping Logitech webcam {device['path']} ({bus_id}/{vid:04X}/{pid:04X})")
return None
# Check for hidpp support
device["hidpp_short"] = False
device["hidpp_long"] = False

View File

@ -656,8 +656,8 @@ def ping(handle, devnumber, long_message: bool = False):
return 1.0
if error in [Hidpp10ErrorCode.RESOURCE_ERROR, Hidpp10ErrorCode.CONNECTION_REQUEST_FAILED]:
return # device unreachable
if error == Hidpp10ErrorCode.UNKNOWN_DEVICE: # no paired device with that number
logger.error("(%s) device %d error on ping request: unknown device", handle, devnumber)
if error == Hidpp10ErrorCode.UNKNOWN_DEVICE: # no device with that number currently accessible
logger.info("(%s) device %d error on ping request: unknown device", handle, devnumber)
raise exceptions.NoSuchDevice(number=devnumber, request=request_id)
if notifications_hook:

View File

@ -425,6 +425,7 @@ _D("G903 Hero Gaming Mouse", codename="G903 Hero", usbid=0xC091)
_D(None, kind=DEVICE_KIND.mouse, usbid=0xC092, interface=1) # two mice share this ID
_D("M500S Mouse", codename="M500S", usbid=0xC093, interface=1)
# _D('G600 Gaming Mouse', codename='G600 Gaming', usbid=0xc24a, interface=1) # not an HID++ device
_D("G500 Gaming Mouse", codename="G500 Gaming", usbid=0xC068, interface=1, protocol=1.0)
_D("G500s Gaming Mouse", codename="G500s Gaming", usbid=0xC24E, interface=1, protocol=1.0)
_D("G502 Proteus Spectrum Optical Mouse", codename="G502 Proteus Spectrum", usbid=0xC332, interface=1)
_D("Logitech PRO Gaming Keyboard", codename="PRO Gaming Keyboard", usbid=0xC339, interface=1)

View File

@ -183,13 +183,16 @@ class Device:
self.descriptor = (
descriptors.get_btid(self.product_id) if self.bluetooth else descriptors.get_usbid(self.product_id)
)
if self.number is None: # for direct-connected devices get 'number' from descriptor protocol else use 0xFF
if self.descriptor and self.descriptor.protocol and self.descriptor.protocol < 2.0:
number = 0x00
# for direct-connected devices get 'number' from descriptor protocol else use 0xFF
self.number = 0x00 if self.descriptor and self.descriptor.protocol and self.descriptor.protocol < 2.0 else 0xFF
try: # determine whether a direct-connected device is online
self.ping()
except exceptions.NoSuchDevice as e:
if self.number == 0xFF: # guessed wrong number?
self.number = 0x00
self.ping()
else:
number = 0xFF
self.number = number
self.ping() # determine whether a direct-connected device is online
raise e
if self.descriptor:
self._name = self.descriptor.name

View File

@ -73,9 +73,10 @@ logger = logging.getLogger(__name__)
# KeyPress action gets the current keyboard group using XkbGetState from libX11.so using ctypes definitions
# under Wayland the keyboard group is None resulting in using the first keyboard group
# KeyPress action translates keysyms to keycodes using the GDK keymap
# KeyPress, MouseScroll, and MouseClick actions use XTest (under X11) or uinput.
# KeyPress, MouseScroll, and MouseClick actions use uinput.
# For uinput to work the user must have write access for /dev/uinput.
# To get this access run sudo setfacl -m u:${user}:rw /dev/uinput
# The Solaar udev rule should set this up
# Otherwise run sudo setfacl -m u:${user}:rw /dev/uinput
#
# Rule GUI keyname determination uses a local file generated
# from http://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h
@ -85,8 +86,7 @@ logger = logging.getLogger(__name__)
# Setting up is complex because there are several systems that each provide partial facilities:
# GDK - always available (when running with a window system) but only provides access to keymap
# X11 - provides access to active process and process with window under mouse and current modifier keys
# Xtest extension to X11 - provides input simulation, partly works under Wayland
# Wayland - provides input simulation
# uinput and evdev - provides input simulation
XK_KEYS: Dict[str, int] = keysymdef.key_symbols
@ -111,14 +111,11 @@ if wayland:
)
try:
import Xlib
_x11 = None # X11 might be available
except Exception:
_x11 = False # X11 is not available
# Globals
xtest_available = True # Xtest might be available
xdisplay = None
@ -170,7 +167,7 @@ class XkbStateRec(ctypes.Structure):
def x11_setup():
global _x11, xdisplay, modifier_keycodes, NET_ACTIVE_WINDOW, NET_WM_PID, WM_CLASS, xtest_available
global _x11, xdisplay, modifier_keycodes, NET_ACTIVE_WINDOW, NET_WM_PID, WM_CLASS
if _x11 is not None:
return _x11
try:
@ -187,7 +184,6 @@ def x11_setup():
except Exception:
logger.warning("X11 not available - some rule capabilities inoperable", exc_info=sys.exc_info())
_x11 = False
xtest_available = False
return _x11
@ -272,10 +268,6 @@ def setup_uinput():
logger.warning("cannot create uinput device: %s", e)
if wayland: # Wayland can't use xtest so may as well set up uinput now
setup_uinput()
def kbdgroup():
if xkb_setup():
state = XkbStateRec()
@ -324,31 +316,6 @@ def xy_direction(_x, _y):
return "noop"
def simulate_xtest(code, event):
global xtest_available
if x11_setup() and xtest_available:
try:
event = (
Xlib.X.KeyPress
if event == _KEY_PRESS
else Xlib.X.KeyRelease
if event == _KEY_RELEASE
else Xlib.X.ButtonPress
if event == _BUTTON_PRESS
else Xlib.X.ButtonRelease
if event == _BUTTON_RELEASE
else None
)
Xlib.ext.xtest.fake_input(xdisplay, event, code)
xdisplay.sync()
if logger.isEnabledFor(logging.DEBUG):
logger.debug("xtest simulated input %s %s %s", xdisplay, event, code)
return True
except Exception as e:
xtest_available = False
logger.warning("xtest fake input failed: %s", e)
def simulate_uinput(what, code, arg):
global udevice
if setup_uinput():
@ -364,30 +331,11 @@ def simulate_uinput(what, code, arg):
def simulate_key(code, event): # X11 keycode but Solaar event code
if not wayland and simulate_xtest(code, event):
return True
if evdev and simulate_uinput(evdev.ecodes.EV_KEY, code - 8, event):
return True
logger.warning("no way to simulate key input")
def click_xtest(button, count):
if isinstance(count, int):
for _ in range(count):
if not simulate_xtest(button[0], _BUTTON_PRESS):
return False
if not simulate_xtest(button[0], _BUTTON_RELEASE):
return False
else:
if count != RELEASE:
if not simulate_xtest(button[0], _BUTTON_PRESS):
return False
if count != DEPRESS:
if not simulate_xtest(button[0], _BUTTON_RELEASE):
return False
return True
def click_uinput(button, count):
if isinstance(count, int):
for _ in range(count):
@ -406,8 +354,6 @@ def click_uinput(button, count):
def click(button, count):
if not wayland and click_xtest(button, count):
return True
if click_uinput(button, count):
return True
logger.warning("no way to simulate mouse click")
@ -415,14 +361,6 @@ def click(button, count):
def simulate_scroll(dx, dy):
if not wayland and xtest_available:
success = True
if dx:
success = click_xtest(buttons["scroll_right" if dx > 0 else "scroll_left"], count=abs(dx))
if dy and success:
success = click_xtest(buttons["scroll_up" if dy > 0 else "scroll_down"], count=abs(dy))
if success:
return True
if setup_uinput():
success = True
if dx:

View File

@ -1972,7 +1972,7 @@ class SuperstrikeTuning_(settings.Setting):
class HapticLevel(settings.Setting):
name = "haptic-level"
label = _("Haptic Feeback Level")
label = _("Haptic Feedback Level")
description = _("Change power of haptic feedback. (Zero to turn off.)")
feature = _F.HAPTIC
choices_universe = common.NamedInts(Off=0, Low=25, Medium=50, High=75, Maximum=100)

View File

@ -56,6 +56,7 @@ class AboutModel:
"El Jinete Sin Cabeza (Español)",
"Ferdina Kusumah (Indonesia)",
"John Erling Blad (Norwegian Bokmål, Norwegian Nynorsk)",
"Oleksandr Afanasiev (Ukrainian)",
]
def get_credit_sections(self) -> List[Tuple[str, List[str]]]:

3448
po/fi.po

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
msgid ""
msgstr "Project-Id-Version: solaar 1.1.19\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-08 22:39-0300\n"
"POT-Creation-Date: 2026-03-05 14:00+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -1151,7 +1151,7 @@ msgid "Force Sensing Button"
msgstr ""
#: lib/logitech_receiver/settings_templates.py:1814
msgid "Haptic Feeback Level"
msgid "Haptic Feedback Level"
msgstr ""
#: lib/logitech_receiver/settings_templates.py:1815
@ -1175,19 +1175,19 @@ msgid "Manages Logitech receivers,\n"
"keyboards, mice, and tablets."
msgstr ""
#: lib/solaar/ui/about/model.py:63
#: lib/solaar/ui/about/model.py:64
msgid "Additional Programming"
msgstr ""
#: lib/solaar/ui/about/model.py:64
#: lib/solaar/ui/about/model.py:65
msgid "GUI design"
msgstr ""
#: lib/solaar/ui/about/model.py:66
#: lib/solaar/ui/about/model.py:67
msgid "Testing"
msgstr ""
#: lib/solaar/ui/about/model.py:74
#: lib/solaar/ui/about/model.py:75
msgid "Logitech documentation"
msgstr ""

View File

@ -3,6 +3,7 @@
# Allows non-root users to have raw access to Logitech devices.
# Allowing users to write to the device is potentially dangerous
# because they could perform firmware updates.
KERNEL=="uinput", SUBSYSTEM=="misc", TAG+="uaccess", OPTIONS+="static_node=uinput"
ACTION == "remove", GOTO="solaar_end"
SUBSYSTEM != "hidraw", GOTO="solaar_end"

View File

@ -89,5 +89,9 @@ setup(
packages=find_packages(where="lib"),
data_files=list(_data_files()),
include_package_data=True,
scripts=glob("bin/*"),
entry_points={
"console_scripts": [
"solaar = solaar.gtk:main",
],
},
)

164
tools/install-rhel.sh Executable file
View File

@ -0,0 +1,164 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_NAME="$(basename "$0")"
LOG_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/solaar"
LOG_FILE="$LOG_DIR/rhel-install-$(date +%Y%m%d-%H%M%S).log"
mkdir -p "$LOG_DIR"
exec > >(tee -a "$LOG_FILE") 2>&1
say() {
printf '\n[%s] %s\n' "$SCRIPT_NAME" "$*"
}
warn() {
printf '\n[%s] WARNING: %s\n' "$SCRIPT_NAME" "$*"
}
fail() {
printf '\n[%s] ERROR: %s\n' "$SCRIPT_NAME" "$*"
exit 1
}
ask_yes_no() {
local prompt="$1"
local default="${2:-y}"
local answer
while true; do
if [[ "$default" == "y" ]]; then
read -r -p "$prompt [Y/n]: " answer || true
answer="${answer:-y}"
else
read -r -p "$prompt [y/N]: " answer || true
answer="${answer:-n}"
fi
case "${answer,,}" in
y|yes) return 0 ;;
n|no) return 1 ;;
*) echo "Please answer y or n." ;;
esac
done
}
ask_value() {
local prompt="$1"
local default="$2"
local answer
read -r -p "$prompt [$default]: " answer || true
printf '%s\n' "${answer:-$default}"
}
run_cmd() {
say "Running: $*"
"$@"
}
run_sudo() {
say "Running with sudo: $*"
sudo "$@"
}
if [[ "${EUID}" -eq 0 ]]; then
fail "Do not run as root. Run as your normal user; this script uses sudo when needed."
fi
say "Log file: $LOG_FILE"
say "This installer follows RHEL.md for RHEL 10-like systems."
if ask_yes_no "Update dnf metadata first?" y; then
run_sudo dnf makecache
fi
if ask_yes_no "Check for Logitech USB receiver with lsusb now?" y; then
if lsusb | grep -i logitech; then
say "Logitech device detected."
else
warn "No Logitech USB receiver detected via lsusb right now. You can continue anyway."
ask_yes_no "Continue without receiver detection?" y || fail "Aborted by user."
fi
fi
BASE_PACKAGES=(python3 python3-pip git libinput evemu)
DEV_PACKAGES=(python3-devel gcc pkgconf-pkg-config gtk3 python3-gobject)
say "Base packages: ${BASE_PACKAGES[*]}"
say "Build/runtime packages: ${DEV_PACKAGES[*]}"
if ask_yes_no "Install required packages with dnf?" y; then
run_sudo dnf install -y "${BASE_PACKAGES[@]}" "${DEV_PACKAGES[@]}"
fi
REPO_PARENT_DEFAULT="$HOME/dev-repos"
REPO_PARENT="$(ask_value "Repository parent directory" "$REPO_PARENT_DEFAULT")"
REPO_URL_DEFAULT="https://github.com/pwr-Solaar/Solaar.git"
REPO_URL="$(ask_value "Git URL for Solaar" "$REPO_URL_DEFAULT")"
REPO_DIR_DEFAULT="$REPO_PARENT/Solaar"
REPO_DIR="$(ask_value "Local checkout directory" "$REPO_DIR_DEFAULT")"
run_cmd mkdir -p "$REPO_PARENT"
if [[ -d "$REPO_DIR/.git" ]]; then
say "Existing git checkout found at $REPO_DIR"
if ask_yes_no "Pull latest changes in this repository?" y; then
run_cmd git -C "$REPO_DIR" pull --ff-only
fi
else
run_cmd git clone "$REPO_URL" "$REPO_DIR"
fi
say "Installing Solaar into user site-packages"
if ask_yes_no "Use upgrade mode for pip install?" n; then
run_cmd python3 -m pip install --user --upgrade "$REPO_DIR"
else
run_cmd python3 -m pip install --user "$REPO_DIR"
fi
SOLAAR_BIN="$HOME/.local/bin/solaar"
if [[ ! -x "$SOLAAR_BIN" ]]; then
fail "Expected executable not found: $SOLAAR_BIN"
fi
say "Installed binary: $SOLAAR_BIN"
run_cmd "$SOLAAR_BIN" --help >/dev/null
if ask_yes_no "Add alias 'solaar=$SOLAAR_BIN' to ~/.bashrc if missing?" y; then
if grep -Fqx "alias solaar=\"$SOLAAR_BIN\"" "$HOME/.bashrc" 2>/dev/null; then
say "Alias already exists in ~/.bashrc"
else
printf '\n# Solaar user-local install\nalias solaar="%s"\n' "$SOLAAR_BIN" >> "$HOME/.bashrc"
say "Alias appended to ~/.bashrc"
fi
fi
if ask_yes_no "Run 'solaar show' now for validation?" y; then
run_cmd "$SOLAAR_BIN" show || warn "'solaar show' returned a non-zero status."
fi
if ask_yes_no "Run 'solaar config <device name>' now?" n; then
DEVICE_NAME_DEFAULT="M720 Triathlon Multi-Device Mouse"
DEVICE_NAME="$(ask_value "Device name" "$DEVICE_NAME_DEFAULT")"
run_cmd "$SOLAAR_BIN" config "$DEVICE_NAME" || warn "'solaar config' returned a non-zero status."
fi
if ask_yes_no "Run libinput debug-events for a specific /dev/input/eventX device?" n; then
EVENT_NODE="$(ask_value "Input event node" "/dev/input/eventX")"
warn "This is a live monitor and may run until interrupted (Ctrl+C)."
run_sudo libinput debug-events --device "$EVENT_NODE"
fi
if ask_yes_no "Run keyd monitor (/usr/local/bin/keyd) if present?" n; then
if [[ -x /usr/local/bin/keyd ]]; then
warn "This is a live monitor and may run until interrupted (Ctrl+C)."
run_sudo /usr/local/bin/keyd monitor
else
warn "/usr/local/bin/keyd not found; skipping."
fi
fi
say "Install workflow completed."
say "To use alias in current shell: source ~/.bashrc"
say "Evidence log saved at: $LOG_FILE"