superx-goes-arch/tools/superx-ctrl.py
Kay Türtscher 625a90b3e7 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
2026-06-29 21:22:18 +02:00

170 lines
No EOL
5.8 KiB
Python

#!/usr/bin/env python3
"""superx-ctrl.py v4 — Frostbay cooling + RGB presets + Direct D-Bus writes."""
import json, sys, os, subprocess, time, dbus
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"},
{"id": "silent", "label": "Silent", "icon": "ac_unit"},
{"id": "gentle", "label": "Gentle", "icon": "air"},
{"id": "strong", "label": "Strong", "icon": "speed"},
{"id": "extreme", "label": "Extreme", "icon": "rocket_launch"},
{"id": "smart", "label": "Smart", "icon": "auto_mode"},
]
RGB_PRESETS = [
{"id": 1, "label": "Mode 1"},
{"id": 2, "label": "Mode 2"},
{"id": 3, "label": "Mode 3"},
{"id": 4, "label": "Mode 4"},
{"id": 5, "label": "Mode 5"},
{"id": 6, "label": "Mode 6"},
{"id": 7, "label": "Mode 7"},
{"id": 8, "label": "Mode 8"},
{"id": 9, "label": "Aurora"},
{"id": 10, "label": "Mode 10"},
]
# Direct BLE access
BUS = dbus.SystemBus()
BASE = "/org/bluez/hci0/dev_C8_17_17_F5_C8_62"
CHAR = f"{BASE}/service003f/char0040"
def ble_read():
c = dbus.Interface(BUS.get_object("org.bluez", CHAR), "org.bluez.GattCharacteristic1")
return bytes(c.ReadValue({}))
def ble_write(data):
c = dbus.Interface(BUS.get_object("org.bluez", CHAR), "org.bluez.GattCharacteristic1")
c.WriteValue(dbus.Array(list(data), signature="y"), {})
def make_buff():
"""Build a fresh 64-byte buffer based on current state."""
cur = ble_read()
b = bytearray(64)
b[:] = cur[:64]
b[0] = 0xC1
b[1] = 0x02 # write flag
b[3] = 0x01 # follow mode
b[4] = 0xFF # custom mode (keep fans/pump in current state)
return b
def run_frostbay(*args):
try:
r = subprocess.run([sys.executable, FROSTBAY_BLE] + list(args),
capture_output=True, text=True, timeout=10)
return {"ok": True, "stdout": r.stdout}
except Exception as e:
return {"ok": False, "error": str(e)}
def cmd_status():
r = run_frostbay("raw")
return {"ok": r["ok"], "modes": COOL_MODES, "rgb_presets": RGB_PRESETS}
def cmd_mode(mid):
r = run_frostbay("mode", mid)
return {"ok": r["ok"], "mode": mid}
def cmd_custom(fan, pump):
r = run_frostbay("mode", "custom", "--fan", str(fan), "--pump", str(pump))
return {"ok": r["ok"], "fan": fan, "pump": pump}
def cmd_rgb_preset(num):
"""Two-step direct BLE write: brightness first, then preset."""
try:
# Step 1: Brightness/switch AE
b = make_buff()
b[16] = 0xFD
b[17] = 0x07 # all strips
b[18] = 0x05 # speed
b[19] = 0x04 # brightness max
ble_write(bytes(b))
time.sleep(0.3)
# Step 2: Preset number
b2 = make_buff()
b2[16] = num & 0xFF
ble_write(bytes(b2))
time.sleep(0.3)
return {"ok": True, "preset": num}
except Exception as e:
return {"ok": False, "error": str(e)}
def cmd_rgb_brightness(switch, speed, brightness):
try:
b = make_buff()
b[16] = 0xFD
b[17] = switch & 0xFF
b[18] = speed & 0xFF
b[19] = brightness & 0xFF
ble_write(bytes(b))
return {"ok": True}
except Exception as e:
return {"ok": False, "error": str(e)}
def cmd_rgb_off():
try:
b = make_buff()
b[16] = 0xFD
b[17] = 0x00 # switch=0 = all off
ble_write(bytes(b))
return {"ok": True}
except Exception as e:
return {"ok": False, "error": str(e)}
TABLET_CMD = os.path.expanduser("~/git.nepharius.at/superx-goes-arch/tools/tablet-rgb.py")
def cmd_tablet(*args):
"""Control tablet RGB via HIDRAW."""""
try:
r = subprocess.run([sys.executable, TABLET_CMD] + list(args),
capture_output=True, text=True, timeout=10)
return json.loads(r.stdout)
except Exception as e:
return {"ok": False, "error": str(e)}
def cmd_ec_status():
"""Read EC turbo status via sudo."""
try:
r = subprocess.run(["sudo", "/home/nepharius/.local/bin/ec-read.py"],
capture_output=True, text=True, timeout=5)
if r.returncode == 0:
return json.loads(r.stdout)
return {"ok": False, "error": r.stderr}
except Exception as e:
return {"ok": False, "error": str(e)}
def cmd_ec_turbo(on=True):
"""Set turbo via EC (writes to offset 0x30)."""
try:
val = "06" if on else "00"
r = subprocess.run(
["sh", "-c", f"""printf '\\x{val}' | sudo dd of=/sys/kernel/debug/ec/ec0/io bs=1 seek=$((0x30)) count=1 conv=notrunc 2>/dev/null"""],
capture_output=True, text=True, timeout=5
)
return {"ok": True, "turbo": on}
except Exception as e:
return {"ok": False, "error": str(e)}
if __name__ == "__main__":
if len(sys.argv) < 2:
print(json.dumps({"ok": False, "error": "Need command"}))
sys.exit(1)
cmd = sys.argv[1]
if cmd == "status": result = cmd_status()
elif cmd == "mode" and len(sys.argv) >= 3: result = cmd_mode(sys.argv[2])
elif cmd == "custom" and len(sys.argv) >= 4: result = cmd_custom(int(sys.argv[2]), int(sys.argv[3]))
elif cmd == "rgb-preset" and len(sys.argv) >= 3: result = cmd_rgb_preset(int(sys.argv[2]))
elif cmd == "rgb-brightness" and len(sys.argv) >= 5: result = cmd_rgb_brightness(int(sys.argv[2]), int(sys.argv[3]), int(sys.argv[4]))
elif cmd == "rgb-on": result = cmd_rgb_brightness(7, 5, 4)
elif cmd == "rgb-off": result = cmd_rgb_off()
elif cmd == "tablet" and len(sys.argv) >= 3: result = cmd_tablet(*sys.argv[2:])
elif cmd == "ec-status": result = cmd_ec_status()
elif cmd == "ec-turbo-on": result = cmd_ec_turbo(True)
elif cmd == "ec-turbo-off": result = cmd_ec_turbo(False)
else: result = {"ok": False, "error": f"Unknown: {cmd}"}
print(json.dumps(result))