feat: add Hyprland screensaver helper
This commit is contained in:
@@ -4,8 +4,14 @@ general {
|
||||
after_sleep_cmd = hyprctl dispatch dpms on
|
||||
}
|
||||
|
||||
listener {
|
||||
timeout = 600
|
||||
on-timeout = hypr-screensaver start
|
||||
on-resume = hypr-screensaver stop
|
||||
}
|
||||
|
||||
listener {
|
||||
timeout = 900
|
||||
on-timeout = hyprctl dispatch dpms off
|
||||
on-timeout = hypr-screensaver stop && hyprctl dispatch dpms off
|
||||
on-resume = hyprctl dispatch dpms on
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
module Main (main) where
|
||||
|
||||
import Control.Concurrent (threadDelay)
|
||||
import Control.Monad (when)
|
||||
import Control.Monad (void, when)
|
||||
import Control.Monad.IO.Class (MonadIO, liftIO)
|
||||
import Control.Monad.Trans.Reader (asks)
|
||||
import Data.Char (toLower)
|
||||
@@ -26,6 +26,7 @@ import qualified StatusNotifier.Tray as SNITray
|
||||
import System.Environment (lookupEnv)
|
||||
import System.Environment.XDG.BaseDir (getUserConfigFile)
|
||||
import System.Log.Logger (Priority (WARNING), rootLoggerName, setLevel, updateGlobalLogger)
|
||||
import System.Process (spawnCommand)
|
||||
import System.Taffybar (startTaffybar)
|
||||
import System.Taffybar.Context
|
||||
( Backend (BackendWayland, BackendX11),
|
||||
@@ -491,6 +492,34 @@ simplifiedScreenLockWidget =
|
||||
{ ScreenLock.screenLockIcon = T.pack "\xF023" <> " Lock"
|
||||
}
|
||||
|
||||
simplifiedScreensaverWidget :: TaffyIO Gtk.Widget
|
||||
simplifiedScreensaverWidget =
|
||||
liftIO $ do
|
||||
label <- Gtk.labelNew (Just (T.pack "\xF108" <> " Saver"))
|
||||
ebox <- Gtk.eventBoxNew
|
||||
Gtk.containerAdd ebox label
|
||||
_ <- widgetSetClassGI ebox "screensaver"
|
||||
Gtk.widgetSetTooltipText ebox (Just "Left click: toggle screensaver\nRight click: stop screensaver")
|
||||
void $ Gtk.onWidgetButtonPressEvent ebox $ \event -> do
|
||||
eventType <- Gdk.getEventButtonType event
|
||||
button <- Gdk.getEventButtonButton event
|
||||
if eventType /= Gdk.EventTypeButtonPress
|
||||
then return False
|
||||
else case button of
|
||||
1 -> do
|
||||
void $ spawnCommand "hypr-screensaver toggle >/dev/null 2>&1"
|
||||
return True
|
||||
3 -> do
|
||||
void $ spawnCommand "hypr-screensaver stop >/dev/null 2>&1"
|
||||
return True
|
||||
_ -> return False
|
||||
Gtk.widgetShowAll ebox
|
||||
Gtk.toWidget ebox
|
||||
|
||||
screensaverWidget :: TaffyIO Gtk.Widget
|
||||
screensaverWidget =
|
||||
decorateWithClassAndBoxM "screensaver" simplifiedScreensaverWidget
|
||||
|
||||
simplifiedWlsunsetWidget :: TaffyIO Gtk.Widget
|
||||
simplifiedWlsunsetWidget =
|
||||
-- Inner widget: no extra pill wrapping (the combiner provides that).
|
||||
@@ -584,6 +613,7 @@ endWidgetsForHost hostName =
|
||||
ramSwapWidget,
|
||||
diskUsageWidget,
|
||||
networkWidget,
|
||||
screensaverWidget,
|
||||
sunLockWidget,
|
||||
mprisWidget
|
||||
]
|
||||
@@ -594,6 +624,7 @@ endWidgetsForHost hostName =
|
||||
audioBacklightWidget,
|
||||
cpuWidget,
|
||||
ramSwapWidget,
|
||||
screensaverWidget,
|
||||
sunLockWidget,
|
||||
mprisWidget
|
||||
]
|
||||
|
||||
188
dotfiles/lib/bin/hypr-screensaver
Executable file
188
dotfiles/lib/bin/hypr-screensaver
Executable file
@@ -0,0 +1,188 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
script_path="$(readlink -f "${BASH_SOURCE[0]}")"
|
||||
state_dir="${XDG_RUNTIME_DIR:-/tmp}/hypr-screensaver"
|
||||
mkdir -p "$state_dir"
|
||||
|
||||
title_prefix="hypr-screensaver:"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: hypr-screensaver <start|stop|toggle|status|session>
|
||||
|
||||
Commands:
|
||||
start Launch the screensaver on every Hyprland monitor.
|
||||
stop Stop any running screensaver windows.
|
||||
toggle Start if stopped, otherwise stop.
|
||||
status Exit 0 if any screensaver window is running, otherwise exit 1.
|
||||
session Run the configured screensaver payload for one monitor.
|
||||
|
||||
The default payload is an mpv-rendered lavfi animation. You can override the
|
||||
source with HYPR_SCREENSAVER_SOURCE, for example:
|
||||
HYPR_SCREENSAVER_SOURCE='/path/to/video.mp4'
|
||||
HYPR_SCREENSAVER_SOURCE='av://lavfi:mandelbrot=s=2560x1440:r=60'
|
||||
EOF
|
||||
}
|
||||
|
||||
monitors_json() {
|
||||
hyprctl -j monitors
|
||||
}
|
||||
|
||||
monitor_names() {
|
||||
monitors_json | jq -r '.[].name'
|
||||
}
|
||||
|
||||
monitor_specs() {
|
||||
monitors_json | jq -c '.[] | { name, width, height }'
|
||||
}
|
||||
|
||||
focused_monitor() {
|
||||
monitors_json | jq -r '.[] | select(.focused) | .name'
|
||||
}
|
||||
|
||||
screensaver_window_pids() {
|
||||
hyprctl -j clients 2>/dev/null | jq -r --arg prefix "$title_prefix" '
|
||||
.[]
|
||||
| select((.title // "") | startswith($prefix))
|
||||
| .pid
|
||||
' | sort -u
|
||||
}
|
||||
|
||||
is_running() {
|
||||
local pid
|
||||
for pid in $(screensaver_window_pids); do
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
shopt -s nullglob
|
||||
local pid_file
|
||||
for pid_file in "$state_dir"/*.pid; do
|
||||
pid="$(<"$pid_file")"
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
default_source() {
|
||||
local width="$1"
|
||||
local height="$2"
|
||||
printf 'av://lavfi:life=s=%sx%s:r=60:mold=10:ratio=0.065:death_color=#101414:life_color=#7dd3fc:mold_color=#1e3a5f,format=yuv420p' \
|
||||
"$width" "$height"
|
||||
}
|
||||
|
||||
start() {
|
||||
local current_monitor spec monitor width height pid
|
||||
|
||||
if is_running; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
current_monitor="$(focused_monitor || true)"
|
||||
|
||||
while IFS= read -r spec; do
|
||||
monitor="$(jq -r '.name' <<<"$spec")"
|
||||
width="$(jq -r '.width' <<<"$spec")"
|
||||
height="$(jq -r '.height' <<<"$spec")"
|
||||
[ -n "$monitor" ] || continue
|
||||
HYPR_SCREENSAVER_MONITOR="$monitor" \
|
||||
HYPR_SCREENSAVER_WIDTH="$width" \
|
||||
HYPR_SCREENSAVER_HEIGHT="$height" \
|
||||
"$script_path" session >/dev/null 2>&1 &
|
||||
pid=$!
|
||||
printf '%s\n' "$pid" > "$state_dir/${monitor}.pid"
|
||||
sleep 0.15
|
||||
done < <(monitor_specs)
|
||||
|
||||
if [ -n "$current_monitor" ]; then
|
||||
hyprctl dispatch focusmonitor "$current_monitor" >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
stop() {
|
||||
local pid pid_file
|
||||
|
||||
for pid in $(screensaver_window_pids); do
|
||||
kill "$pid" >/dev/null 2>&1 || true
|
||||
done
|
||||
|
||||
shopt -s nullglob
|
||||
for pid_file in "$state_dir"/*.pid; do
|
||||
pid="$(<"$pid_file")"
|
||||
kill "$pid" >/dev/null 2>&1 || true
|
||||
rm -f "$pid_file"
|
||||
done
|
||||
}
|
||||
|
||||
session() {
|
||||
local monitor="${HYPR_SCREENSAVER_MONITOR:?missing HYPR_SCREENSAVER_MONITOR}"
|
||||
local width="${HYPR_SCREENSAVER_WIDTH:-1920}"
|
||||
local height="${HYPR_SCREENSAVER_HEIGHT:-1080}"
|
||||
local source="${HYPR_SCREENSAVER_SOURCE:-$(default_source "$width" "$height")}"
|
||||
local -a mpv_args=(
|
||||
--no-config
|
||||
--really-quiet
|
||||
--fullscreen
|
||||
--fs-screen-name="$monitor"
|
||||
--screen-name="$monitor"
|
||||
--force-window=immediate
|
||||
--border=no
|
||||
--title-bar=no
|
||||
--ontop
|
||||
--keep-open=yes
|
||||
--loop-file=inf
|
||||
--audio=no
|
||||
--osc=no
|
||||
--osd-level=0
|
||||
--input-default-bindings=no
|
||||
--wayland-app-id=hypr-screensaver
|
||||
--title="${title_prefix}${monitor}"
|
||||
--image-display-duration=inf
|
||||
"$source"
|
||||
)
|
||||
|
||||
if command -v mpv >/dev/null 2>&1; then
|
||||
exec mpv "${mpv_args[@]}"
|
||||
fi
|
||||
|
||||
exec nix shell nixpkgs#mpv --command mpv "${mpv_args[@]}"
|
||||
}
|
||||
|
||||
status() {
|
||||
is_running
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
toggle)
|
||||
if is_running; then
|
||||
stop
|
||||
else
|
||||
start
|
||||
fi
|
||||
;;
|
||||
status)
|
||||
status
|
||||
;;
|
||||
session)
|
||||
session
|
||||
;;
|
||||
""|-h|--help|help)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
@@ -126,6 +126,7 @@ makeEnable config "myModules.hyprland" true {
|
||||
slurp # Region selection
|
||||
swappy # Screenshot annotation
|
||||
nwg-displays # GUI monitor arrangement
|
||||
mpv # Graphical screensaver payload
|
||||
|
||||
# hy3 plugin from flake (properly built against matching Hyprland)
|
||||
inputs.hy3.packages.${pkgs.stdenv.hostPlatform.system}.hy3
|
||||
|
||||
Reference in New Issue
Block a user