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
parent
fa78fc9a66
commit
f94a825c82
|
|
@ -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 |
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue