feat: add ONEXPLAYER SUPER X support to oxp-sensors + Super X Control tools
- Patched oxpec.c with DMI match for 'ONEXPLAYER SUPER X' (oxp_x1) - Added DKMS build files (dkms.conf, Makefile) - Added superx-ctrl-menu.py (yad-based GUI menu) - Added superx-tray.py (Qt6 system tray, optional) - Fixed frostbay_ble.py path in superx-ctrl.py
This commit is contained in:
parent
cc2d513554
commit
1738820df5
7 changed files with 1439 additions and 1 deletions
7
patches/oxpec-Makefile
Normal file
7
patches/oxpec-Makefile
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
obj-m += oxpec.o
|
||||
|
||||
all:
|
||||
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
|
||||
|
||||
clean:
|
||||
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
|
||||
5
patches/oxpec-dkms.conf
Normal file
5
patches/oxpec-dkms.conf
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
PACKAGE_NAME=oxpec-superx
|
||||
PACKAGE_VERSION=1.0
|
||||
BUILT_MODULE_NAME[0]=oxpec
|
||||
DEST_MODULE_LOCATION[0]=/kernel/drivers/platform/x86
|
||||
AUTOINSTALL=yes
|
||||
1008
patches/oxpec-superx.c
Normal file
1008
patches/oxpec-superx.c
Normal file
File diff suppressed because it is too large
Load diff
183
tools/superx-ctrl-menu.py
Normal file
183
tools/superx-ctrl-menu.py
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Super X Control - CLI Menu (zenity)
|
||||
Einfach, robust, funktioniert immer.
|
||||
Starte mit: python3 superx-ctrl-menu.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import json
|
||||
|
||||
CTRL = os.path.expanduser("~/Projekte/superx-goes-arch/tools/superx-ctrl.py")
|
||||
|
||||
def run_cmd(*args):
|
||||
try:
|
||||
r = subprocess.run([sys.executable, CTRL] + list(args),
|
||||
capture_output=True, text=True, timeout=10)
|
||||
if r.returncode == 0 and r.stdout.strip():
|
||||
return json.loads(r.stdout)
|
||||
return {"ok": False, "error": r.stderr}
|
||||
except Exception as e:
|
||||
return {"ok": False, "error": str(e)}
|
||||
|
||||
def zenity_list(title, text, columns, items):
|
||||
"""Zeigt eine zenity-Liste und gibt die Auswahl zurück."""
|
||||
cols = "--column=" + " --column=".join(columns)
|
||||
args = ["zenity", "--list", "--title=" + title, "--text=" + text,
|
||||
"--width=400", "--height=400", cols]
|
||||
for item in items:
|
||||
args.append(str(item))
|
||||
try:
|
||||
r = subprocess.run(args, capture_output=True, text=True, timeout=30)
|
||||
if r.returncode == 0:
|
||||
return r.stdout.strip()
|
||||
return None
|
||||
except:
|
||||
return None
|
||||
|
||||
def zenity_scale(title, text, value=50):
|
||||
"""Zeigt einen Schieberegler."""
|
||||
try:
|
||||
r = subprocess.run(
|
||||
["zenity", "--scale", "--title=" + title, "--text=" + text,
|
||||
"--value=" + str(value), "--min-value=0", "--max-value=100",
|
||||
"--step=5", "--width=400"],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
if r.returncode == 0 and r.stdout.strip():
|
||||
return int(r.stdout.strip())
|
||||
return None
|
||||
except:
|
||||
return None
|
||||
|
||||
def zenity_question(text):
|
||||
"""Ja/Nein Frage."""
|
||||
try:
|
||||
r = subprocess.run(
|
||||
["zenity", "--question", "--text=" + text, "--width=300"],
|
||||
capture_output=True, timeout=30)
|
||||
return r.returncode == 0
|
||||
except:
|
||||
return False
|
||||
|
||||
def main_menu():
|
||||
while True:
|
||||
choice = zenity_list(
|
||||
"Super X Control",
|
||||
"Wähle eine Aktion:",
|
||||
["Option"],
|
||||
[
|
||||
"❄️ Cooling Mode",
|
||||
"🎮 Custom Fan/Pump",
|
||||
"🌈 Frostbay RGB",
|
||||
"💡 Tablet RGB",
|
||||
"📊 Status",
|
||||
"❌ Beenden"
|
||||
]
|
||||
)
|
||||
|
||||
if not choice or "Beenden" in choice:
|
||||
break
|
||||
|
||||
elif "Cooling" in choice:
|
||||
mode = zenity_list(
|
||||
"Cooling Mode",
|
||||
"Wähle Kühlmodus:",
|
||||
["Modus"],
|
||||
["⏹️ Off", "🔇 Silent", "🌬️ Gentle", "💨 Strong", "🚀 Extreme", "🤖 Smart"]
|
||||
)
|
||||
if mode:
|
||||
mid = mode.split()[-1].lower()
|
||||
if mid == "off": mid = "off"
|
||||
elif mid == "silent": mid = "silent"
|
||||
elif mid == "gentle": mid = "gentle"
|
||||
elif mid == "strong": mid = "strong"
|
||||
elif mid == "extreme": mid = "extreme"
|
||||
elif mid == "smart": mid = "smart"
|
||||
result = run_cmd("mode", mid)
|
||||
if result.get("ok"):
|
||||
subprocess.run(["notify-send", "Super X", f"Mode: {mid}"])
|
||||
else:
|
||||
subprocess.run(["notify-send", "-u", "critical", "Super X", f"Fehler: {result.get('error', '?')}"])
|
||||
|
||||
elif "Custom" in choice:
|
||||
# Beide Regler in einem Fenster
|
||||
try:
|
||||
r = subprocess.run(
|
||||
["yad", "--form", "--title=Lüfter & Pumpe",
|
||||
"--text=Stelle Lüfter und Pumpe ein:",
|
||||
"--field=Lüfter:SCL", "0:100:30",
|
||||
"--field=Pumpe:SCL", "0:100:30",
|
||||
"--width=400", "--button=Abbrechen:1", "--button=Anwenden:0"],
|
||||
capture_output=True, text=True, timeout=60
|
||||
)
|
||||
if r.returncode == 0 and r.stdout.strip():
|
||||
parts = r.stdout.strip().split("|")
|
||||
if len(parts) >= 2:
|
||||
fan = int(float(parts[0].strip()))
|
||||
pump = int(float(parts[1].strip()))
|
||||
result = run_cmd("custom", str(fan), str(pump))
|
||||
if result.get("ok"):
|
||||
subprocess.run(["notify-send", "Super X", f"Fan: {fan}% | Pump: {pump}%"])
|
||||
else:
|
||||
subprocess.run(["notify-send", "-u", "critical", "Super X", f"Fehler: {result.get('error', '?')}"])
|
||||
except Exception as e:
|
||||
subprocess.run(["notify-send", "-u", "critical", "Super X", f"Fehler: {e}"])
|
||||
|
||||
elif "Frostbay" in choice:
|
||||
preset = zenity_list(
|
||||
"Frostbay RGB",
|
||||
"Wähle RGB-Preset:",
|
||||
["Preset"],
|
||||
["1 Static", "2 Breath", "3 Wave", "4 Strobe",
|
||||
"5 Rainbow", "6 Marquee", "7 Meteor", "8 Fire",
|
||||
"9 Aurora", "10 Pulse", "---", "RGB ON", "RGB OFF"]
|
||||
)
|
||||
if preset:
|
||||
if "ON" in preset:
|
||||
run_cmd("rgb-on")
|
||||
subprocess.run(["notify-send", "Super X", "RGB ON"])
|
||||
elif "OFF" in preset:
|
||||
run_cmd("rgb-off")
|
||||
subprocess.run(["notify-send", "Super X", "RGB OFF"])
|
||||
elif preset.strip():
|
||||
pid = preset.split()[0]
|
||||
result = run_cmd("rgb-preset", pid)
|
||||
if result.get("ok"):
|
||||
subprocess.run(["notify-send", "Super X", f"RGB Preset: {pid}"])
|
||||
|
||||
elif "Tablet" in choice:
|
||||
action = zenity_list(
|
||||
"Tablet RGB",
|
||||
"Wähle Aktion:",
|
||||
["Aktion"],
|
||||
["An", "Aus", "Regenbogen", "Aurora", "Farbe wählen"]
|
||||
)
|
||||
if action:
|
||||
if action == "Farbe wählen":
|
||||
color = zenity_list(
|
||||
"Farbe",
|
||||
"Wähle Farbe:",
|
||||
["Farbe"],
|
||||
["Rot", "Grün", "Blau", "Gelb", "Lila", "Weiß"]
|
||||
)
|
||||
if color:
|
||||
colors = {"Rot": "255 0 0", "Grün": "0 255 0", "Blau": "0 0 255",
|
||||
"Gelb": "255 255 0", "Lila": "255 0 255", "Weiß": "255 255 255"}
|
||||
run_cmd("tablet", colors.get(color, "255 255 255"))
|
||||
subprocess.run(["notify-send", "Super X", f"Tablet: {color}"])
|
||||
else:
|
||||
cmd = action.lower()
|
||||
run_cmd("tablet", cmd)
|
||||
subprocess.run(["notify-send", "Super X", f"Tablet: {action}"])
|
||||
|
||||
elif "Status" in choice:
|
||||
result = run_cmd("status")
|
||||
if result.get("ok"):
|
||||
subprocess.run(["notify-send", "Super X", "Status OK ✓"])
|
||||
else:
|
||||
subprocess.run(["notify-send", "-u", "critical", "Super X", f"Fehler: {result.get('error', '?')}"])
|
||||
|
||||
if __name__ == "__main__":
|
||||
main_menu()
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
"""superx-ctrl.py v4 — Frostbay cooling + RGB presets + Direct D-Bus writes."""
|
||||
import json, sys, os, subprocess, time, dbus
|
||||
|
||||
FROSTBAY_BLE = os.path.expanduser("~/git.nepharius.at/superx-goes-arch/tools/frostbay_ble.py")
|
||||
FROSTBAY_BLE = os.path.expanduser("~/Projekte/superx-goes-arch/tools/frostbay_ble.py")
|
||||
|
||||
COOL_MODES = [
|
||||
{"id": "off", "label": "Off", "icon": "power_settings_new"},
|
||||
|
|
|
|||
187
tools/superx-tray.py
Normal file
187
tools/superx-tray.py
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Super X Control - COSMIC System Tray (Qt6)
|
||||
Steuert Frostbay Kühlung, RGB und Tablet-RGB
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import signal
|
||||
import functools
|
||||
|
||||
from PyQt6.QtCore import QProcess, QTimer
|
||||
from PyQt6.QtGui import QIcon, QAction, QPixmap, QPainter, QColor, QFont
|
||||
from PyQt6.QtWidgets import QApplication, QSystemTrayIcon, QMenu, QDialog, QVBoxLayout, QHBoxLayout, QLabel, QSlider, QPushButton
|
||||
|
||||
CTRL = os.path.expanduser("~/Projekte/superx-goes-arch/tools/superx-ctrl.py")
|
||||
|
||||
def run_cmd(*args):
|
||||
proc = QProcess()
|
||||
proc.start(sys.executable, [CTRL] + list(args))
|
||||
proc.waitForFinished(10000)
|
||||
return proc.readAllStandardOutput().data().decode()
|
||||
|
||||
def create_icon():
|
||||
pixmap = QPixmap(32, 32)
|
||||
pixmap.fill(QColor(0, 0, 0, 0))
|
||||
painter = QPainter(pixmap)
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
||||
painter.setBrush(QColor(0, 150, 255))
|
||||
painter.setPen(QColor(0, 100, 200))
|
||||
painter.drawEllipse(2, 2, 28, 28)
|
||||
painter.setPen(QColor(255, 255, 255))
|
||||
font = QFont("sans-serif", 12, QFont.Weight.Bold)
|
||||
painter.setFont(font)
|
||||
painter.drawText(4, 4, 24, 24, 0x0084, "SX")
|
||||
painter.end()
|
||||
return QIcon(pixmap)
|
||||
|
||||
class CustomDialog(QDialog):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle("Lüfter & Pumpe")
|
||||
self.setFixedSize(350, 200)
|
||||
|
||||
layout = QVBoxLayout()
|
||||
|
||||
# Fan
|
||||
fan_row = QHBoxLayout()
|
||||
fan_row.addWidget(QLabel("Lüfter:"))
|
||||
self.fan_slider = QSlider()
|
||||
self.fan_slider.setRange(0, 100)
|
||||
self.fan_slider.setValue(30)
|
||||
self.fan_label = QLabel("30%")
|
||||
self.fan_slider.valueChanged.connect(lambda v: self.fan_label.setText(f"{v}%"))
|
||||
fan_row.addWidget(self.fan_slider)
|
||||
fan_row.addWidget(self.fan_label)
|
||||
layout.addLayout(fan_row)
|
||||
|
||||
# Pump
|
||||
pump_row = QHBoxLayout()
|
||||
pump_row.addWidget(QLabel("Pumpe:"))
|
||||
self.pump_slider = QSlider()
|
||||
self.pump_slider.setRange(0, 100)
|
||||
self.pump_slider.setValue(30)
|
||||
self.pump_label = QLabel("30%")
|
||||
self.pump_slider.valueChanged.connect(lambda v: self.pump_label.setText(f"{v}%"))
|
||||
pump_row.addWidget(self.pump_slider)
|
||||
pump_row.addWidget(self.pump_label)
|
||||
layout.addLayout(pump_row)
|
||||
|
||||
# Buttons
|
||||
btn_row = QHBoxLayout()
|
||||
cancel_btn = QPushButton("Abbrechen")
|
||||
cancel_btn.clicked.connect(self.reject)
|
||||
apply_btn = QPushButton("Anwenden")
|
||||
apply_btn.clicked.connect(self.accept)
|
||||
btn_row.addWidget(cancel_btn)
|
||||
btn_row.addWidget(apply_btn)
|
||||
layout.addLayout(btn_row)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
def get_values(self):
|
||||
return self.fan_slider.value(), self.pump_slider.value()
|
||||
|
||||
class SuperXTray:
|
||||
def __init__(self):
|
||||
self.app = QApplication(sys.argv)
|
||||
self.app.setApplicationName("Super X Control")
|
||||
self.app.setQuitOnLastWindowClosed(False)
|
||||
|
||||
self.tray = QSystemTrayIcon()
|
||||
self.tray.setIcon(create_icon())
|
||||
self.tray.setToolTip("Super X Control")
|
||||
|
||||
self._build_menu()
|
||||
self.tray.setContextMenu(self.menu)
|
||||
self.tray.activated.connect(self._on_activated)
|
||||
self.tray.show()
|
||||
|
||||
def _make_cmd(self, *args):
|
||||
"""Factory-Funktion: erzeugt eine Callback-Funktion mit eingefrorenen args"""
|
||||
def cmd():
|
||||
run_cmd(*args)
|
||||
return cmd
|
||||
|
||||
def _show_custom_dialog(self):
|
||||
dialog = CustomDialog()
|
||||
if dialog.exec() == QDialog.DialogCode.Accepted:
|
||||
fan, pump = dialog.get_values()
|
||||
run_cmd("custom", str(fan), str(pump))
|
||||
|
||||
def _build_menu(self):
|
||||
self.menu = QMenu()
|
||||
|
||||
title = self.menu.addAction("❄️ Cooling Modes")
|
||||
title.setEnabled(False)
|
||||
|
||||
modes = [
|
||||
("off", "⏹️ Off"),
|
||||
("silent", "🔇 Silent"),
|
||||
("gentle", "🌬️ Gentle"),
|
||||
("strong", "💨 Strong"),
|
||||
("extreme", "🚀 Extreme"),
|
||||
("smart", "🤖 Smart"),
|
||||
]
|
||||
for mid, label in modes:
|
||||
action = QAction(label, self.menu)
|
||||
action.triggered.connect(self._make_cmd("mode", mid))
|
||||
self.menu.addAction(action)
|
||||
|
||||
self.menu.addSeparator()
|
||||
|
||||
# Custom Fan/Pump
|
||||
action = QAction("🎮 Custom Fan/Pump...", self.menu)
|
||||
action.triggered.connect(self._show_custom_dialog)
|
||||
self.menu.addAction(action)
|
||||
|
||||
self.menu.addSeparator()
|
||||
|
||||
title = self.menu.addAction("🌈 Frostbay RGB")
|
||||
title.setEnabled(False)
|
||||
|
||||
rgb_presets = [
|
||||
("1", "Static"), ("2", "Breath"), ("3", "Wave"), ("4", "Strobe"),
|
||||
("5", "Rainbow"), ("6", "Marquee"), ("7", "Meteor"), ("8", "Fire"),
|
||||
("9", "Aurora"), ("10", "Pulse"),
|
||||
]
|
||||
for pid, label in rgb_presets:
|
||||
action = QAction(label, self.menu)
|
||||
action.triggered.connect(self._make_cmd("rgb-preset", pid))
|
||||
self.menu.addAction(action)
|
||||
|
||||
self.menu.addAction("RGB ON", self._make_cmd("rgb-on"))
|
||||
self.menu.addAction("RGB OFF", self._make_cmd("rgb-off"))
|
||||
|
||||
self.menu.addSeparator()
|
||||
|
||||
title = self.menu.addAction("💡 Tablet RGB")
|
||||
title.setEnabled(False)
|
||||
|
||||
self.menu.addAction("An", self._make_cmd("tablet", "on"))
|
||||
self.menu.addAction("Aus", self._make_cmd("tablet", "off"))
|
||||
self.menu.addAction("Regenbogen", self._make_cmd("tablet", "rainbow"))
|
||||
self.menu.addAction("Aurora", self._make_cmd("tablet", "aurora"))
|
||||
|
||||
self.menu.addSeparator()
|
||||
self.menu.addAction("❌ Beenden", self.app.quit)
|
||||
|
||||
def _on_activated(self, reason):
|
||||
if reason == QSystemTrayIcon.ActivationReason.DoubleClick:
|
||||
run_cmd("status")
|
||||
|
||||
def run(self):
|
||||
sys.exit(self.app.exec())
|
||||
|
||||
if __name__ == "__main__":
|
||||
while True:
|
||||
try:
|
||||
tray = SuperXTray()
|
||||
tray.run()
|
||||
except Exception as e:
|
||||
print(f"Crash: {e}, restarting...", file=sys.stderr)
|
||||
QTimer.singleShot(2000, lambda: None)
|
||||
continue
|
||||
break
|
||||
48
tools/superx-tray.sh
Normal file
48
tools/superx-tray.sh
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#!/usr/bin/env bash
|
||||
# Super X Control - COSMIC System Tray (yad)
|
||||
set -e
|
||||
|
||||
CTRL="$HOME/Projekte/superx-goes-arch/tools/superx-ctrl.py"
|
||||
|
||||
# yad --notification mit flachem Menu (keine Submenüs möglich)
|
||||
yad --notification \
|
||||
--command="bash -c 'python3 $CTRL status && notify-send \"Super X\" \"Status OK\"'" \
|
||||
--menu="\
|
||||
⏹️ Off!bash -c 'python3 $CTRL mode off'|\
|
||||
🔇 Silent!bash -c 'python3 $CTRL mode silent'|\
|
||||
🌬️ Gentle!bash -c 'python3 $CTRL mode gentle'|\
|
||||
💨 Strong!bash -c 'python3 $CTRL mode strong'|\
|
||||
🚀 Extreme!bash -c 'python3 $CTRL mode extreme'|\
|
||||
🤖 Smart!bash -c 'python3 $CTRL mode smart'|\
|
||||
---!true|\
|
||||
🎮 Custom Fan/Pump!bash -c '
|
||||
FAN=\$(yad --scale --title=\"Lüfter\" --text=\"Lüftergeschwindigkeit:\" --value=30 --min-value=0 --max-value=100 --step=5 --width=400 --button=Abbrechen:1 --button=OK:0)
|
||||
if [ \$? -eq 0 ] && [ -n \"\$FAN\" ]; then
|
||||
PUMP=\$(yad --scale --title=\"Pumpe\" --text=\"Pumpengeschwindigkeit:\" --value=30 --min-value=0 --max-value=100 --step=5 --width=400 --button=Abbrechen:1 --button=OK:0)
|
||||
if [ \$? -eq 0 ] && [ -n \"\$PUMP\" ]; then
|
||||
python3 \"$CTRL\" custom \"\$FAN\" \"\$PUMP\"
|
||||
notify-send \"Super X\" \"Fan: \$FAN% | Pump: \$PUMP%\"
|
||||
fi
|
||||
fi'|\
|
||||
---!true|\
|
||||
Static!bash -c 'python3 $CTRL rgb-preset 1'|\
|
||||
Breath!bash -c 'python3 $CTRL rgb-preset 2'|\
|
||||
Wave!bash -c 'python3 $CTRL rgb-preset 3'|\
|
||||
Strobe!bash -c 'python3 $CTRL rgb-preset 4'|\
|
||||
Rainbow!bash -c 'python3 $CTRL rgb-preset 5'|\
|
||||
Marquee!bash -c 'python3 $CTRL rgb-preset 6'|\
|
||||
Meteor!bash -c 'python3 $CTRL rgb-preset 7'|\
|
||||
Fire!bash -c 'python3 $CTRL rgb-preset 8'|\
|
||||
Aurora!bash -c 'python3 $CTRL rgb-preset 9'|\
|
||||
Pulse!bash -c 'python3 $CTRL rgb-preset 10'|\
|
||||
RGB ON!bash -c 'python3 $CTRL rgb-on'|\
|
||||
RGB OFF!bash -c 'python3 $CTRL rgb-off'|\
|
||||
---!true|\
|
||||
Tablet An!bash -c 'python3 $CTRL tablet on'|\
|
||||
Tablet Aus!bash -c 'python3 $CTRL tablet off'|\
|
||||
Tablet Regenbogen!bash -c 'python3 $CTRL tablet rainbow'|\
|
||||
Tablet Aurora!bash -c 'python3 $CTRL tablet aurora'|\
|
||||
---!true|\
|
||||
❌ Beenden!quit" \
|
||||
--image="preferences-system" \
|
||||
--text="Super X Control"
|
||||
Loading…
Add table
Reference in a new issue