Merge branch 'master' into feat/pro-x-2-superstrike
This commit is contained in:
commit
9b939facd4
|
|
@ -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'
|
||||
```
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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]]]:
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
6
setup.py
6
setup.py
|
|
@ -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",
|
||||
],
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
Loading…
Reference in New Issue