feat: vollständige SuperX-Steuerung dokumentiert & abgelegt

Neu hinzugefügt:
- tools/superx-ctrl.py — zentrale CLI-Steuerung (Frostbay + Tablet RGB + EC Turbo)
- tools/tablet-rgb.py — Tablet RGB via HIDRAW
- tools/ec-read.py — EC Turbo Status Reader
- tools/turbo-*.py — 7 Turbo-Debug/Listener-Scripts
- tools/patch-hhd-superx.py — HHD Patch für Super X Turbo-Bypass
- udev/99-superx-hid.rules — HIDRAW-Zugriffsregel (0x1A86:0x1305)
- dms/plugins/SuperXCtrl/ — DMS Widget (plugin.json + QML + Settings)
- README.md — vollständig überarbeitet mit Architektur, Nutzung, Verzeichnisstruktur

Geändert:
- tools/frostbay_ble.py — rgb-brightness Subcommand hinzugefügt (0xFD Mode)
This commit is contained in:
Kay Türtscher 2026-06-19 17:28:47 +02:00
parent 5ee81f86b8
commit d477b99c73
17 changed files with 1254 additions and 44 deletions

365
README.md
View file

@ -1,64 +1,340 @@
# SuperX-goes-Arch
Arbeitsrepo für Linux-/Arch-Support auf dem OneXplayer Super X.
Arbeitsrepo für Linux-/Arch-Support auf dem **OneXPlayer Super X** (Ryzen AI Max+ 395, Radeon 8060S, 64GB).
## Aktueller Stand (2026-06-05)
Betriebssystem: **Arch Linux** · Compositor: **Hyprland 0.55.4** · Shell: **DankMaterialShell (DMS)**
### Fertig
---
| Bereich | Status |
## Übersicht
| Bereich | Status | Beschreibung |
|---------|--------|-------------|
| **Frostbay BLE (Kühlung)** | ✅ Produktiv | Fan/Pumpe via BlueZ/D-Bus steuern |
| **Frostbay RGB** | ⚠️ Funktional | BLE-Writes funktionieren, aber LED-Strips physisch nicht angeschlossen |
| **superx-ctrl.py** | ✅ Produktiv | Zentrale CLI-Steuerung (Frostbay + Tablet RGB + EC Turbo) |
| **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 |
| **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 |
| **WMI/EC-Kernel-Module** | 🔄 In Arbeit | Erste Module kompilieren, aber noch nicht funktional |
---
## Verzeichnisstruktur
```
superx-goes-arch/
├── README.md # Diese Datei
├── docs/
│ ├── frostbay-ble-protocol.md # BLE GATT Protokoll (vollständig dokumentiert)
│ ├── frostbay-onec1-initial-findings.md
│ ├── frostbay-onec1-protocol.md
│ ├── frostbay-protocol-notes.md
│ ├── internal-rgb-*.md # Mainboard RGB Research
│ ├── egpu-setup.md # eGPU Setup (RX 9060 XT)
│ ├── live-hid-inventory.md # HID Device Inventory
│ ├── hidraw1-feature-probe.md
│ ├── research-summary.md
│ ├── sources.md
│ └── ...
├── tools/
│ ├── frostbay_ble.py # Frostbay BLE GATT Reader/Controller
│ ├── superx-ctrl.py # Zentrale CLI-Steuerung (v4)
│ ├── tablet-rgb.py # Tablet RGB via HIDRAW
│ ├── ec-read.py # EC Turbo Status Reader
│ ├── turbo-daemon.py # Turbo/FN Key Combo Daemon
│ ├── turbo-monitor.py # evdev Monitor für Turbo-Tasten
│ ├── turbo-hid.py # HIDRAW Turbo-Listener
│ ├── turbo-all.py # Multi-Source Turbo-Scanner (ACPI + evdev + HID)
│ ├── turbo-deep.py # Deep Turbo-Scanner (CoolingSystem + HID + Keyboard)
│ ├── turbo-atkbd.py # AT Keyboard Turbo-Listener
│ ├── turbo-scanner.py # Alle evdev-Geräte scannen
│ ├── patch-hhd-superx.py # HHD Patch: Turbo-Bypass für Super X
│ ├── superx_hid_*.py # HID Reverse-Engineering Tools
│ ├── superx_rgb_*.py # RGB Reverse-Engineering Tools
│ ├── superx_wmi_*.py/sh # WMI/EC Tools
│ ├── superx-egpu-perf.sh # eGPU Performance Tuning
│ ├── superx-game-wrapper.sh # Game Wrapper mit GPU-Selection
│ ├── steam-egpu* # Steam eGPU Integration
│ └── wmi/ # WMI Kernel Module Source
├── udev/
│ ├── 99-superx-hid.rules # HIDRAW-Zugriff für Super X Vendor HID
│ └── 99-superx-rgb-hidraw.rules # Alternative HIDRAW-Regeln (älter)
├── configs/
│ ├── hyprland.conf # Hyprland Konfiguration
│ ├── amdgpu-egpu.conf # AMDGPU eGPU Tuning
│ └── steam-egpu.desktop # Steam Desktop Entry mit eGPU
└── dms/
└── plugins/
└── SuperXCtrl/
├── plugin.json # DMS Plugin-Metadaten
├── plugin_settings.json # Plugin-Settings (enabled)
└── SuperXWidget.qml # QML Widget (Cooling + RGB Tabs)
```
---
## Frostbay BLE (Kühlung)
Die Frostbay ONEC1 Wasserkühlung kommuniziert via **BLE GATT** (nicht HID!).
### Verbindung
| Eigenschaft | Wert |
|---|---|
| **Frostbay BLE (Drosselung)** | Produktiv — `tools/frostbay_ble.py` steuert Fan/Pumpe via BlueZ/D-Bus |
| **Frostbay Status** | Live-Read von Temp, RPM, Flow, Alerts via BLE GATT |
| **Niri + DMS** | Installiert & konfiguriert — niri 26.04 + DankMaterialShell 1.4.6 |
| **Protokoll-Doku** | `docs/frostbay-ble-protocol.md` — vollständig dokumentiert |
| Gerätename | `CoolingSystem_ONEC1` |
| BT-Adresse | variabel (z.B. `C8:17:17:F5:C8:62`) |
| Service | `0000ffe0-0000-1000-8000-00805f9b34fb` |
| Char (R/W) | `0000ffe1-0000-1000-8000-00805f9b34fb` (64 Byte Blocks) |
### In Arbeit / Blockiert
| Bereich | Status |
|---|---|
| Frostbay RGB | Funktional via BLE, aber LED-Strips physisch nicht angeschlossen |
| Mainboard RGB (WS2812B) | Hardware-Reverse läuft — Logic Analyzer bestellt, `tools/superx_signal_decode.py` vorbereitet |
| WMI/EC-Kernel-Module | Erste Module kompilieren, aber noch nicht funktional |
## Ordner
- `docs/` — Protokolldokumentation, Research-Notizen
- `tools/` — Python-Skripte und WMI-Kernel-Module
- `config/` — Niri/Wayland-Konfigurationen
## Frostbay-Steuerung
### Nutzung
```bash
# Status check
sudo python3 tools/frostbay_ble.py status
# Status abfragen
python3 tools/frostbay_ble.py status
# Drosseln (custom mode)
sudo python3 tools/frostbay_ble.py mode custom --fan 25 --pump 25
# Modus setzen
python3 tools/frostbay_ble.py mode silent
python3 tools/frostbay_ble.py mode custom --fan 25 --pump 25
# Ausschalten
sudo python3 tools/frostbay_ble.py off
python3 tools/frostbay_ble.py off
# Raw-Dump
python3 tools/frostbay_ble.py raw
```
**Wichtig:** D-Bus (BlueZ) muss für den User zugänglich sein. Standardmässig via `TAG+="uaccess"` in der udev-Regel. Kein sudo nötig.
Details: `docs/frostbay-ble-protocol.md`
## Niri + DankMaterialShell
---
Niri ist ein scrollend-kachelnder Wayland-Compositor, DMS liefert
Material-3-Shell mit Panel, Launcher, Notification-Center und mehr.
Konfiguration liegt unter `config/niri/`.
## superx-ctrl.py (Zentrale Steuerung)
`tools/superx-ctrl.py` ist die zentrale CLI-Schnittstelle, die alle Subsysteme zusammenfasst.
### Installation
```bash
# Installation (bereits erfolgt)
sudo pacman -S dms-shell dms-shell-niri matugen
# Kopieren nach ~/.local/bin/ für PATH-Zugriff
cp tools/superx-ctrl.py ~/.local/bin/superx-ctrl.py
chmod +x ~/.local/bin/superx-ctrl.py
```
### Nutzung
```bash
# Status (Frostbay + verfügbare Modi)
superx-ctrl.py status
# Frostbay Cooling Mode
superx-ctrl.py mode off|silent|gentle|strong|extreme|smart
# Custom Fan/Pump
superx-ctrl.py custom 30 30
# Frostbay RGB Preset (1-10)
superx-ctrl.py rgb-preset 9
# Frostbay RGB Helligkeit
superx-ctrl.py rgb-brightness <switch> <speed> <brightness>
superx-ctrl.py rgb-on
superx-ctrl.py rgb-off
# Tablet RGB (HIDRAW)
superx-ctrl.py tablet on|off|rainbow|aurora
superx-ctrl.py tablet <r> <g> <b> # Static Color
# EC Turbo
superx-ctrl.py ec-status
superx-ctrl.py ec-turbo-on
superx-ctrl.py ec-turbo-off
```
### Architektur
```
superx-ctrl.py
├── Frostbay BLE (D-Bus/BlueZ) → tools/frostbay_ble.py
│ ├── Cooling Modes (off/silent/gentle/strong/extreme/smart/custom)
│ ├── RGB Presets (1-10) via 2-Step BLE Write
│ └── RGB Brightness/Switch/Speed
├── Tablet RGB (HIDRAW) → tools/tablet-rgb.py
│ ├── Static Color (R G B)
│ ├── Presets (rainbow, aurora)
│ └── On/Off
└── EC Turbo (debugfs) → tools/ec-read.py
├── Turbo Status lesen
└── Turbo setzen (0x30 = 0x06/0x00)
```
---
## DMS SuperXCtrl Widget
Das DMS-Widget bietet eine GUI für Cooling- und RGB-Steuerung im DMS-Panel.
### Installation
```bash
# Plugin-Verzeichnis anlegen
mkdir -p ~/.config/DankMaterialShell/plugins/SuperXCtrl/
# Dateien kopieren
cp dms/plugins/SuperXCtrl/* ~/.config/DankMaterialShell/plugins/SuperXCtrl/
# Plugin aktivieren (plugin_settings.json)
# → {"superXCtrl": {"enabled": true}}
```
### Features
- **Cooling Tab:** 6 Modes (Off/Silent/Gentle/Strong/Extreme/Smart) + Custom Fan/Pump Slider
- **RGB Tab:** 10 Frostbay RGB Presets + On/Off + Tablet RGB (4 Presets + 6 Farben)
- **Status-Pill** in der DMS-Bar: zeigt an ob Cooling aktiv ist
### QML-Architektur
Das Widget (`SuperXWidget.qml`) ruft `superx-ctrl.py` via `Process`-Komponente auf:
```qml
Process {
id: cmdProcess
command: ["sh", "-c", "python3 /home/nepharius/.local/bin/superx-ctrl.py " + args + " 2>/dev/null"]
running: false
}
```
---
## HHD Patch (Turbo-Bypass)
`tools/patch-hhd-superx.py` patcht HHD, damit der Turbo-Button auf dem Super X funktioniert — auch ohne `oxp-platform` Kernel-Modul.
### Was der Patch macht
Ersetzt in `/usr/lib/python3.14/site-packages/hhd/device/oxp/__init__.py` die Turbo-Erkennung:
- **Vorher:** Turbo nur aktiv wenn `/sys/devices/platform/oxp-platform/tt_toggle` existiert
- **Nachher:** Zusätzlich: Wenn Super X Vendor HID (0x1A86:0x1305) erkannt wird → Turbo aktivieren
### Anwendung
```bash
# Nach HHD-Update erneut ausführen
sudo python3 tools/patch-hhd-superx.py
```
---
## Turbo-Daemon
`tools/turbo-daemon.py` lauscht auf Key Combos und führt Aktionen aus.
### Key Combos
| Combo | Aktion |
|---|---|
| `LEFTCTRL + LEFTALT + LEFTMETA` | Turbo → Cooling Mode "smart" |
| `LEFTMETA + D` | Mode → RGB Preset 9 (Aurora) |
| `LEFTMETA + O + RIGHTCTRL` | KB → Cooling Mode "gentle" |
### Start
```bash
# Manuell
python3 tools/turbo-daemon.py
# Als systemd-user Service (empfohlen)
# → ~/.config/systemd/user/turbo-daemon.service
```
---
## udev-Regeln
### 99-superx-hid.rules (aktiv)
```udev
KERNEL=="hidraw*", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="1305", MODE="0666", TAG+="uaccess"
```
Gibt Lese-/Schreibzugriff auf die Super X Vendor HID-Schnittstelle (0x1A86:0x1305).
### Installation
```bash
sudo cp udev/99-superx-hid.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules
sudo udevadm trigger
```
---
## EC Turbo
Der Embedded Controller des Super X speichert den Turbo-Status in EC-RAM.
### Register
| Offset | Grösse | Beschreibung |
|---|---|---|
| 0x30 | 1 Byte | Turbo Status (0x00=off, 0x06=turbo) |
| 0x31 | 1 Byte | Fan1 PWM |
| 0x32 | 1 Byte | Fan2 PWM |
| 0x33 | 1 Byte | Pump PWM |
### Nutzung
```bash
# Status lesen
sudo python3 tools/ec-read.py
# Watch-Mode (alle 500ms)
sudo python3 tools/ec-read.py watch
# Turbo setzen
sudo sh -c 'printf "\\x06" | dd of=/sys/kernel/debug/ec/ec0/io bs=1 seek=$((0x30)) count=1 conv=notrunc 2>/dev/null'
```
---
## Tablet RGB (HIDRAW)
`tools/tablet-rgb.py` steuert die RGB-Beleuchtung des Tablet-Moduls via `/dev/hidraw2`.
### Nutzung
```bash
# An/Aus
python3 tools/tablet-rgb.py on
python3 tools/tablet-rgb.py off
# Presets
python3 tools/tablet-rgb.py rainbow
python3 tools/tablet-rgb.py aurora
python3 tools/tablet-rgb.py preset 0x09
# Static Color
python3 tools/tablet-rgb.py 255 0 0 # Rot
python3 tools/tablet-rgb.py 0 255 0 # Grün
python3 tools/tablet-rgb.py 0 0 255 # Blau
```
---
## Hardware
- OneXplayer Super X (Intel Meteor Lake)
- Eingelötete WS2812B-Stripes am Mainboard-Anschluss RGB2
- SYSTEK ZT5571VG-Platine (GA25-RGB_FPC_VA_5522) als RGB-Controller
- Frostbay ONEC1 Wasserkühlung (BLE GATT)
- **OneXPlayer Super X** (Ryzen AI Max+ 395, Radeon 8060S, 64GB)
- **Frostbay ONEC1** Wasserkühlung (BLE GATT)
- **Tablet-Modul** mit RGB via HIDRAW (0x1A86:0x1305)
- **Eingelötete WS2812B-Stripes** am Mainboard-Anschluss RGB2 (aktuell nicht angeschlossen)
- **SYSTEK ZT5571VG-Platine** (GA25-RGB_FPC_VA_5522) als RGB-Controller
- **eGPU:** RX 9060 XT via OCuLink (all-ways-egpu)
## Git Remote
@ -68,8 +344,11 @@ git remote add origin ssh://git@git.nepharius.at:2222/nepharius/superx-goes-arch
## Roadmap
1. ~~Frostbay unter Linux/BlueZ auf Arch reproduzierbar steuern~~
2. Logic Analyzer an Mainboard-RGB2-Pin → OEM-Protokoll decodieren
3. Entweder kompatible Ersatz-LEDs oder Software-Übersetzer (MCU→WS2812B)
4. Niri/DMS-Integration mit Frostbay-Daemon (Fan-Kurven + Quick-Toggles)
5. Optional: hhd-Plugin oder Standalone-Daemon für vollständige Systemsteuerung
1. ✅ ~~Frostbay BLE unter Linux/BlueZ steuern~~
2. ✅ ~~superx-ctrl.py als zentrale CLI~~
3. ✅ ~~DMS Widget für GUI-Steuerung~~
4. ✅ ~~HHD Patch für Turbo-Button~~
5. ✅ ~~Tablet RGB via HIDRAW~~
6. 🔄 Logic Analyzer an Mainboard-RGB2-Pin → OEM-Protokoll decodieren
7. 🔄 Kompatible Ersatz-LEDs oder Software-Übersetzer (MCU→WS2812B)
8. 🔄 WMI/EC-Kernel-Module für vollständige Systemsteuerung

