Screen: switch off by itself when nothing is going on

With `general.screen.off_command` set, the monitor switches off once nothing has
played and nobody has done anything for `auto_off_minutes` (15 by default, 0 for
never, editable in parent mode). "Doing anything" is any intent a person could
have caused: a remote key, a web command, or a key press or tap in the kiosk,
which the page reports at most every 30s via POST /api/screen/activity. The first
such intent after an auto-off runs `on_command`, so the remote wakes the screen
too. The auto-off itself is silent; switches a person asks for still blubb.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 19:42:59 +02:00
parent 459599d2ba
commit 077c4e819f
16 changed files with 455 additions and 49 deletions

View File

@@ -8,7 +8,7 @@ from pathlib import Path
import httpx2
import pytest
from musicmouse.config import WebConfig, load_config
from musicmouse.config import ScreenConfig, WebConfig, load_config
from musicmouse.services.web.service import build_app
from musicmouse.services.web.settings import to_device_volume, to_percent
from musicmouse.simulator.harness import Simulation, build_simulation
@@ -99,8 +99,46 @@ def test_a_degenerate_range_reads_as_full(config_dir: Path) -> None:
async def test_settings_expose_only_the_editable_subset(client: httpx2.AsyncClient) -> None:
body = (await client.get("/api/settings")).json()
assert set(body) == set(SETTINGS)
assert set(body) == {*SETTINGS, "screen_auto_off_minutes"}
assert body["max_volume"] == 60
assert body["screen_auto_off_minutes"] is None # no screen to switch off
async def test_the_screen_auto_off_is_read_and_saved_into_its_section(
client: httpx2.AsyncClient, sim: Simulation, config_dir: Path
) -> None:
path = config_dir / "config.yml"
path.write_text(
COMMENTED.replace(
"\nfigures:",
" screen:\n toggle_command: kiosk-screen toggle\n"
" off_command: kiosk-screen off\n\nfigures:",
),
encoding="utf-8",
)
sim.app.config.general.screen = ScreenConfig(
toggle_command="kiosk-screen toggle", off_command="kiosk-screen off"
)
assert (await client.get("/api/settings")).json()["screen_auto_off_minutes"] == 15
response = await client.put("/api/settings", json={**SETTINGS, "screen_auto_off_minutes": 30})
assert response.status_code == 200
assert response.json()["screen_auto_off_minutes"] == 30
assert sim.app.config.general.screen.auto_off_minutes == 30 # live, for ScreenService
reloaded = load_config(path)
assert reloaded.general.screen is not None
assert reloaded.general.screen.auto_off_minutes == 30
assert "screen_auto_off_minutes" not in path.read_text(encoding="utf-8")
async def test_the_screen_auto_off_without_a_screen_is_ignored(
client: httpx2.AsyncClient, config_dir: Path
) -> None:
response = await client.put("/api/settings", json={**SETTINGS, "screen_auto_off_minutes": 30})
assert response.status_code == 200
assert response.json()["screen_auto_off_minutes"] is None
assert "screen" not in (config_dir / "config.yml").read_text(encoding="utf-8")
# -------------------------------------------------------------------------- writing