mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 12:43:12 +02:00
Full history preserved via git filter-repo; production wake-alarm.service already cut over to the pip-installed standalone package and verified clean. Also fixes morning_routine._orchestrator, which has been silently broken since screen-locker's extraction on 2026-05-28: it still referenced python_pkg.screen_locker.screen_lock, causing ModuleNotFoundError on every morning the workout-lock handoff ran. Both module references now point at the pip-installed standalone packages (wake_alarm._alarm, screen_locker.screen_lock); fixing this required pip-installing screen_locker into system Python for the first time too (see the separate screen-locker packaging-fix commits), which it had never been.
102 lines
3.6 KiB
Python
102 lines
3.6 KiB
Python
"""Orchestrate the morning wake/workout flow as one sequential routine.
|
|
|
|
The wake alarm (``wake_alarm``, https://github.com/kuhyx/wake-alarm) and the
|
|
workout screen lock (``screen_locker``, https://github.com/kuhyx/screen-locker)
|
|
used to run as two independent ``graphical-session.target`` user services,
|
|
each opening its own fullscreen ``-topmost`` Tk window. On a wake morning they
|
|
could grab the screen at the same time, so the alarm could end up hidden
|
|
behind the workout lock (or vice versa).
|
|
|
|
This orchestrator makes them one coherent flow by running them as **sequential
|
|
subprocesses**: the alarm runs first and owns the fullscreen until it is
|
|
dismissed, then the workout lock runs. Only one fullscreen window is ever alive
|
|
at a time, so they can never collide. Each subprocess still self-gates (the
|
|
alarm only fires on alarm days when undismissed; the lock exits if a skip was
|
|
earned or the workout is already logged), so this is safe to run on every wake.
|
|
|
|
Both ``wake_alarm`` and ``screen_locker`` are pip-installed into system
|
|
Python's user site-packages (each repo's own install.sh does this), so
|
|
``python -m <module>`` resolves them with no extra ``PYTHONPATH``/``cwd``
|
|
plumbing here.
|
|
|
|
Usage:
|
|
python -m python_pkg.morning_routine._orchestrator --with-alarm # resume
|
|
python -m python_pkg.morning_routine._orchestrator # lock only
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import logging
|
|
import subprocess
|
|
import sys
|
|
|
|
from python_pkg.shared.logging_setup import configure_logging
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
# Modules invoked as ``python -m <module> --production``.
|
|
ALARM_MODULE: str = "wake_alarm._alarm"
|
|
WORKOUT_LOCK_MODULE: str = "screen_locker.screen_lock"
|
|
|
|
|
|
def _run_module(module: str) -> int:
|
|
"""Run *module* as a blocking ``python -m`` subprocess in production mode.
|
|
|
|
Args:
|
|
module: Dotted module path to execute with ``python -m``.
|
|
|
|
Returns:
|
|
The subprocess exit code, or ``1`` when the process could not start.
|
|
"""
|
|
cmd = [sys.executable, "-m", module, "--production"]
|
|
_logger.info("morning-routine: running %s", module)
|
|
try:
|
|
result = subprocess.run(cmd, check=False)
|
|
except OSError:
|
|
_logger.warning("Failed to run %s", module, exc_info=True)
|
|
return 1
|
|
return result.returncode
|
|
|
|
|
|
def _run_alarm() -> int:
|
|
"""Run the wake alarm and block until it is dismissed (or self-exits)."""
|
|
return _run_module(ALARM_MODULE)
|
|
|
|
|
|
def _run_workout_lock() -> int:
|
|
"""Run the workout screen lock after the alarm has been dealt with."""
|
|
return _run_module(WORKOUT_LOCK_MODULE)
|
|
|
|
|
|
def _parse_args(argv: list[str]) -> argparse.Namespace:
|
|
"""Parse CLI arguments for the orchestrator."""
|
|
parser = argparse.ArgumentParser(description="Unified morning routine.")
|
|
parser.add_argument(
|
|
"--with-alarm",
|
|
action="store_true",
|
|
help="Run the wake alarm before the workout lock (used on resume).",
|
|
)
|
|
parser.add_argument(
|
|
"--production",
|
|
action="store_true",
|
|
help="Production mode (kept for systemd/CLI symmetry).",
|
|
)
|
|
return parser.parse_args(argv)
|
|
|
|
|
|
def main() -> None:
|
|
"""Entry point: optionally run the alarm, then always run the workout lock."""
|
|
configure_logging()
|
|
args = _parse_args(sys.argv[1:])
|
|
# Alarm first so it owns the fullscreen and escalates until dismissed; only
|
|
# then hand off to the workout lock. Running them in this order in a single
|
|
# process guarantees they never fight for the screen.
|
|
if args.with_alarm:
|
|
_run_alarm()
|
|
_run_workout_lock()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|