View file

@ -0,0 +1,275 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Plugins
PluginComponent {
id: root
pluginId: "superXCtrl"
property string currentMode: pluginData.currentMode || "smart"
property bool coolingEnabled: pluginData.coolingEnabled !== false
property int customFan: pluginData.customFan || 30
property int customPump: pluginData.customPump || 30
property int currentPreset: pluginData.currentPreset || 9
property int currentTab: 0 // 0=Cooling, 1=RGB
popoutWidth: 360
popoutHeight: 680
popoutContent: Component {
Rectangle {
implicitWidth: 360
implicitHeight: Math.max(col.implicitHeight + Theme.spacingL * 2, 620)
radius: Theme.cornerRadius
color: Theme.surfaceContainer
border.color: Theme.outlineMedium
border.width: Theme.layerOutlineWidth
Column {
id: col
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
// --- Header ---
Row {
width: parent.width; spacing: Theme.spacingM
DankIcon { name: "settings_input_component"; size: 28; color: Theme.primary; anchors.verticalCenter: parent.verticalCenter }
StyledText { text: "Super X Control"; font.pixelSize: Theme.fontSizeLarge; color: Theme.surfaceText; anchors.verticalCenter: parent.verticalCenter }
Item { width: 10; height: 1 }
Rectangle {
width: 60; height: 32; radius: Theme.cornerRadius
color: root.coolingEnabled ? Theme.primary : Theme.surfaceVariant
anchors.verticalCenter: parent.verticalCenter
MouseArea {
anchors.fill: parent; cursorShape: Qt.PointingHandCursor
onClicked: {
root.coolingEnabled = !root.coolingEnabled
root.runCmd(root.coolingEnabled ? "mode " + root.currentMode : "mode off")
}
}
StyledText { text: root.coolingEnabled ? "ON" : "OFF"; font.pixelSize: Theme.fontSizeSmall; color: root.coolingEnabled ? Theme.primaryText : Theme.surfaceVariantText; anchors.centerIn: parent }
}
}
// --- Tab Bar ---
Rectangle {
width: parent.width; height: 36; radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh; border.width: 0
Row {
anchors.centerIn: parent; spacing: Theme.spacingXS
Rectangle {
width: (parent.parent.width - Theme.spacingXS) / 2; height: 30; radius: Theme.cornerRadius
color: root.currentTab === 0 ? Theme.primary : "transparent"
MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: root.currentTab = 0 }
StyledText { text: "❄️ Cooling"; font.pixelSize: Theme.fontSizeSmall; color: root.currentTab === 0 ? Theme.primaryText : Theme.surfaceVariantText; anchors.centerIn: parent }
}
Rectangle {
width: (parent.parent.width - Theme.spacingXS) / 2; height: 30; radius: Theme.cornerRadius
color: root.currentTab === 1 ? Theme.primary : "transparent"
MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: root.currentTab = 1 }
StyledText { text: "🌈 RGB"; font.pixelSize: Theme.fontSizeSmall; color: root.currentTab === 1 ? Theme.primaryText : Theme.surfaceVariantText; anchors.centerIn: parent }
}
}
}
// --- Cooling Tab Content ---
Column {
width: parent.width
spacing: Theme.spacingM
visible: root.currentTab === 0
StyledText { text: "Cooling Mode"; font.pixelSize: Theme.fontSizeMedium; color: Theme.surfaceText }
Grid {
width: parent.width; columns: 3; rowSpacing: Theme.spacingXS; columnSpacing: Theme.spacingXS
Repeater {
model: [
{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"},
]
Rectangle {
width: (col.width - Theme.spacingXS * 2) / 3; height: 48; radius: Theme.cornerRadius
color: mam.containsMouse ? Theme.surfaceContainerHighest : "transparent"
border.color: root.currentMode === modelData.id ? Theme.primary : Theme.outlineMedium
border.width: root.currentMode === modelData.id ? 2 : 1
MouseArea {
id: mam; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor
onClicked: root.setMode(modelData.id)
}
Column {
anchors.centerIn: parent; spacing: 2
DankIcon { name: modelData.icon; color: root.currentMode === modelData.id ? Theme.primary : Theme.surfaceVariantText; size: Theme.iconSize; anchors.horizontalCenter: parent.horizontalCenter }
StyledText { text: modelData.label; font.pixelSize: Theme.fontSizeSmall; color: root.currentMode === modelData.id ? Theme.primary : Theme.surfaceText; horizontalAlignment: Text.AlignHCenter; anchors.horizontalCenter: parent.horizontalCenter }
}
}
}
}
StyledText { text: "Custom Fan / Pump"; font.pixelSize: Theme.fontSizeMedium; color: Theme.surfaceText }
Rectangle {
width: parent.width; height: 100; radius: Theme.cornerRadius; color: Theme.surfaceContainerHigh; border.width: 0
Column {
anchors.fill: parent; anchors.margins: Theme.spacingM; spacing: Theme.spacingXS
Row {
width: parent.width; spacing: Theme.spacingS
StyledText { text: "Fan: " + root.customFan + "%"; font.pixelSize: Theme.fontSizeSmall; color: Theme.surfaceText; width: 70; anchors.verticalCenter: parent.verticalCenter }
Slider { width: parent.width - 120; from: 0; to: 100; value: root.customFan; stepSize: 5; anchors.verticalCenter: parent.verticalCenter; onValueChanged: root.customFan = value }
}
Row {
width: parent.width; spacing: Theme.spacingS
StyledText { text: "Pump: " + root.customPump + "%"; font.pixelSize: Theme.fontSizeSmall; color: Theme.surfaceText; width: 70; anchors.verticalCenter: parent.verticalCenter }
Slider { width: parent.width - 120; from: 0; to: 100; value: root.customPump; stepSize: 5; anchors.verticalCenter: parent.verticalCenter; onValueChanged: root.customPump = value }
}
Rectangle {
width: 80; height: 28; radius: Theme.cornerRadius; color: Theme.primary; anchors.horizontalCenter: parent.horizontalCenter
MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: root.setCustom() }
StyledText { text: "Apply"; font.pixelSize: Theme.fontSizeSmall; color: Theme.primaryText; anchors.centerIn: parent }
}
}
}
}
// --- RGB Tab Content ---
Column {
width: parent.width
spacing: Theme.spacingM
visible: root.currentTab === 1
StyledText { text: "Frostbay RGB"; font.pixelSize: Theme.fontSizeMedium; color: Theme.surfaceText }
Grid {
width: parent.width; columns: 4; rowSpacing: Theme.spacingXS; columnSpacing: Theme.spacingXS
Repeater {
model: [
{id: 1, label: "Static", icon: "circle"},
{id: 2, label: "Breath", icon: "blur_on"},
{id: 3, label: "Wave", icon: "waves"},
{id: 4, label: "Strobe", icon: "flash_on"},
{id: 5, label: "Rainbow", icon: "gradient"},
{id: 6, label: "Marquee", icon: "arrow_forward"},
{id: 7, label: "Meteor", icon: "star"},
{id: 8, label: "Fire", icon: "local_fire_department"},
{id: 9, label: "Aurora", icon: "auto_awesome"},
{id: 10, label: "Pulse", icon: "favorite"},
]
Rectangle {
width: (col.width - Theme.spacingXS * 3) / 4; height: 40; radius: Theme.cornerRadius
color: rgbma.containsMouse ? Theme.surfaceContainerHighest : "transparent"
border.color: root.currentPreset === modelData.id ? Theme.primary : Theme.outlineMedium
border.width: root.currentPreset === modelData.id ? 2 : 1
MouseArea {
id: rgbma; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor
onClicked: root.runCmd("rgb-preset " + modelData.id)
}
StyledText { text: modelData.label; font.pixelSize: Theme.fontSizeSmall; color: root.currentPreset === modelData.id ? Theme.primary : Theme.surfaceText; anchors.centerIn: parent }
}
}
}
Row {
width: parent.width; spacing: Theme.spacingM
Rectangle {
width: (parent.width - Theme.spacingM) / 2; height: 32; radius: Theme.cornerRadius
color: Theme.primary
MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: root.runCmd("rgb-on") }
StyledText { text: "RGB ON"; font.pixelSize: Theme.fontSizeSmall; color: Theme.primaryText; anchors.centerIn: parent }
}
Rectangle {
width: (parent.width - Theme.spacingM) / 2; height: 32; radius: Theme.cornerRadius
color: Theme.surfaceVariant
MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: root.runCmd("rgb-off") }
StyledText { text: "RGB OFF"; font.pixelSize: Theme.fontSizeSmall; color: Theme.surfaceVariantText; anchors.centerIn: parent }
}
}
Item { width: 1; height: Theme.spacingXS }
// --- Tablet RGB (HIDRAW) ---
StyledText { text: "Tablet RGB"; font.pixelSize: Theme.fontSizeMedium; color: Theme.surfaceText }
Grid {
width: parent.width; columns: 4; rowSpacing: Theme.spacingXS; columnSpacing: Theme.spacingXS
Repeater {
model: [
{cmd: "on", label: "An", icon: "lightbulb"},
{cmd: "off", label: "Aus", icon: "light_off"},
{cmd: "rainbow", label: "Regenbogen", icon: "gradient"},
{cmd: "aurora", label: "Aurora", icon: "auto_awesome"},
]
Rectangle {
width: (col.width - Theme.spacingXS * 3) / 4; height: 40; radius: Theme.cornerRadius
color: tma.containsMouse ? Theme.surfaceContainerHighest : "transparent"
border.color: Theme.outlineMedium; border.width: 1
MouseArea { id: tma; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onClicked: root.runCmd("tablet " + modelData.cmd) }
Column { anchors.centerIn: parent; spacing: 2
DankIcon { name: modelData.icon; color: Theme.primary; size: Theme.iconSize - 4; anchors.horizontalCenter: parent.horizontalCenter }
StyledText { text: modelData.label; font.pixelSize: Theme.fontSizeSmall; color: Theme.surfaceText; horizontalAlignment: Text.AlignHCenter }
}
}
}
}
StyledText { text: "Farbe wählen"; font.pixelSize: Theme.fontSizeSmall; color: Theme.surfaceVariantText }
Grid {
width: parent.width; columns: 6; rowSpacing: Theme.spacingXS; columnSpacing: Theme.spacingXS
Repeater {
model: [
{cmd: "255 0 0", label: "Rot", color: "#ff0000"},
{cmd: "0 255 0", label: "Grün", color: "#00ff00"},
{cmd: "0 0 255", label: "Blau", color: "#0000ff"},
{cmd: "255 255 0", label: "Gelb", color: "#ffff00"},
{cmd: "255 0 255", label: "Lila", color: "#ff00ff"},
{cmd: "255 255 255", label: "Weiß", color: "#ffffff"},
]
Rectangle {
width: (col.width - Theme.spacingXS * 5) / 6; height: 36; radius: Theme.cornerRadius
color: tma2.containsMouse ? Theme.surfaceContainerHighest : "transparent"
border.color: modelData.color; border.width: 2
MouseArea { id: tma2; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onClicked: root.runCmd("tablet " + modelData.cmd) }
StyledText { text: modelData.label; font.pixelSize: Theme.fontSizeSmall; color: Theme.surfaceText; anchors.centerIn: parent }
}
}
}
}
}
}
}
horizontalBarPill: Component {
Row { spacing: 4
DankIcon { name: root.coolingEnabled ? "settings_input_component" : "ac_unit"; color: root.coolingEnabled ? Theme.primary : Theme.surfaceVariantText; size: Theme.iconSize - 4; anchors.verticalCenter: parent.verticalCenter }
StyledText { text: "SX"; font.pixelSize: Theme.fontSizeSmall; color: root.coolingEnabled ? Theme.primary : Theme.surfaceVariantText; anchors.verticalCenter: parent.verticalCenter }
}
}
function runCmd(args) {
cmdProcess.command = ["sh", "-c", "python3 /home/nepharius/.local/bin/superx-ctrl.py " + args + " 2>/dev/null"]
cmdProcess.running = true
}
Process { id: cmdProcess; running: false }
function setMode(modeId) {
currentMode = modeId
if (!coolingEnabled) coolingEnabled = true
runCmd("mode " + modeId)
}
function setCustom() {
if (!coolingEnabled) return
runCmd("custom " + customFan + " " + customPump)
}
}

