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:
parent
5ee81f86b8
commit
d477b99c73
17 changed files with 1254 additions and 44 deletions
365
README.md
365
README.md
|
|
@ -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
|
||||
|
|
|
|||
275
dms/plugins/SuperXCtrl/SuperXWidget.qml
Normal file
275
dms/plugins/SuperXCtrl/SuperXWidget.qml
Normal 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)
|
||||
}
|
||||
}
|
||||
12
dms/plugins/SuperXCtrl/plugin.json
Normal file
12
dms/plugins/SuperXCtrl/plugin.json
Normal 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"]
|
||||
}
|
||||
5
dms/plugins/SuperXCtrl/plugin_settings.json
Normal file
5
dms/plugins/SuperXCtrl/plugin_settings.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"superXCtrl": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
42
tools/ec-read.py
Executable file
42
tools/ec-read.py
Executable 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())))
|
||||
|
|
@ -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
58
tools/patch-hhd-superx.py
Normal 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
170
tools/superx-ctrl.py
Normal 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
84
tools/tablet-rgb.py
Normal 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
56
tools/turbo-all.py
Normal 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
24
tools/turbo-atkbd.py
Normal 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
69
tools/turbo-daemon.py
Normal 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
55
tools/turbo-deep.py
Normal 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
21
tools/turbo-hid.py
Normal 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
21
tools/turbo-monitor.py
Normal 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
25
tools/turbo-scanner.py
Normal 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
1
udev/99-superx-hid.rules
Normal file
|
|
@ -0,0 +1 @@
|
|||
KERNEL=="hidraw*", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="1305", MODE="0666", TAG+="uaccess"
|
||||
Loading…
Add table
Reference in a new issue