Add on-screen clock overlay (outlined text, configurable corner/timezone)

A small always-on-top mpv window renders the local time in a screen
corner. Implementation notes:

- Transparent lavfi canvas (color=...@0.0, --alpha=yes) so the camera
  video shows through; no new dependencies. If the compositor can't do
  alpha it degrades to a dark backing, still legible.
- Time drawn as an ASS osd-overlay pushed over mpv IPC once a second:
  white fill + black outline (\bord), so it reads on both bright (day)
  and dark (night) scenes without sampling the picture. Formatting the
  text in Go avoids any filtergraph escaping.
- Time computed with time.LoadLocation against a configured IANA zone
  (default "Local"), so it's correct regardless of the host clock's zone
  and handles DST. A bad zone name fails at startup.
- Managed on the daemon's own context (survives layout switches); the
  daemon keeps it positioned and raised above camera tiles. ensureClock
  is a no-op when the clock config + resolution are unchanged, so a
  reload never disturbs it.

Config: new `clock` section (enabled, timezone, format, corner,
font_size, width, height, margin) with defaults and corner validation.
Documented in README and config.example (shipped enabled, America/Denver,
24-hour w/ seconds). Tests cover corner geometry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Levi Woodard
2026-07-02 14:20:49 -05:00
parent 840324825b
commit caed091dc2
8 changed files with 419 additions and 1 deletions

View File

@@ -179,6 +179,19 @@ func (c *Client) Place(ctx context.Context, pid int, r Rect) error {
return c.run(ctx, cmd)
}
// PlaceAndRaise positions the window owned by pid and raises it above the
// other floating windows (by focusing it — sway raises the focused float).
// Used for the clock overlay, which must stay on top of the camera tiles even
// after a layout switch remaps windows over it. There is no keyboard on the
// kiosk, so taking focus has no downside.
func (c *Client) PlaceAndRaise(ctx context.Context, pid int, r Rect) error {
cmd := fmt.Sprintf(
"[pid=%d] floating enable, move absolute position %d %d, resize set %d %d, focus",
pid, r.X, r.Y, r.W, r.H,
)
return c.run(ctx, cmd)
}
// PrepareForMPV installs a global rule so every mpv window maps floating,
// ready for the daemon to position. Borders are already off via the kiosk
// config's `default_border none`, so this rule is a single command: sway's