View file

@ -0,0 +1,12 @@
{
"id": "superXCtrl",
"name": "Super X Control",
"description": "Frostbay cooling modes, fan curves, and RGB control for OneXPlayer Super X",
"version": "1.0.0",
"author": "Kay & Hoerman",
"type": "widget",
"capabilities": ["control-center"],
"component": "./SuperXWidget.qml",
"icon": "settings_input_component",
"permissions": ["settings_read", "settings_write"]
}

View file

@ -0,0 +1,5 @@
{
"superXCtrl": {
"enabled": true
}
}

42
tools/ec-read.py Executable file
View file

@ -0,0 +1,42 @@
#!/usr/bin/env python3
"""EC Turbo Reader — Liest EC RAM. Wird via sudo ausgeführt."""
import os, struct, json, sys, time
EC_DEV = "/sys/kernel/debug/ec/ec0/io"
def read_ec():
with open(EC_DEV, "rb") as f:
data = f.read()
return data
def get_turbo(data):
b30 = data[0x30] # Turbo Status
fan1 = data[0x31]
fan2 = data[0x32]
pump = data[0x33]
turbo_map = {
0: "off",
6: "turbo",
}
return {
"turbo": b30 > 0,
"turbo_status": b30,
"turbo_label": turbo_map.get(b30, f"mode_{b30}"),
"fan1_pwm": fan1,
"fan2_pwm": fan2,
"pump_pwm": pump,
}
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "watch":
prev = None
while True:
status = get_turbo(read_ec())
if status != prev:
print(json.dumps(status), flush=True)
prev = status
time.sleep(0.5)
else:
print(json.dumps(get_turbo(read_ec())))

