aboutsummaryrefslogtreecommitdiffstats
path: root/hyprsunset_qt/ui.py
diff options
context:
space:
mode:
Diffstat (limited to 'hyprsunset_qt/ui.py')
-rw-r--r--hyprsunset_qt/ui.py238
1 files changed, 238 insertions, 0 deletions
diff --git a/hyprsunset_qt/ui.py b/hyprsunset_qt/ui.py
new file mode 100644
index 0000000..5281f94
--- /dev/null
+++ b/hyprsunset_qt/ui.py
@@ -0,0 +1,238 @@
+"""PyQt6 GUI for hyprsunset-qt."""
+
+from __future__ import annotations
+
+from PyQt6.QtWidgets import (
+ QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
+ QLabel, QLineEdit, QCheckBox, QSpinBox, QDoubleSpinBox, QPushButton,
+ QGroupBox, QScrollArea,
+)
+
+from . import config as cfg
+from . import daemon
+from . import sun
+from .settings import Settings
+
+
+class ProfileRow(QWidget):
+ def __init__(self, profile: cfg.Profile):
+ super().__init__()
+ lay = QHBoxLayout(self)
+ lay.setContentsMargins(0, 0, 0, 0)
+
+ self.time = QLineEdit(profile.time)
+ self.time.setFixedWidth(70)
+
+ self.identity = QCheckBox("identity")
+ self.identity.setChecked(profile.identity)
+
+ self.temp_on = QCheckBox("temp")
+ self.temp_on.setChecked(profile.temperature is not None)
+ self.temp = QSpinBox()
+ self.temp.setRange(1000, 20000)
+ self.temp.setValue(profile.temperature or 5500)
+
+ self.gamma_on = QCheckBox("gamma")
+ self.gamma_on.setChecked(profile.gamma is not None)
+ self.gamma = QDoubleSpinBox()
+ self.gamma.setRange(0.0, 2.0)
+ self.gamma.setSingleStep(0.05)
+ self.gamma.setValue(profile.gamma if profile.gamma is not None else 1.0)
+
+ self.remove = QPushButton("✕")
+ self.remove.setFixedWidth(28)
+
+ for w in (QLabel("time"), self.time, self.identity,
+ self.temp_on, self.temp, self.gamma_on, self.gamma,
+ self.remove):
+ lay.addWidget(w)
+ lay.addStretch()
+
+ self.temp_on.toggled.connect(self.temp.setEnabled)
+ self.gamma_on.toggled.connect(self.gamma.setEnabled)
+ self.temp.setEnabled(self.temp_on.isChecked())
+ self.gamma.setEnabled(self.gamma_on.isChecked())
+
+ def to_profile(self) -> cfg.Profile:
+ return cfg.Profile(
+ time=self.time.text().strip(),
+ identity=self.identity.isChecked(),
+ temperature=self.temp.value() if self.temp_on.isChecked() else None,
+ gamma=self.gamma.value() if self.gamma_on.isChecked() else None,
+ )
+
+
+class MainWindow(QMainWindow):
+ def __init__(self):
+ super().__init__()
+ self.setWindowTitle("hyprsunset-qt")
+ self.settings = Settings.load()
+ self.rows: list[ProfileRow] = []
+
+ central = QWidget()
+ self.setCentralWidget(central)
+ root = QVBoxLayout(central)
+
+ # Daemon status bar
+ bar = QHBoxLayout()
+ self.status = QLabel()
+ restart_btn = QPushButton("Restart")
+ restart_btn.clicked.connect(self.on_restart)
+ bar.addWidget(self.status)
+ bar.addStretch()
+ bar.addWidget(restart_btn)
+ root.addLayout(bar)
+
+ # Location group
+ loc = QGroupBox("Location")
+ loc_lay = QVBoxLayout(loc)
+ coords = QHBoxLayout()
+ self.lat = QLineEdit(self.settings.lat)
+ self.lon = QLineEdit(self.settings.lon)
+ self.auto = QCheckBox("auto")
+ self.auto.setChecked(self.settings.auto_detect)
+ detect = QPushButton("Detect")
+ detect.clicked.connect(self.on_detect)
+ for w in (QLabel("Lat"), self.lat, QLabel("Lon"), self.lon,
+ self.auto, detect):
+ coords.addWidget(w)
+ loc_lay.addLayout(coords)
+ fetch_row = QHBoxLayout()
+ fetch_btn = QPushButton("Fetch sun times")
+ fetch_btn.clicked.connect(self.on_fetch)
+ self.sun_label = QLabel("sunrise — sunset —")
+ fetch_row.addWidget(fetch_btn)
+ fetch_row.addWidget(self.sun_label)
+ fetch_row.addStretch()
+ loc_lay.addLayout(fetch_row)
+ root.addWidget(loc)
+
+ # Profiles group
+ prof_box = QGroupBox("Profiles")
+ prof_outer = QVBoxLayout(prof_box)
+ add_row = QHBoxLayout()
+ add_btn = QPushButton("+ Add")
+ add_btn.clicked.connect(lambda: self.add_row(cfg.Profile(time="0:00")))
+ add_row.addStretch()
+ add_row.addWidget(add_btn)
+ prof_outer.addLayout(add_row)
+ self.rows_lay = QVBoxLayout()
+ prof_outer.addLayout(self.rows_lay)
+ root.addWidget(prof_box)
+
+ # Bottom actions
+ actions = QHBoxLayout()
+ preview_btn = QPushButton("Live preview")
+ preview_btn.clicked.connect(self.on_preview)
+ save_btn = QPushButton("Save + Restart")
+ save_btn.clicked.connect(self.on_save)
+ actions.addWidget(preview_btn)
+ actions.addStretch()
+ actions.addWidget(save_btn)
+ root.addLayout(actions)
+
+ for p in cfg.read_config():
+ self.add_row(p)
+ self.refresh_status()
+ self.load_cached_sun()
+
+ # --- helpers ---
+ def add_row(self, profile: cfg.Profile) -> None:
+ row = ProfileRow(profile)
+ row.remove.clicked.connect(lambda: self.remove_row(row))
+ self.rows.append(row)
+ self.rows_lay.addWidget(row)
+
+ def remove_row(self, row: ProfileRow) -> None:
+ self.rows.remove(row)
+ row.setParent(None)
+
+ def profiles(self) -> list[cfg.Profile]:
+ return [r.to_profile() for r in self.rows]
+
+ def refresh_status(self) -> None:
+ running = daemon.is_running()
+ self.status.setText(
+ "Daemon: ● running" if running else "Daemon: ○ stopped"
+ )
+
+ def save_settings(self) -> None:
+ self.settings.lat = self.lat.text().strip()
+ self.settings.lon = self.lon.text().strip()
+ self.settings.auto_detect = self.auto.isChecked()
+ self.settings.save()
+
+ # --- actions ---
+ def on_restart(self) -> None:
+ daemon.restart(self.settings.daemon_command)
+ self.refresh_status()
+
+ def on_detect(self) -> None:
+ try:
+ lat, lon = sun.geolocate()
+ self.lat.setText(lat)
+ self.lon.setText(lon)
+ except Exception as e: # noqa: BLE001
+ self.sun_label.setText(f"detect failed: {e}")
+
+ def on_fetch(self) -> None:
+ self.save_settings()
+ lat, lon = self.lat.text().strip(), self.lon.text().strip()
+ try:
+ data = sun.fetch_sun(lat, lon)
+ except Exception as e: # noqa: BLE001
+ self.sun_label.setText(f"fetch failed: {e}")
+ return
+ data["lat"], data["lon"] = lat, lon
+ sun.write_cache(data, self.settings.cache_path_expanded())
+ self.apply_sun(data)
+
+ def load_cached_sun(self) -> None:
+ data = sun.read_cache(self.settings.cache_path_expanded())
+ if data:
+ self.apply_sun(data)
+
+ def apply_sun(self, data: dict) -> None:
+ r = data.get("results", {})
+ sunrise = sun.to_local_hm(r["sunrise"]) if r.get("sunrise") else "—"
+ sunset = sun.to_local_hm(r["sunset"]) if r.get("sunset") else "—"
+ self.sun_label.setText(f"sunrise {sunrise} sunset {sunset}")
+ profiles = self.profiles()
+ di = cfg.day_index(profiles)
+ ni = cfg.night_index(profiles)
+ if di is not None and sunrise != "—":
+ self.rows[di].time.setText(sunrise)
+ if ni is not None and sunset != "—":
+ self.rows[ni].time.setText(sunset)
+
+ def on_preview(self) -> None:
+ profiles = self.profiles()
+ ni = cfg.night_index(profiles)
+ target = profiles[ni] if ni is not None else (
+ profiles[0] if profiles else None
+ )
+ if target is None:
+ return
+ daemon.live_preview(
+ temperature=target.temperature,
+ gamma=target.gamma,
+ identity=target.identity,
+ )
+
+ def on_save(self) -> None:
+ profiles = self.profiles()
+ for p in profiles:
+ if not cfg.valid_time(p.time):
+ self.sun_label.setText(f"invalid time: {p.time!r}")
+ return
+ if p.temperature is not None and not cfg.valid_temperature(p.temperature):
+ self.sun_label.setText(f"invalid temperature: {p.temperature}")
+ return
+ if p.gamma is not None and not cfg.valid_gamma(p.gamma):
+ self.sun_label.setText(f"invalid gamma: {p.gamma}")
+ return
+ cfg.write_config(profiles)
+ self.save_settings()
+ daemon.restart(self.settings.daemon_command)
+ self.refresh_status()
+ self.sun_label.setText("saved + restarted")