feat(hyprdrive): dark holo scanlines for kitty + caffeine widget in station-bar

kitty: regenerate scanlines.png — the old tile was a washed-out near-white
overlay that milked out the terminal. Replace it with a dark holographic tile
(gentle violet #5018DD tint + crisp violet scanlines every 4px + sparse
magenta/crimson grain) on a transparent base, so the window's own opacity/blur
still show the wallpaper through it. Seamlessly tileable; matches the suite's
HologramOverlay palette.

station-bar: add a caffeine badge to the right zone, a faithful port of
hyprlua's 4-state eww caffeine widget. Click toggles the shared idle-inhibit
lock via the same scripts/caffeine.sh; the glyph tracks manual inhibit (coffee)
/ webcam presence (eye) / idle (sleep) and glows magenta while presence holds
the lock, reading the same lock/flag files the status scripts do. Nerd Font
codepoints verified against Agave's cmap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
main
Amir Alexander Abdelbaki 2026-07-21 19:38:36 +02:00
parent fa78fc9a66
commit f94a825c82
4 changed files with 97 additions and 4 deletions

View File

@ -12,11 +12,12 @@ scrollback_lines 20000
background_opacity 0.4
background_blur 1
# Holographic scanlines + noise grain + soft ambient violet/crimson bloom,
# Holographic scanlines + gentle violet tint + sparse magenta/crimson grain,
# matching the suite's HologramOverlay treatment (orbit-menu/astro-menu/
# station-bar/beacon) — a static tiled equivalent since kitty has no
# per-frame draw hook to animate it, and no shader hook to bloom glyphs
# directly, so the "glow" comes from ambient light behind the text instead.
# station-bar/beacon) — a static, seamlessly-tiled equivalent since kitty has
# no per-frame draw hook to animate it and no shader hook to bloom glyphs
# directly. Regenerate with scratch gen_scanlines.py (violet #5018DD stripes on
# a transparent base, so the window's own translucency/blur still show through).
background_image scanlines.png
background_image_layout tiled
background_tint 0.3

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -19,6 +19,7 @@ on hide so windows reflow to use the freed space.
from __future__ import annotations
import math
import os
import subprocess
import time
from typing import Optional
@ -61,6 +62,24 @@ ICON_SPACESHIP = chr(0xf135)
ICON_WINDOW = chr(0xf10ac)
ICON_VOLUME = chr(0xf04c3)
ICON_FUEL = chr(0xf07c5) # md-fuel — the "battery" reads as a fuel tank
# md-coffee 0xf0176 — a manual caffeine session is holding the idle lock
# md-eye 0xf0208 — presence-detect sees you, so idle is inhibited for you
# md-sleep 0xf04b2 — nothing inhibiting idle; the machine may sleep
# (codepoints verified against Agave Nerd Font's cmap; the ☕ emoji hyprlua's
# eww widget used has no glyph in this font, so md-coffee stands in for it.)
ICON_COFFEE = chr(0xf0176)
ICON_EYE = chr(0xf0208)
ICON_SLEEP = chr(0xf04b2)
# Shared idle-lock protocol, identical to scripts/caffeine.sh and the status
# scripts it ships alongside — we read the same files rather than shelling out to
# caffeine-manual-status.sh / presence-status.sh on every poll:
# PID alive → the idle inhibitor is held (by anyone)
# PID alive AND no OWNED flag → a MANUAL caffeine toggle holds it
# PRESENCE flag present → the webcam presence daemon currently sees you
_CAFFEINE_PID_FILE = "/tmp/caffeine-inhibit.pid"
_PRESENCE_OWNED_FLAG = "/tmp/presence-inhibit-owned"
_PRESENCE_FLAG = "/tmp/presence-detected"
class StationBar(Gtk.Window):
@ -70,6 +89,7 @@ class StationBar(Gtk.Window):
BATTERY_POLL_S = 20
CLOCK_POLL_S = 1
VOLUME_POLL_S = 3 # cheap fallback so hardware volume keys still reflect promptly
CAFFEINE_POLL_S = 2 # matches hyprlua's eww caffeine poll; catches external toggles
def __init__(self, hologram_enabled: bool = True, monitor=None) -> None:
super().__init__()
@ -153,9 +173,11 @@ class StationBar(Gtk.Window):
GLib.timeout_add_seconds(self.BATTERY_POLL_S, self._refresh_battery)
GLib.timeout_add_seconds(self.CLOCK_POLL_S, self._refresh_clock)
GLib.timeout_add_seconds(self.VOLUME_POLL_S, self._refresh_volume)
GLib.timeout_add_seconds(self.CAFFEINE_POLL_S, self._refresh_caffeine)
self._refresh_battery()
self._refresh_clock()
self._refresh_volume()
self._refresh_caffeine()
self.set_visible(True)
self._ensure_tick()
@ -411,6 +433,16 @@ class StationBar(Gtk.Window):
self._volume_btn.add_controller(scroll)
self._right.append(self._volume_btn)
# Caffeine — clicking toggles the shared idle-inhibit lock via the same
# scripts/caffeine.sh hyprlua's eww bar drives. Glyph and colour track
# the same two inputs as that widget (manual inhibit + camera presence).
self._caffeine_btn = Gtk.Button(label=ICON_SLEEP)
self._caffeine_btn.add_css_class("station-badge")
self._caffeine_btn.add_css_class("station-caffeine")
self._caffeine_btn.set_has_frame(False)
self._caffeine_btn.connect("clicked", self._on_caffeine_clicked)
self._right.append(self._caffeine_btn)
self._clock_label = Gtk.Label()
self._clock_label.add_css_class("station-badge")
self._clock_label.add_css_class("station-clock")
@ -465,6 +497,59 @@ class StationBar(Gtk.Window):
self._refresh_volume()
return False
# -- caffeine (shared idle-inhibit lock) -------------------------------------
@staticmethod
def _pid_alive(pid_file: str) -> bool:
"""True if pid_file holds the PID of a still-running process (kill -0)."""
try:
with open(pid_file) as fh:
pid = int(fh.read().strip())
except (OSError, ValueError):
return False
try:
os.kill(pid, 0) # signal 0 = liveness probe, sends nothing
except OSError:
return False
return True
def _caffeine_state(self) -> tuple[bool, bool]:
"""(manual, presence) — same two inputs as hyprlua's eww caffeine widget:
a manual toggle holding the lock, and the webcam presence flag."""
manual = self._pid_alive(_CAFFEINE_PID_FILE) and not os.path.exists(_PRESENCE_OWNED_FLAG)
presence = os.path.exists(_PRESENCE_FLAG)
return manual, presence
def _refresh_caffeine(self) -> bool:
manual, presence = self._caffeine_state()
if manual:
icon = ICON_COFFEE
tip = "Caffeine: ON (manual + presence)" if presence else "Caffeine: ON (manual)"
elif presence:
icon = ICON_EYE
tip = "Awake: presence detected"
else:
icon = ICON_SLEEP
tip = "Idle: no inhibit"
self._caffeine_btn.set_label(icon)
self._caffeine_btn.set_tooltip_text(tip)
# Colour tracks presence (magenta), exactly like eww's .caffeine-presence;
# the glyph carries the manual/presence/idle distinction on its own.
if presence:
self._caffeine_btn.add_css_class("station-caffeine-active")
else:
self._caffeine_btn.remove_css_class("station-caffeine-active")
return True
def _on_caffeine_clicked(self, *_a) -> None:
subprocess.Popen(["bash", "-c", "$HOME/.config/scripts/caffeine.sh"],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
# caffeine.sh flips the lock synchronously; give it a beat, then reflect it.
GLib.timeout_add(250, self._refresh_caffeine_once)
def _refresh_caffeine_once(self) -> bool:
self._refresh_caffeine()
return False
def _refresh_clock(self) -> bool:
self._clock_label.set_label(time.strftime("%H:%M"))
return True

View File

@ -75,6 +75,13 @@ button.station-badge,
.station-clock { color: @glow_amber; text-shadow: 0 0 7px alpha(@glow_amber, 0.85); }
.station-volume:hover { text-shadow: 0 0 13px @glow_cyan; }
/* caffeine dim violet at rest (idle/manual), magenta glow while the webcam
* presence daemon is keeping the screen awake, mirroring eww's .caffeine-presence */
.station-caffeine { color: alpha(@glow_violet, 0.8); text-shadow: 0 0 7px alpha(@glow_violet, 0.55); }
.station-caffeine:hover { color: @glow_violet; text-shadow: 0 0 13px @glow_violet; }
.station-caffeine-active { color: @glow_magenta; text-shadow: 0 0 8px alpha(@glow_magenta, 0.9); }
.station-caffeine-active:hover { text-shadow: 0 0 14px @glow_magenta, 0 0 4px @glow_magenta; }
/* Orbit / Astro launchers — larger glowing icon glyphs */
.station-launcher {
font-size: 17pt;