View file

@ -133,7 +133,7 @@ def write_state(mode_code: int, fan_pwm: int = None, pump_pwm: int = None,
if rgb_mode is not None:
buff[16] = rgb_mode & 0xFF
if rgb_mode == 0xFE or rgb_mode == 0xFF:
if rgb_mode == 0xFE or rgb_mode == 0xFF or rgb_mode == 0xFD:
# RGB values go to write_buff[17-19] (→ Ram_op_params_buff[20-22])
if rgb_r is not None:
buff[17] = rgb_r & 0xFF
@ -174,6 +174,11 @@ def main():
p_rgb.add_argument("g", type=int)
p_rgb.add_argument("b", type=int)
p_rgb_brightness = sub.add_parser("rgb-brightness", help="Set RGB brightness/switch/speed (0xFD)")
p_rgb_brightness.add_argument("switch", type=int)
p_rgb_brightness.add_argument("speed", type=int)
p_rgb_brightness.add_argument("brightness", type=int)
sub.add_parser("off", help="Turn Frostbay off")
sub.add_parser("raw", help="Raw hex dump of ffe1")
@ -216,6 +221,14 @@ def main():
rgb_r=args.r, rgb_g=args.g, rgb_b=args.b)
print_state(s)
elif args.cmd == "rgb-brightness":
cur = parse_state(read_state())
s = write_state(0xFF, rgb_mode=0xFD,
fan_pwm=cur["fan_pwm"],
pump_pwm=cur["pump_pwm"],
rgb_r=args.switch, rgb_g=args.speed, rgb_b=args.brightness)
print_state(s)
elif args.cmd == "off":
s = write_state(0x00)
print_state(s)

58
tools/patch-hhd-superx.py Normal file
View file

@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""Patch HHD __init__.py: enable turbo for Super X even without oxp-platform."""
filepath = "/usr/lib/python3.14/site-packages/hhd/device/oxp/__init__.py"
with open(filepath) as f:
c = f.read()
old = """ turbo = False
if self.dconf.get("turbo", True) and os.path.exists(
"/sys/devices/platform/oxp-platform/tt_toggle"
):
try:
with open("/sys/devices/platform/oxp-platform/tt_toggle", "w") as f:
f.write("1")
logger.info(f"Turbo button takeover enabled")
turbo = True
if os.path.exists("/sys/devices/platform/oxp-platform/tt_led"):
with open("/sys/devices/platform/oxp-platform/tt_led", "w") as f:
f.write("0")
except Exception:
logger.warning(
f"Turbo takeover failed. Ensure you have the latest oxp-sensors driver installed."
)
self.turbo = turbo"""
new = """ turbo = False
from hhd.controller.lib.hid import enumerate_unique
has_superx = bool(enumerate_unique(vid=0x1A86, pid=0x1305, usage_page=0xFF00, usage=0xFF00))
if self.dconf.get("turbo", True):
if os.path.exists(
"/sys/devices/platform/oxp-platform/tt_toggle"
):
try:
with open("/sys/devices/platform/oxp-platform/tt_toggle", "w") as f:
f.write("1")
logger.info(f"Turbo button takeover enabled")
turbo = True
if os.path.exists("/sys/devices/platform/oxp-platform/tt_led"):
with open("/sys/devices/platform/oxp-platform/tt_led", "w") as f:
f.write("0")
except Exception:
logger.warning(
f"Turbo takeover failed. Ensure you have the latest oxp-sensors driver installed."
)
elif has_superx:
logger.info(f"Super X turbo button enabled via HID vendor device.")
turbo = True
self.turbo = turbo"""
assert old in c, "Patch target not found"
c = c.replace(old, new)
with open(filepath, "w") as f:
f.write(c)
print("✅ PATCHED: __init__.py — Super X turbo bypasses oxp-platform check")

170
tools/superx-ctrl.py Normal file
View file

@ -0,0 +1,170 @@
#!/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("~/.local/bin/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))

84
tools/tablet-rgb.py Normal file
View file

@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""Test tablet RGB via HIDRAW — adapted from ps1x/onexplayer-superxcontrol for Super X."""
import sys, os, json, time
HIDRAW = "/dev/hidraw2"
PACKET_SIZE = 64
def pad(prefix):
return prefix + [0] * (PACKET_SIZE - len(prefix))
def write_hidraw(data):
with open(HIDRAW, "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],
"mid": [0x07, 0xFF, 0xFD, 0x01, 0x05, 0x03],
"high": [0x07, 0xFF, 0xFD, 0x01, 0x05, 0x04],
}
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":
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": "need args"}))

56
tools/turbo-all.py Normal file
View file

@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""Listen for ACPI events + HID + evdev — catch Turbo from anywhere."""
import evdev, select, os, subprocess, threading, time
# Start acpi_listen in background
acpi_out = []
def acpi_thread():
try:
proc = subprocess.Popen(['acpi_listen'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)
while True:
line = proc.stdout.readline()
if line:
print(f'[ACPI] {line.strip()}', flush=True)
except:
pass
t = threading.Thread(target=acpi_thread, daemon=True)
t.start()
# Also listen on hidraw2
try:
fd = os.open('/dev/hidraw2', os.O_RDONLY | os.O_NONBLOCK)
hid_fds = [fd]
except:
hid_fds = []
# And all evdev
evdevs = []
for i in range(30):
try:
d = evdev.InputDevice(f'/dev/input/event{i}')
evdevs.append(d)
except:
pass
print(f'📟 Hörman lauscht auf:')
print(f' - ACPI events')
print(f' - {len(evdevs)} evdev devices')
print(f' - hidraw2 (vendor HID)')
print(f'Drück den TURBO-Knopf am Gehäuse!')
print('=' * 50)
while True:
r, _, _ = select.select(evdevs + hid_fds, [], [], 1.0)
for fd in r:
if isinstance(fd, int): # hidraw
data = os.read(fd, 64)
if data:
print(f'[HIDRAW] {data[:16].hex(" ")}', flush=True)
else: # evdev
for e in fd.read():
if e.type != 0:
name = evdev.ecodes.KEY.get(e.code, f'0x{e.code:04x}')
val = {1: '⬇️', 0: '⬆️', 2: '🔄'}.get(e.value, str(e.value))
print(f'[{fd.name[:30]:30s}] {val} {name:20s} 0x{e.code:04x}', flush=True)

24
tools/turbo-atkbd.py Normal file
View file

@ -0,0 +1,24 @@
#!/usr/bin/env python3
"""Listen on AT keyboard (event2) for Turbo combo - no grab."""
import evdev, select
d = evdev.InputDevice('/dev/input/event2')
print(f'📟 Hörman lauscht auf: {d.name}')
print('Drück den TURBO-Knopf am Gehäuse!')
print('=' * 50)
pressed = set()
TURBO = {29, 56, 125}
for e in d.read_loop():
if e.type == 1:
name = evdev.ecodes.KEY.get(e.code, f'0x{e.code:04x}')
if e.value == 1:
pressed.add(e.code)
if TURBO.issubset(pressed):
print(f'🔥🔥🔥 TURBO PRESSED!', flush=True)
elif e.value == 0:
pressed.discard(e.code)
if e.value != 2:
val = {1: '⬇️', 0: '⬆️'}.get(e.value, str(e.value))
print(f' {val} {name:25s} 0x{e.code:04x}', flush=True)

69
tools/turbo-daemon.py Normal file
View file

@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""Turbo/FN Button Daemon für Super X — direct key combo listening."""
import evdev, select, subprocess, os, signal
# HHD Key combos from const.py:
# Turbo = LEFTCTRL(29) + LEFTALT(56) + LEFTMETA(125)
# Mode = KEY_D(32) + LEFTMETA(125)
# KB = KEY_O(24) + RIGHTCTRL(97) + LEFTMETA(125)
TURBO_COMBO = {29, 56, 125}
MODE_COMBO = {32, 125}
KB_COMBO = {24, 97, 125}
# Super X Vendor HID (for RGB + Turntable)
VENDOR_VID = 0x1A86
VENDOR_PID = 0x1305
pressed = set()
# Auto-start HHD
try:
subprocess.run(["systemctl", "--user", "is-active", "hhd"], capture_output=True, timeout=2)
except:
pass
def run_cmd(cmd):
subprocess.run(cmd, capture_output=True, timeout=5)
def handle_combo(keys, label):
print(f"🔥 {label} combo triggered! active keys: {keys}", flush=True)
if TURBO_COMBO.issubset(keys):
# Turbo: toggle TDP profile
run_cmd(["python3", "/home/nepharius/.local/bin/superx-ctrl.py", "mode", "smart"])
print(" -> TDP balanced", flush=True)
elif MODE_COMBO.issubset(keys):
# Mode: toggle RGB preset
run_cmd(["python3", "/home/nepharius/.local/bin/superx-ctrl.py", "rgb-preset", "9"])
print(" -> RGB Aurora", flush=True)
elif KB_COMBO.issubset(keys):
# KB: toggle cooling mode
run_cmd(["python3", "/home/nepharius/.local/bin/superx-ctrl.py", "mode", "gentle"])
print(" -> Cooling gentle", flush=True)
d = evdev.InputDevice('/dev/input/event4')
print(f'Hörman Turbo Daemon läuft auf: {d.name}', flush=True)
print(f'Active combos:', flush=True)
print(f' LEFTCTRL+LEFTALT+LEFTMETA = Turbo (TDP toggle)', flush=True)
print(f' LEFTSUPER+D = Mode (RGB Aurora)', flush=True)
print(f' LEFTSUPER+O+RIGHTCTRL = Keyboard (Cooling)', flush=True)
for e in d.read_loop():
if e.type == 1: # KEY event
ec = evdev.ecodes.KEY
name = ec.get(e.code, hex(e.code))
if e.value == 1: # press
pressed.add(e.code)
# Check combos instantly
if TURBO_COMBO.issubset(pressed):
print(f'🔥 TURBO PRESSED!', flush=True)
try:
subprocess.run(["python3", "/home/nepharius/.local/bin/superx-ctrl.py", "mode", "smart"],
capture_output=True, timeout=3)
except: pass
elif e.value == 0: # release
pressed.discard(e.code)

