feat: add Hyprland screensaver helper
This commit is contained in:
@@ -4,8 +4,14 @@ general {
|
|||||||
after_sleep_cmd = hyprctl dispatch dpms on
|
after_sleep_cmd = hyprctl dispatch dpms on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listener {
|
||||||
|
timeout = 600
|
||||||
|
on-timeout = hypr-screensaver start
|
||||||
|
on-resume = hypr-screensaver stop
|
||||||
|
}
|
||||||
|
|
||||||
listener {
|
listener {
|
||||||
timeout = 900
|
timeout = 900
|
||||||
on-timeout = hyprctl dispatch dpms off
|
on-timeout = hypr-screensaver stop && hyprctl dispatch dpms off
|
||||||
on-resume = hyprctl dispatch dpms on
|
on-resume = hyprctl dispatch dpms on
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
module Main (main) where
|
module Main (main) where
|
||||||
|
|
||||||
import Control.Concurrent (threadDelay)
|
import Control.Concurrent (threadDelay)
|
||||||
import Control.Monad (when)
|
import Control.Monad (void, when)
|
||||||
import Control.Monad.IO.Class (MonadIO, liftIO)
|
import Control.Monad.IO.Class (MonadIO, liftIO)
|
||||||
import Control.Monad.Trans.Reader (asks)
|
import Control.Monad.Trans.Reader (asks)
|
||||||
import Data.Char (toLower)
|
import Data.Char (toLower)
|
||||||
@@ -26,6 +26,7 @@ import qualified StatusNotifier.Tray as SNITray
|
|||||||
import System.Environment (lookupEnv)
|
import System.Environment (lookupEnv)
|
||||||
import System.Environment.XDG.BaseDir (getUserConfigFile)
|
import System.Environment.XDG.BaseDir (getUserConfigFile)
|
||||||
import System.Log.Logger (Priority (WARNING), rootLoggerName, setLevel, updateGlobalLogger)
|
import System.Log.Logger (Priority (WARNING), rootLoggerName, setLevel, updateGlobalLogger)
|
||||||
|
import System.Process (spawnCommand)
|
||||||
import System.Taffybar (startTaffybar)
|
import System.Taffybar (startTaffybar)
|
||||||
import System.Taffybar.Context
|
import System.Taffybar.Context
|
||||||
( Backend (BackendWayland, BackendX11),
|
( Backend (BackendWayland, BackendX11),
|
||||||
@@ -491,6 +492,34 @@ simplifiedScreenLockWidget =
|
|||||||
{ ScreenLock.screenLockIcon = T.pack "\xF023" <> " Lock"
|
{ 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 :: TaffyIO Gtk.Widget
|
||||||
simplifiedWlsunsetWidget =
|
simplifiedWlsunsetWidget =
|
||||||
-- Inner widget: no extra pill wrapping (the combiner provides that).
|
-- Inner widget: no extra pill wrapping (the combiner provides that).
|
||||||
@@ -584,6 +613,7 @@ endWidgetsForHost hostName =
|
|||||||
ramSwapWidget,
|
ramSwapWidget,
|
||||||
diskUsageWidget,
|
diskUsageWidget,
|
||||||
networkWidget,
|
networkWidget,
|
||||||
|
screensaverWidget,
|
||||||
sunLockWidget,
|
sunLockWidget,
|
||||||
mprisWidget
|
mprisWidget
|
||||||
]
|
]
|
||||||
@@ -594,6 +624,7 @@ endWidgetsForHost hostName =
|
|||||||
audioBacklightWidget,
|
audioBacklightWidget,
|
||||||
cpuWidget,
|
cpuWidget,
|
||||||
ramSwapWidget,
|
ramSwapWidget,
|
||||||
|
screensaverWidget,
|
||||||
sunLockWidget,
|
sunLockWidget,
|
||||||
mprisWidget
|
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
|
slurp # Region selection
|
||||||
swappy # Screenshot annotation
|
swappy # Screenshot annotation
|
||||||
nwg-displays # GUI monitor arrangement
|
nwg-displays # GUI monitor arrangement
|
||||||
|
mpv # Graphical screensaver payload
|
||||||
|
|
||||||
# hy3 plugin from flake (properly built against matching Hyprland)
|
# hy3 plugin from flake (properly built against matching Hyprland)
|
||||||
inputs.hy3.packages.${pkgs.stdenv.hostPlatform.system}.hy3
|
inputs.hy3.packages.${pkgs.stdenv.hostPlatform.system}.hy3
|
||||||
|
|||||||
Reference in New Issue
Block a user