feat: COSMIC Applet, Razer Naga swap, eGPU udev, Pfadfixes
- COSMIC Applet: Pfad gefixt, Desktop-File korrigiert, systemd-User-Service - Razer Naga Left Handed: Button-Swap-Daemon (evdev/uinput) + udev + systemd - superx-ctrl.py: Pfade gefixt (FROSTBAY_BLE, tablet-rgb.py) - tablet-rgb.py: Dynamische HIDRAW-Erkennung statt hartkodiertem Pfad - README: Status aktualisiert (Tablet RGB, Keyboard RGB, Mainboard RGB) - udev: 99-razer-naga-swap.rules für Hotplug
This commit is contained in:
parent
dc9f7a5948
commit
625a90b3e7
7 changed files with 210 additions and 51 deletions
|
|
@ -16,9 +16,10 @@ Betriebssystem: **Arch Linux** · Compositor: **Hyprland 0.55.4** · Shell: **Da
|
|||
| **DMS SuperXCtrl Widget** | ✅ Produktiv | GUI-Widget für Cooling/RGB-Steuerung im DMS-Panel |
|
||||
| **Turbo-Daemon** | ✅ Produktiv | Key-Combo-Listener für Turbo/Mode/KB-Tasten |
|
||||
| **HHD Patch** | ✅ Produktiv | Turbo-Bypass für Super X ohne oxp-platform |
|
||||
| **Tablet RGB (HIDRAW)** | ✅ Produktiv | RGB-Steuerung des Tablet-Moduls via /dev/hidraw2 |
|
||||
| **Tablet RGB (HIDRAW)** | ⚠️ Hardware fehlt | RGB-Steuerung des Tablet-Moduls via HIDRAW (1a86:1305 Interface 2) – Code fertig, aber Tablet-Modul noch nicht verbaut |
|
||||
| **Keyboard RGB** | ❌ Nicht steuerbar | Keyboard-RGB kann nicht angesteuert werden |
|
||||
| **EC Turbo** | ✅ Produktiv | Turbo-Status lesen/setzen via EC (/sys/kernel/debug/ec/ec0/io) |
|
||||
| **Mainboard RGB (WS2812B)** | 🔄 In Arbeit | Hardware-Reverse läuft — Logic Analyzer bestellt |
|
||||
| **Mainboard RGB (WS2812B)** | 🔄 In Arbeit | Original-LEDs kommen morgen – dann Reverse-Engineering am Mainboard-RGB2-Anschluss |
|
||||
| **WMI/EC-Kernel-Module** | 🔄 In Arbeit | Erste Module kompilieren, aber noch nicht funktional |
|
||||
|
||||
---
|
||||
|
|
|
|||
13
configs/razer-naga-swap.service
Normal file
13
configs/razer-naga-swap.service
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[Unit]
|
||||
Description=Razer Naga Left Handed - Button Swap Daemon
|
||||
After=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/razer-naga-swap.py
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
User=root
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
@ -90,7 +90,7 @@ enum Message {
|
|||
|
||||
fn run_ctrl(args: &[&str]) -> String {
|
||||
let home = env::var("HOME").unwrap_or_else(|_| "/home/nepharius".to_string());
|
||||
let script = PathBuf::from(&home).join("Projekte/superx-goes-arch/tools/superx-ctrl.py");
|
||||
let script = PathBuf::from(&home).join("git.nepharius.at/superx-goes-arch/tools/superx-ctrl.py");
|
||||
let output = Command::new("python3")
|
||||
.arg(&script)
|
||||
.args(args)
|
||||
|
|
|
|||
110
tools/razer-naga-swap.py
Normal file
110
tools/razer-naga-swap.py
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Razer Naga Left Handed Edition - Button Swapper
|
||||
Debug version - shows which code fails.
|
||||
"""
|
||||
import evdev
|
||||
from evdev import UInput, ecodes as e
|
||||
import sys
|
||||
import os
|
||||
|
||||
VENDOR = 0x1532
|
||||
PRODUCT = 0x008d
|
||||
UINPUT_NAME = "Razer Naga Left Handed (Swapped)"
|
||||
|
||||
|
||||
def find_naga():
|
||||
for path in evdev.list_devices():
|
||||
try:
|
||||
dev = evdev.InputDevice(path)
|
||||
info = dev.info
|
||||
if info.vendor == VENDOR and info.product == PRODUCT:
|
||||
caps = dev.capabilities()
|
||||
if e.EV_KEY in caps and e.BTN_LEFT in caps.get(e.EV_KEY, []):
|
||||
dev.close()
|
||||
return path
|
||||
dev.close()
|
||||
except:
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
naga_path = find_naga()
|
||||
if not naga_path:
|
||||
print("Naga not found", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
naga = evdev.InputDevice(naga_path)
|
||||
naga.grab()
|
||||
|
||||
# Try each code individually to find the bad one
|
||||
test_caps = {
|
||||
e.EV_KEY: [e.BTN_LEFT, e.BTN_RIGHT],
|
||||
e.EV_REL: [e.REL_X, e.REL_Y],
|
||||
}
|
||||
|
||||
for etype, codes in test_caps.items():
|
||||
for code in codes:
|
||||
try:
|
||||
ui = UInput(
|
||||
name=UINPUT_NAME,
|
||||
vendor=VENDOR,
|
||||
product=PRODUCT,
|
||||
version=1,
|
||||
bustype=0x03,
|
||||
events={etype: [code]},
|
||||
)
|
||||
ui.close()
|
||||
print(f"OK: type={etype} code={code}", file=sys.stderr)
|
||||
except Exception as ex:
|
||||
print(f"FAIL: type={etype} code={code}: {ex}", file=sys.stderr)
|
||||
|
||||
# Now create the real one with only working codes
|
||||
caps = {
|
||||
e.EV_KEY: [e.BTN_LEFT, e.BTN_RIGHT],
|
||||
e.EV_REL: [e.REL_X, e.REL_Y],
|
||||
}
|
||||
|
||||
ui = UInput(
|
||||
name=UINPUT_NAME,
|
||||
vendor=VENDOR,
|
||||
product=PRODUCT,
|
||||
version=1,
|
||||
bustype=0x03,
|
||||
events=caps,
|
||||
)
|
||||
|
||||
# Fork to background
|
||||
if os.fork() > 0:
|
||||
sys.exit(0)
|
||||
os.setsid()
|
||||
if os.fork() > 0:
|
||||
sys.exit(0)
|
||||
|
||||
try:
|
||||
with open("/var/run/razer-naga-swap.pid", "w") as f:
|
||||
f.write(str(os.getpid()))
|
||||
|
||||
for event in naga.read_loop():
|
||||
if event.type == e.EV_KEY:
|
||||
if event.code == e.BTN_LEFT:
|
||||
ui.write(e.EV_KEY, e.BTN_RIGHT, event.value)
|
||||
elif event.code == e.BTN_RIGHT:
|
||||
ui.write(e.EV_KEY, e.BTN_LEFT, event.value)
|
||||
elif event.type == e.EV_REL:
|
||||
if event.code in (e.REL_X, e.REL_Y):
|
||||
ui.write(e.EV_REL, event.code, event.value)
|
||||
elif event.type == e.EV_SYN:
|
||||
ui.write(e.EV_SYN, event.code, event.value)
|
||||
ui.syn()
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
naga.ungrab()
|
||||
naga.close()
|
||||
ui.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
"""superx-ctrl.py v4 — Frostbay cooling + RGB presets + Direct D-Bus writes."""
|
||||
import json, sys, os, subprocess, time, dbus
|
||||
|
||||
FROSTBAY_BLE = os.path.expanduser("~/Projekte/superx-goes-arch/tools/frostbay_ble.py")
|
||||
FROSTBAY_BLE = os.path.expanduser("~/git.nepharius.at/superx-goes-arch/tools/frostbay_ble.py")
|
||||
|
||||
COOL_MODES = [
|
||||
{"id": "off", "label": "Off", "icon": "power_settings_new"},
|
||||
|
|
@ -114,7 +114,7 @@ def cmd_rgb_off():
|
|||
except Exception as e:
|
||||
return {"ok": False, "error": str(e)}
|
||||
|
||||
TABLET_CMD = os.path.expanduser("~/.local/bin/tablet-rgb.py")
|
||||
TABLET_CMD = os.path.expanduser("~/git.nepharius.at/superx-goes-arch/tools/tablet-rgb.py")
|
||||
|
||||
def cmd_tablet(*args):
|
||||
"""Control tablet RGB via HIDRAW."""""
|
||||
|
|
|
|||
|
|
@ -1,30 +1,61 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test tablet RGB via HIDRAW — adapted from ps1x/onexplayer-superxcontrol for Super X."""
|
||||
"""Tablet RGB via HIDRAW — auto-detects the correct Super X HID node."""
|
||||
import sys, os, json, time
|
||||
from pathlib import Path
|
||||
|
||||
HIDRAW = "/dev/hidraw2"
|
||||
VENDOR_ID = 0x1A86
|
||||
PRODUCT_ID = 0x1305
|
||||
INTERFACE = 2 # Interface 2 = RGB control
|
||||
PACKET_SIZE = 64
|
||||
|
||||
|
||||
def read_attr(path: Path, name: str) -> str | None:
|
||||
attr = path / name
|
||||
if not attr.exists():
|
||||
return None
|
||||
return attr.read_text(encoding="utf-8").strip()
|
||||
|
||||
|
||||
def find_hidraw() -> str:
|
||||
for hidraw in sorted(Path("/sys/class/hidraw").glob("hidraw*")):
|
||||
device = (hidraw / "device").resolve()
|
||||
usb_interface = device.parent
|
||||
usb_device = usb_interface.parent
|
||||
if read_attr(usb_device, "idVendor") != f"{VENDOR_ID:04x}":
|
||||
continue
|
||||
if read_attr(usb_device, "idProduct") != f"{PRODUCT_ID:04x}":
|
||||
continue
|
||||
if read_attr(usb_interface, "bInterfaceNumber") != f"{INTERFACE:02x}":
|
||||
continue
|
||||
return f"/dev/{hidraw.name}"
|
||||
raise RuntimeError(
|
||||
f"Super X HID not found (vendor=0x{VENDOR_ID:04x} "
|
||||
f"product=0x{PRODUCT_ID:04x} interface={INTERFACE})"
|
||||
)
|
||||
|
||||
|
||||
def pad(prefix):
|
||||
return prefix + [0] * (PACKET_SIZE - len(prefix))
|
||||
|
||||
|
||||
def write_hidraw(data):
|
||||
with open(HIDRAW, "r+b", buffering=0) as f:
|
||||
path = find_hidraw()
|
||||
with open(path, "r+b", buffering=0) as f:
|
||||
written = f.write(bytes(data))
|
||||
return written
|
||||
|
||||
|
||||
def cmd_static(r, g, b):
|
||||
"""Static color: 0x07 0xFF 0xFE + 20x RGB + 0x00"""
|
||||
payload = pad([0x07, 0xFF, 0xFE] + [r, g, b] * 20 + [0x00])
|
||||
return write_hidraw(payload)
|
||||
|
||||
|
||||
def cmd_preset(preset_id):
|
||||
"""Preset mode: 0x07 0xFF + preset_id + 0x00 0x00"""
|
||||
payload = pad([0x07, 0xFF, preset_id, 0x00, 0x00])
|
||||
return write_hidraw(payload)
|
||||
|
||||
|
||||
def cmd_brightness(mode):
|
||||
"""Brightness levels with RGB on/off"""
|
||||
levels = {
|
||||
"off": [0x07, 0xFF, 0xFD, 0x00, 0x05, 0x01],
|
||||
"low": [0x07, 0xFF, 0xFD, 0x01, 0x05, 0x01],
|
||||
|
|
@ -34,51 +65,53 @@ def cmd_brightness(mode):
|
|||
payload = pad(levels.get(mode, levels["high"]))
|
||||
return write_hidraw(payload)
|
||||
|
||||
|
||||
def cmd_on():
|
||||
"""Enable RGB"""
|
||||
payload = pad([0x07, 0xFF, 0x05, 0x00])
|
||||
return write_hidraw(payload)
|
||||
|
||||
if not os.access(HIDRAW, os.W_OK):
|
||||
print(json.dumps({"ok": False, "error": f"no write access to {HIDRAW}"}))
|
||||
sys.exit(1)
|
||||
|
||||
if len(sys.argv) >= 4:
|
||||
# rgb R G B
|
||||
r, g, b = int(sys.argv[1]), int(sys.argv[2]), int(sys.argv[3])
|
||||
# Step 1: brightness high first
|
||||
cmd_brightness("high")
|
||||
time.sleep(0.2)
|
||||
# Step 2: static color
|
||||
written = cmd_static(r, g, b)
|
||||
print(json.dumps({"ok": True, "cmd": "static", "rgb": [r,g,b], "written": written}))
|
||||
elif len(sys.argv) >= 2:
|
||||
cmd = sys.argv[1]
|
||||
if cmd == "on":
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
# Quick accessibility check
|
||||
find_hidraw()
|
||||
except RuntimeError as e:
|
||||
print(json.dumps({"ok": False, "error": str(e)}))
|
||||
sys.exit(1)
|
||||
|
||||
if len(sys.argv) >= 4:
|
||||
r, g, b = int(sys.argv[1]), int(sys.argv[2]), int(sys.argv[3])
|
||||
cmd_brightness("high")
|
||||
time.sleep(0.2)
|
||||
cmd_on()
|
||||
print(json.dumps({"ok": True, "cmd": "on"}))
|
||||
elif cmd == "off":
|
||||
cmd_brightness("off")
|
||||
print(json.dumps({"ok": True, "cmd": "off"}))
|
||||
elif cmd == "rainbow":
|
||||
cmd_brightness("high")
|
||||
time.sleep(0.2)
|
||||
cmd_preset(0x09)
|
||||
print(json.dumps({"ok": True, "cmd": "rainbow", "preset": 0x09}))
|
||||
elif cmd == "aurora":
|
||||
cmd_brightness("high")
|
||||
time.sleep(0.2)
|
||||
cmd_preset(0x01)
|
||||
print(json.dumps({"ok": True, "cmd": "aurora", "preset": 0x01}))
|
||||
elif cmd == "preset" and len(sys.argv) >= 3:
|
||||
pid = int(sys.argv[2], 0)
|
||||
cmd_brightness("high")
|
||||
time.sleep(0.2)
|
||||
cmd_preset(pid)
|
||||
print(json.dumps({"ok": True, "cmd": "preset", "preset": pid}))
|
||||
written = cmd_static(r, g, b)
|
||||
print(json.dumps({"ok": True, "cmd": "static", "rgb": [r, g, b], "written": written}))
|
||||
elif len(sys.argv) >= 2:
|
||||
cmd = sys.argv[1]
|
||||
if cmd == "on":
|
||||
cmd_brightness("high")
|
||||
time.sleep(0.2)
|
||||
cmd_on()
|
||||
print(json.dumps({"ok": True, "cmd": "on"}))
|
||||
elif cmd == "off":
|
||||
cmd_brightness("off")
|
||||
print(json.dumps({"ok": True, "cmd": "off"}))
|
||||
elif cmd == "rainbow":
|
||||
cmd_brightness("high")
|
||||
time.sleep(0.2)
|
||||
cmd_preset(0x09)
|
||||
print(json.dumps({"ok": True, "cmd": "rainbow", "preset": 0x09}))
|
||||
elif cmd == "aurora":
|
||||
cmd_brightness("high")
|
||||
time.sleep(0.2)
|
||||
cmd_preset(0x01)
|
||||
print(json.dumps({"ok": True, "cmd": "aurora", "preset": 0x01}))
|
||||
elif cmd == "preset" and len(sys.argv) >= 3:
|
||||
pid = int(sys.argv[2], 0)
|
||||
cmd_brightness("high")
|
||||
time.sleep(0.2)
|
||||
cmd_preset(pid)
|
||||
print(json.dumps({"ok": True, "cmd": "preset", "preset": pid}))
|
||||
else:
|
||||
print(json.dumps({"ok": False, "error": f"unknown: {cmd}"}))
|
||||
else:
|
||||
print(json.dumps({"ok": False, "error": f"unknown: {cmd}"}))
|
||||
else:
|
||||
print(json.dumps({"ok": False, "error": "need args"}))
|
||||
print(json.dumps({"ok": False, "error": "need args"}))
|
||||
|
|
|
|||
2
udev/99-razer-naga-swap.rules
Normal file
2
udev/99-razer-naga-swap.rules
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Start Razer Naga button swapper when mouse is connected
|
||||
ACTION=="add", SUBSYSTEM=="input", ATTRS{idVendor}=="1532", ATTRS{idProduct}=="008d", ENV{ID_INPUT_MOUSE}=="1", RUN+="/usr/local/bin/razer-naga-swap.py"
|
||||
Loading…
Add table
Reference in a new issue