55
tools/turbo-deep.py Normal file
View file

@ -0,0 +1,55 @@
#!/usr/bin/env python3
"""Listen on ACPI + CoolingSystem + all for Turbo."""
import evdev, select, os, subprocess, threading
# ACPI listener
def acpi_loop():
try:
proc = subprocess.Popen(['acpi_listen'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)
for line in iter(proc.stdout.readline, ''):
if line:
print(f'[ACPI] {line.strip()}', flush=True)
except: pass
threading.Thread(target=acpi_loop, daemon=True).start()
# CoolingSystem (event20) - Frostbay's own HID
try:
d_cool = evdev.InputDevice('/dev/input/event20')
print(f'📟 CoolingSystem: {d_cool.name}', flush=True)
except: d_cool = None
# Also listen on hidraw2 (vendor HID)
try:
fd_hid = os.open('/dev/hidraw2', os.O_RDONLY | os.O_NONBLOCK)
print(f'📟 hidraw2 (vendor HID)', flush=True)
except: fd_hid = None
# And event4 (Juer Xin keyboard) for MSC scan codes
try:
d_kbd = evdev.InputDevice('/dev/input/event4')
print(f'📟 Juer Xin Keyboard (event4)', flush=True)
except: d_kbd = None
devices = []
if d_cool: devices.append(d_cool)
if d_kbd: devices.append(d_kbd)
hid_fds = []
if fd_hid: hid_fds.append(fd_hid)
print(f'\nDrück den TURBO-Knopf am Gehäuse!')
print('=' * 50)
while True:
r, _, _ = select.select(devices + hid_fds, [], [], 1.0)
for fd in r:
if isinstance(fd, int): # hidraw
data = os.read(fd, 64)
if data:
print(f'[HIDRAW] {data[:16].hex(" ")}', flush=True)
else: # evdev
for e in fd.read():
if e.type != 0:
name = evdev.ecodes.KEY.get(e.code, f'0x{e.code:04x}')
val = {1: '⬇️', 0: '⬆️', 2: '🔄'}.get(e.value, str(e.value))
print(f'[{fd.name[:30]:30s}] {val} {name:20s} 0x{e.code:04x}', flush=True)

21
tools/turbo-hid.py Normal file
View file

@ -0,0 +1,21 @@
#!/usr/bin/env python3
"""Read raw HID reports from Super X vendor interface."""
import select, os, struct
fd = os.open('/dev/hidraw2', os.O_RDONLY | os.O_NONBLOCK)
print('📟 Hörman lauscht auf /dev/hidraw2 (Super X Vendor HID)')
print('Drück den TURBO-Knopf am Gehäuse!')
print('=' * 50)
while True:
r, _, _ = select.select([fd], [], [], 60)
if r:
data = os.read(fd, 64)
if data and len(data) >= 8:
hex_str = ' '.join(f'{b:02x}' for b in data[:20])
vals = list(data[:20])
cid = data[0]
btn = data[6] if len(data) > 6 else 0
pressed = data[12] if len(data) > 12 else 0
print(f'REPORT: {hex_str}')
print(f' cid=0x{cid:02x} btn=0x{btn:02x} pressed={pressed}', flush=True)

21
tools/turbo-monitor.py Normal file
View file

@ -0,0 +1,21 @@
#!/usr/bin/env python3
"""Turbo/fn monitor — listen on keyboard for scan codes + key events."""
import evdev, select
d = evdev.InputDevice('/dev/input/event4')
d.grab()
print(f'📟 Hörman lauscht auf: {d.name}', flush=True)
print('Drück Turbo / FN / Spezialtasten!', flush=True)
KEY_EVENT = 1
MSC_EVENT = 4
for e in d.read_loop():
if e.type == MSC_EVENT:
print(f' MSC code=0x{e.code:04x}', flush=True)
elif e.type == KEY_EVENT:
name = evdev.ecodes.KEY.get(e.code, f'0x{e.code:04x}')
val = {1: '⬇️', 0: '⬆️', 2: '🔄'}.get(e.value, str(e.value))
print(f' KEY {val} {name:30s} 0x{e.code:04x}', flush=True)
elif e.type == 0: # SYN
pass

25
tools/turbo-scanner.py Normal file
View file

@ -0,0 +1,25 @@
#!/usr/bin/env python3
"""Scan ALL input devices for Turbo button activity."""
import evdev, select, os
# List all event devices
devices = []
for path in [f'/dev/input/event{i}' for i in range(30)]:
try:
d = evdev.InputDevice(path)
devices.append(d)
except:
pass
print(f'Hörman scannt {len(devices)} Devices...')
print('Drück den TURBO-Knopf (am Gehäuse, unterhalb Volume)!')
print('=' * 50)
while True:
r, _, _ = select.select(devices, [], [], 1.0)
for d in r:
for e in d.read():
if e.type != 0: # Skip SYN
name = evdev.ecodes.KEY.get(e.code, f'0x{e.code:04x}')
val = {1: '⬇️', 0: '⬆️', 2: '🔄'}.get(e.value, str(e.value))
print(f'[{d.name[:35]:35s}] {val} {name:25s} 0x{e.code:04x}', flush=True)

1
udev/99-superx-hid.rules Normal file
View file

@ -0,0 +1 @@
KERNEL=="hidraw*", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="1305", MODE="0666", TAG+="uaccess"