mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-10 20:27:13 +01:00
The grab axes are grab=GrabPolicy(kind=..., disable_vt=..., preempt_weaker_holder=...) and every colour read goes through config.palette; nothing about the lock's behaviour changed. Also carries the pylint hook on 4.0.8 with the source-only scope config; the gate stays at 8.0 until the remaining findings are cleared. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HHCbkJWXHjtTUaqvnHRAqA
112 lines
4.5 KiB
Python
112 lines
4.5 KiB
Python
"""Live Warsaw-temperature background check mixin for ``StatusWindow``.
|
|
|
|
Fetches the same reading the real locker's heat-skip check would see
|
|
(``_temperature.fetch_current_temp_with_status``), in a background thread,
|
|
bounded to ``_temperature.HARD_TIMEOUT_SECONDS`` — display-only, never
|
|
writes to ``log.json``. Host class must provide:
|
|
|
|
- ``self.root`` — the Tk root, for ``root.after`` polling.
|
|
- ``self.temperature_fetcher`` — injected fetch callable (tests override it).
|
|
- ``self._temp_future`` / ``self._temp_result`` — state slots, initialized
|
|
by the host's ``__init__``.
|
|
- ``self._last_snapshot`` — the most recently rendered ``StatusSnapshot``.
|
|
- ``self.render(snapshot)`` — full redraw, called after a result resolves.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
from screen_locker._constants import HEAT_SKIP_CITY, HEAT_SKIP_TEMP_THRESHOLD
|
|
from screen_locker._temperature import HARD_TIMEOUT_SECONDS, submit_background
|
|
|
|
if TYPE_CHECKING:
|
|
import tkinter as tk
|
|
|
|
from screen_locker._temperature import TemperatureCheck
|
|
|
|
|
|
class TemperatureStatusMixin:
|
|
"""Provides the background Warsaw-temperature fetch/poll/render."""
|
|
|
|
def _section_temperature(self, parent: tk.Widget) -> None:
|
|
"""Render the live Warsaw temperature.
|
|
|
|
The same reading the real locker's heat-skip check would see,
|
|
fetched in the background.
|
|
"""
|
|
del parent
|
|
self._label("Warsaw Temperature", role="body", pad="sm")
|
|
result = self._temp_result
|
|
if result is None:
|
|
self._text(
|
|
"Checking Warsaw temperature…",
|
|
role="caption",
|
|
color=self._colors.palette.muted,
|
|
)
|
|
return
|
|
if result.timed_out:
|
|
self._text(
|
|
f"Warsaw temperature check timed out after ~"
|
|
f"{int(HARD_TIMEOUT_SECONDS)}s — network may be down.",
|
|
role="caption",
|
|
color=self._colors.palette.warning,
|
|
)
|
|
return
|
|
if result.temp_celsius is None:
|
|
self._text(
|
|
"Warsaw temperature check failed (network/API error).",
|
|
role="caption",
|
|
color=self._colors.palette.warning,
|
|
)
|
|
return
|
|
hot = result.temp_celsius >= HEAT_SKIP_TEMP_THRESHOLD
|
|
self._text(
|
|
f"Warsaw: {result.temp_celsius:.0f}°C (heat-skip threshold "
|
|
f"{HEAT_SKIP_TEMP_THRESHOLD}°C)",
|
|
role="caption",
|
|
color=self._colors.palette.warning if hot else self._colors.palette.muted,
|
|
)
|
|
if hot:
|
|
self._text(
|
|
"Would trigger heat-skip today.",
|
|
role="caption",
|
|
color=self._colors.palette.danger,
|
|
)
|
|
|
|
def _start_temperature_check(self) -> None:
|
|
"""Submit a background Warsaw-temperature fetch and start polling it.
|
|
|
|
Unlike ``_on_check_phone_clicked`` (an explicit, opt-in user action),
|
|
this runs on every window open and refresh, so the first poll is
|
|
always deferred through ``root.after`` rather than checked
|
|
synchronously — otherwise a worker thread that happens to finish
|
|
before this call returns would trigger a second, redundant render
|
|
from within the same call stack that hasn't finished the first one.
|
|
"""
|
|
# Shares _temperature.submit_background rather than spinning up its
|
|
# own ThreadPoolExecutor per open and per refresh: those workers are
|
|
# non-daemon and joined at interpreter exit, so one stalled fetch
|
|
# held the whole process open past sys.exit(0).
|
|
self._temp_future = submit_background(
|
|
lambda: self.temperature_fetcher(HEAT_SKIP_CITY)
|
|
)
|
|
self.root.after(500, self._poll_temperature_check)
|
|
|
|
def _poll_temperature_check(self) -> None:
|
|
"""Poll the background temperature-check future until it resolves."""
|
|
if self._temp_future is not None and self._temp_future.done():
|
|
self._on_temperature_check_result(self._temp_future.result())
|
|
else:
|
|
self.root.after(500, self._poll_temperature_check)
|
|
|
|
def _on_temperature_check_result(self, result: TemperatureCheck) -> None:
|
|
"""Store the resolved temperature check and redraw to show it.
|
|
|
|
Re-renders the last snapshot already on screen rather than
|
|
re-gathering from disk — the temperature reading is independent of
|
|
on-disk workout state, so there's nothing new to read.
|
|
"""
|
|
self._temp_result = result
|
|
self.render(self._last_snapshot)
|