Integra calibrazione RGB nel plasmoide e ripristina backend funzionante
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Calibrazione Ambilight: fascia bianca e regolazioni live via ambilight-ctl."""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import QProcess, QTimer, Qt
|
||||
from PySide6.QtWidgets import (QApplication, QFileDialog, QHBoxLayout, QLabel,
|
||||
QMessageBox, QPushButton, QSlider, QVBoxLayout, QWidget)
|
||||
|
||||
CTL = str(Path.home() / ".local/bin/ambilight-ctl")
|
||||
FIELDS = (("gain_r", "Rosso", 0, 200), ("gain_g", "Verde", 0, 200),
|
||||
("gain_b", "Blu", 0, 200), ("brightness", "Luminosità", 5, 200))
|
||||
|
||||
|
||||
class Calibration(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle("Calibrazione Ambilight")
|
||||
self.values = {}
|
||||
self.pending = {}
|
||||
self.initialized = False
|
||||
self.process = QProcess(self)
|
||||
self.process.finished.connect(self.finished)
|
||||
self.process.errorOccurred.connect(self.process_error)
|
||||
self.current = None
|
||||
self.debounce = QTimer(self, interval=80, singleShot=True)
|
||||
self.debounce.timeout.connect(self.pump)
|
||||
|
||||
outer = QVBoxLayout(self)
|
||||
outer.setContentsMargins(0, 0, 0, 0)
|
||||
outer.setSpacing(0)
|
||||
self.white = QWidget()
|
||||
self.white.setStyleSheet("background: white;")
|
||||
outer.addWidget(self.white, 30) # 30%, maggiore della banda predefinita del 12%
|
||||
panel = QWidget()
|
||||
panel.setStyleSheet("background: #20242b; color: #eeeeee;")
|
||||
outer.addWidget(panel, 70)
|
||||
layout = QVBoxLayout(panel)
|
||||
layout.setContentsMargins(50, 20, 50, 24)
|
||||
title = QLabel("Calibrazione Ambilight")
|
||||
title.setStyleSheet("font-size: 24px; font-weight: bold;")
|
||||
layout.addWidget(title)
|
||||
hint = QLabel("La fascia superiore è bianca. Le modifiche si applicano e salvano nel demone in tempo reale.")
|
||||
hint.setWordWrap(True)
|
||||
layout.addWidget(hint)
|
||||
self.status = QLabel("Connessione al demone…")
|
||||
layout.addWidget(self.status)
|
||||
self.sliders = {}
|
||||
self.labels = {}
|
||||
for key, name, lo, hi in FIELDS:
|
||||
row = QHBoxLayout()
|
||||
label = QLabel(name)
|
||||
label.setMinimumWidth(120)
|
||||
row.addWidget(label)
|
||||
slider = QSlider(Qt.Horizontal)
|
||||
slider.setRange(lo, hi)
|
||||
slider.setEnabled(False)
|
||||
row.addWidget(slider, 1)
|
||||
value_label = QLabel("—")
|
||||
value_label.setMinimumWidth(55)
|
||||
row.addWidget(value_label)
|
||||
slider.valueChanged.connect(lambda value, k=key: self.changed(k, value))
|
||||
self.sliders[key], self.labels[key] = slider, value_label
|
||||
layout.addLayout(row)
|
||||
layout.addStretch(1)
|
||||
buttons = QHBoxLayout()
|
||||
save = QPushButton("Esporta profilo JSON…")
|
||||
save.clicked.connect(self.export_profile)
|
||||
buttons.addWidget(save)
|
||||
buttons.addStretch(1)
|
||||
close = QPushButton("Chiudi")
|
||||
close.clicked.connect(self.close)
|
||||
buttons.addWidget(close)
|
||||
layout.addLayout(buttons)
|
||||
self.call("state", [])
|
||||
|
||||
def call(self, kind, args):
|
||||
self.current = kind
|
||||
self.process.start(CTL, args or ["state"])
|
||||
|
||||
def changed(self, key, value):
|
||||
self.labels[key].setText(f"{value}%")
|
||||
if not self.initialized:
|
||||
return
|
||||
self.pending[key] = value / 100.0
|
||||
self.status.setText("Applicazione in corso…")
|
||||
self.debounce.start()
|
||||
|
||||
def pump(self):
|
||||
if self.process.state() != QProcess.NotRunning or not self.pending:
|
||||
return
|
||||
key = next(iter(self.pending))
|
||||
value = self.pending.pop(key)
|
||||
self.call(key, ["set", key, f"{value:.2f}"])
|
||||
|
||||
def process_error(self, error):
|
||||
self.status.setText("Errore: impossibile avviare ambilight-ctl; controlla il servizio.")
|
||||
self.current = None
|
||||
|
||||
def finished(self, code, exit_status):
|
||||
kind, self.current = self.current, None
|
||||
raw = bytes(self.process.readAllStandardOutput()).decode("utf-8", "replace")
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
if code != 0 or data.get("ok") is False:
|
||||
raise ValueError(data.get("error", "servizio non raggiungibile"))
|
||||
state = data.get("state", data)
|
||||
if not state.get("daemon"):
|
||||
raise ValueError(state.get("error", "servizio non raggiungibile"))
|
||||
if kind == "state":
|
||||
for key, _, _, _ in FIELDS:
|
||||
value = round(float(state[key]) * 100)
|
||||
self.sliders[key].setValue(value)
|
||||
self.sliders[key].setEnabled(True)
|
||||
self.values[key] = value / 100
|
||||
self.initialized = True
|
||||
else:
|
||||
self.values[kind] = float(state[kind])
|
||||
self.status.setText("Valori applicati e salvati dal demone." if not self.pending
|
||||
else "Applicazione in corso…")
|
||||
except (ValueError, KeyError, TypeError) as exc:
|
||||
self.status.setText(f"Errore: {exc}. Valore non confermato.")
|
||||
self.pump()
|
||||
|
||||
def export_profile(self):
|
||||
if not self.initialized or self.pending or self.process.state() != QProcess.NotRunning:
|
||||
QMessageBox.warning(self, "Profilo", "Attendi la conferma di tutte le regolazioni.")
|
||||
return
|
||||
filename, _ = QFileDialog.getSaveFileName(self, "Esporta profilo", "ambilight-calibrazione.json", "JSON (*.json)")
|
||||
if not filename:
|
||||
return
|
||||
try:
|
||||
with open(filename, "w", encoding="utf-8") as f:
|
||||
json.dump({key: self.values[key] for key, _, _, _ in FIELDS}, f, indent=2)
|
||||
f.write("\n")
|
||||
self.status.setText(f"Profilo esportato: {filename}")
|
||||
except OSError as exc:
|
||||
QMessageBox.critical(self, "Errore esportazione", str(exc))
|
||||
|
||||
def closeEvent(self, event):
|
||||
if self.pending or self.process.state() != QProcess.NotRunning:
|
||||
self.status.setText("Attendi la conferma delle modifiche prima di chiudere.")
|
||||
event.ignore()
|
||||
return
|
||||
event.accept()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
window = Calibration()
|
||||
window.showFullScreen()
|
||||
sys.exit(app.exec())
|
||||
Reference in New Issue
Block a user