Generate managed lock screen configuration
This commit is contained in:
@@ -748,6 +748,40 @@ Singleton {
|
||||
detail: "Shown on every output"
|
||||
},
|
||||
|
||||
// ── Lock-screen appearance ─────────────────────────────────────────
|
||||
// scripts/panama-lock validates these again before generating a state
|
||||
// config. The tracked hyprlock.conf remains the safe fallback.
|
||||
{
|
||||
key: "lockBackgroundMode", type: "enum", def: "screenshot", group: "lockAppearance",
|
||||
label: "Background", detail: "What appears behind the lock screen",
|
||||
options: [
|
||||
{ value: "screenshot", label: "Blurred desktop" },
|
||||
{ value: "wallpaper", label: "Current wallpaper" },
|
||||
{ value: "solid", label: "Solid color" }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "lockBlurLevel", type: "int", def: 3, min: 0, max: 5, step: 1,
|
||||
group: "lockAppearance", label: "Background blur",
|
||||
detail: "Softens what is behind the password field"
|
||||
},
|
||||
{
|
||||
key: "lockShowClock", type: "bool", def: true, group: "lockAppearance",
|
||||
label: "Show clock", detail: "Use the desktop's 12 or 24-hour format"
|
||||
},
|
||||
{
|
||||
key: "lockShowDate", type: "bool", def: true, group: "lockAppearance",
|
||||
label: "Show date", detail: "Show the weekday and full date"
|
||||
},
|
||||
{
|
||||
key: "lockShowUser", type: "bool", def: true, group: "lockAppearance",
|
||||
label: "Show user name", detail: "Identify the signed-in account"
|
||||
},
|
||||
{
|
||||
key: "lockFadeOnEmpty", type: "bool", def: false, group: "lockAppearance",
|
||||
label: "Hide password field until typing", detail: "Keep the empty field out of the way"
|
||||
},
|
||||
|
||||
// ── Idle, lock, and sleep ───────────────────────────────────────────
|
||||
// Written into a generated hypridle config; see scripts/panama-idle.
|
||||
// Zero means never for all three.
|
||||
|
||||
Executable
+336
@@ -0,0 +1,336 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Generates Panama's hyprlock configuration into the state directory. The
|
||||
# tracked config is never rewritten and remains the fallback if generation
|
||||
# fails, so a malformed preference can never leave the session without a
|
||||
# working locker.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
|
||||
state_home="${XDG_STATE_HOME:-$HOME/.local/state}"
|
||||
settings="$config_home/panama/settings.json"
|
||||
state_dir="$state_home/panama"
|
||||
generated="$state_dir/hyprlock.conf"
|
||||
status_file="$state_dir/hyprlock-status.json"
|
||||
fallback="$config_home/hypr/hyprlock.conf"
|
||||
temporary="$generated.tmp.$$"
|
||||
status_temporary="$status_file.tmp.$$"
|
||||
|
||||
settings_valid=false
|
||||
|
||||
cleanup() {
|
||||
rm -f "$temporary" "$status_temporary" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
read_string() {
|
||||
local key="$1" default="$2" value
|
||||
if [[ "$settings_valid" != true ]]; then
|
||||
printf '%s' "$default"
|
||||
return
|
||||
fi
|
||||
value="$(jq -er --arg key "$key" \
|
||||
'if has($key) and (.[$key] | type) == "string" then .[$key] else empty end' \
|
||||
"$settings" 2>/dev/null)" || value="$default"
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
read_bool() {
|
||||
local key="$1" default="$2" value
|
||||
if [[ "$settings_valid" != true ]]; then
|
||||
printf '%s' "$default"
|
||||
return
|
||||
fi
|
||||
value="$(jq -er --arg key "$key" \
|
||||
'if has($key) and (.[$key] | type) == "boolean" then (.[$key] | tostring) else empty end' \
|
||||
"$settings" 2>/dev/null)" || value="$default"
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
read_int() {
|
||||
local key="$1" default="$2" low="$3" high="$4" value
|
||||
if [[ "$settings_valid" != true ]]; then
|
||||
printf '%s' "$default"
|
||||
return
|
||||
fi
|
||||
value="$(jq -er --arg key "$key" \
|
||||
'if has($key) and (.[$key] | type) == "number" and (.[$key] | floor) == .[$key]
|
||||
then (.[$key] | tostring) else empty end' "$settings" 2>/dev/null)" || value="$default"
|
||||
if [[ ! "$value" =~ ^-?[0-9]+$ ]] || (( value < low || value > high )); then
|
||||
value="$default"
|
||||
fi
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
read_object() {
|
||||
local key="$1"
|
||||
if [[ "$settings_valid" != true ]]; then
|
||||
printf '{}'
|
||||
return
|
||||
fi
|
||||
jq -c --arg key "$key" \
|
||||
'if has($key) and (.[$key] | type) == "object" then .[$key] else {} end' \
|
||||
"$settings" 2>/dev/null || printf '{}'
|
||||
}
|
||||
|
||||
valid_path() {
|
||||
[[ "$1" == /* && "$1" != *","* && "$1" != *$'\n'* ]]
|
||||
}
|
||||
|
||||
load_preferences() {
|
||||
if [[ -r "$settings" ]] && jq -e 'type == "object"' "$settings" >/dev/null 2>&1; then
|
||||
settings_valid=true
|
||||
else
|
||||
settings_valid=false
|
||||
fi
|
||||
|
||||
background_mode="$(read_string lockBackgroundMode screenshot)"
|
||||
[[ "$background_mode" == screenshot || "$background_mode" == wallpaper || "$background_mode" == solid ]] \
|
||||
|| background_mode=screenshot
|
||||
|
||||
blur_level="$(read_int lockBlurLevel 3 0 5)"
|
||||
show_clock="$(read_bool lockShowClock true)"
|
||||
show_date="$(read_bool lockShowDate true)"
|
||||
show_user="$(read_bool lockShowUser true)"
|
||||
fade_on_empty="$(read_bool lockFadeOnEmpty false)"
|
||||
use_24_hour="$(read_bool use24Hour false)"
|
||||
|
||||
color_scheme="$(read_string colorScheme dark)"
|
||||
[[ "$color_scheme" == dark || "$color_scheme" == light ]] || color_scheme=dark
|
||||
|
||||
wallpaper_mode="$(read_string wallpaperMode single)"
|
||||
[[ "$wallpaper_mode" == single || "$wallpaper_mode" == slideshow || "$wallpaper_mode" == per-monitor ]] \
|
||||
|| wallpaper_mode=single
|
||||
wallpaper_path="$(read_string wallpaperPath '')"
|
||||
if ! valid_path "$wallpaper_path"; then
|
||||
wallpaper_path="$HOME/Pictures/Wallpapers/faroe_islands.jpg"
|
||||
fi
|
||||
wallpaper_assignments="$(read_object wallpaperPerMonitor)"
|
||||
|
||||
case "$blur_level" in
|
||||
0) blur_passes=0; blur_size=1 ;;
|
||||
1) blur_passes=1; blur_size=3 ;;
|
||||
2) blur_passes=2; blur_size=5 ;;
|
||||
3) blur_passes=3; blur_size=8 ;;
|
||||
4) blur_passes=4; blur_size=10 ;;
|
||||
5) blur_passes=5; blur_size=12 ;;
|
||||
esac
|
||||
|
||||
if [[ "$color_scheme" == light ]]; then
|
||||
background_color='rgba(245, 246, 250, 1.0)'
|
||||
foreground_color='rgba(55, 63, 87, 1.0)'
|
||||
dim_color='rgba(111, 119, 151, 1.0)'
|
||||
field_color='rgba(220, 223, 232, 0.88)'
|
||||
else
|
||||
background_color='rgba(34, 36, 54, 1.0)'
|
||||
foreground_color='rgba(200, 211, 245, 1.0)'
|
||||
dim_color='rgba(130, 139, 184, 1.0)'
|
||||
field_color='rgba(46, 47, 61, 0.85)'
|
||||
fi
|
||||
}
|
||||
|
||||
monitor_names() {
|
||||
hyprctl -j monitors 2>/dev/null \
|
||||
| jq -r '.[]? | .name | select(type == "string") | select(test("^[A-Za-z0-9_.-]+$"))' \
|
||||
2>/dev/null || true
|
||||
}
|
||||
|
||||
emit_background() {
|
||||
local monitor="$1" path="$2"
|
||||
printf 'background {\n'
|
||||
printf ' monitor = %s\n' "$monitor"
|
||||
if [[ -n "$path" ]]; then
|
||||
printf ' path = %s\n' "$path"
|
||||
fi
|
||||
printf ' blur_passes = %s\n' "$blur_passes"
|
||||
printf ' blur_size = %s\n' "$blur_size"
|
||||
printf ' noise = 0.0117\n'
|
||||
printf ' contrast = 0.9\n'
|
||||
printf ' brightness = 0.8\n'
|
||||
printf ' vibrancy = 0.17\n'
|
||||
printf ' vibrancy_darkness = 0.05\n'
|
||||
printf ' color = %s\n' "$background_color"
|
||||
printf ' zindex = -1\n'
|
||||
printf '}\n\n'
|
||||
}
|
||||
|
||||
emit_backgrounds() {
|
||||
local output assigned saw_output=false
|
||||
case "$background_mode" in
|
||||
screenshot)
|
||||
emit_background '' screenshot
|
||||
;;
|
||||
solid)
|
||||
emit_background '' ''
|
||||
;;
|
||||
wallpaper)
|
||||
while IFS= read -r output; do
|
||||
[[ -n "$output" ]] || continue
|
||||
saw_output=true
|
||||
assigned="$wallpaper_path"
|
||||
if [[ "$wallpaper_mode" == per-monitor ]]; then
|
||||
candidate="$(jq -r --arg output "$output" \
|
||||
'if has($output) and (.[$output] | type) == "string" then .[$output] else "" end' \
|
||||
<<<"$wallpaper_assignments" 2>/dev/null || true)"
|
||||
if valid_path "$candidate"; then
|
||||
assigned="$candidate"
|
||||
fi
|
||||
fi
|
||||
emit_background "$output" "$assigned"
|
||||
done < <(monitor_names)
|
||||
if [[ "$saw_output" != true ]]; then
|
||||
emit_background '' "$wallpaper_path"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
emit_config() {
|
||||
printf '# Generated by Panama. Do not edit.\n\n'
|
||||
printf 'general {\n'
|
||||
printf ' hide_cursor = true\n'
|
||||
printf ' fractional_scaling = 2\n'
|
||||
printf ' screencopy_mode = 0\n'
|
||||
printf ' fail_timeout = 2000\n'
|
||||
printf '}\n\n'
|
||||
printf 'auth {\n'
|
||||
printf ' pam:enabled = true\n'
|
||||
printf ' pam:module = hyprlock\n'
|
||||
printf '}\n\n'
|
||||
|
||||
emit_backgrounds
|
||||
|
||||
if [[ "$show_clock" == true ]]; then
|
||||
printf 'label {\n'
|
||||
printf ' monitor =\n'
|
||||
if [[ "$use_24_hour" == true ]]; then
|
||||
printf ' text = cmd[update:1000] date +"%%H:%%M"\n'
|
||||
else
|
||||
printf ' text = cmd[update:1000] date +"%%-I:%%M"\n'
|
||||
fi
|
||||
printf ' color = %s\n' "$foreground_color"
|
||||
printf ' font_size = 120\n'
|
||||
printf ' font_family = Adwaita Sans Light\n'
|
||||
printf ' position = 0, 260\n'
|
||||
printf ' halign = center\n'
|
||||
printf ' valign = center\n'
|
||||
printf '}\n\n'
|
||||
fi
|
||||
|
||||
if [[ "$show_date" == true ]]; then
|
||||
printf 'label {\n'
|
||||
printf ' monitor =\n'
|
||||
printf ' text = cmd[update:60000] date +"%%A, %%B %%-d"\n'
|
||||
printf ' color = %s\n' "$dim_color"
|
||||
printf ' font_size = 24\n'
|
||||
printf ' font_family = Adwaita Sans\n'
|
||||
printf ' position = 0, 160\n'
|
||||
printf ' halign = center\n'
|
||||
printf ' valign = center\n'
|
||||
printf '}\n\n'
|
||||
fi
|
||||
|
||||
printf 'input-field {\n'
|
||||
printf ' monitor =\n'
|
||||
printf ' size = 340, 52\n'
|
||||
printf ' position = 0, -40\n'
|
||||
printf ' halign = center\n'
|
||||
printf ' valign = center\n'
|
||||
printf ' outline_thickness = 2\n'
|
||||
printf ' rounding = 26\n'
|
||||
printf ' outer_color = rgba(130, 170, 255, 0.9)\n'
|
||||
printf ' inner_color = %s\n' "$field_color"
|
||||
printf ' font_color = %s\n' "$foreground_color"
|
||||
printf ' check_color = rgba(130, 170, 255, 1.0)\n'
|
||||
printf ' fail_color = rgba(255, 117, 127, 1.0)\n'
|
||||
printf ' dots_size = 0.25\n'
|
||||
printf ' dots_spacing = 0.3\n'
|
||||
printf ' dots_center = true\n'
|
||||
printf ' placeholder_text = <span foreground="##828bb8"><i>Password</i></span>\n'
|
||||
printf ' fail_text = <span foreground="##ff757f"><i>$FAIL ($ATTEMPTS)</i></span>\n'
|
||||
printf ' fade_on_empty = %s\n' "$fade_on_empty"
|
||||
printf ' hide_input = false\n'
|
||||
printf '}\n\n'
|
||||
|
||||
if [[ "$show_user" == true ]]; then
|
||||
printf 'label {\n'
|
||||
printf ' monitor =\n'
|
||||
printf ' text = $USER\n'
|
||||
printf ' color = %s\n' "$foreground_color"
|
||||
printf ' font_size = 16\n'
|
||||
printf ' font_family = Adwaita Sans\n'
|
||||
printf ' position = 0, -110\n'
|
||||
printf ' halign = center\n'
|
||||
printf ' valign = center\n'
|
||||
printf '}\n'
|
||||
fi
|
||||
}
|
||||
|
||||
write_status() {
|
||||
local generated_value="$1" path_value="$2" fallback_value="$3" error_value="$4"
|
||||
mkdir -p "$state_dir" 2>/dev/null || return 0
|
||||
[[ -w "$state_dir" ]] || return 0
|
||||
jq -nc --argjson generated "$generated_value" --arg path "$path_value" \
|
||||
--argjson fallback "$fallback_value" --arg error "$error_value" \
|
||||
'{generated:$generated,path:$path,fallback:$fallback,error:$error}' \
|
||||
>"$status_temporary" 2>/dev/null || return 0
|
||||
mv "$status_temporary" "$status_file" 2>/dev/null || true
|
||||
}
|
||||
|
||||
generate() {
|
||||
load_preferences
|
||||
mkdir -p "$state_dir" || return 1
|
||||
if ! emit_config >"$temporary"; then
|
||||
rm -f "$temporary" 2>/dev/null || true
|
||||
write_status false "$fallback" true "The lock-screen configuration could not be generated."
|
||||
return 1
|
||||
fi
|
||||
if [[ ! -s "$temporary" ]] \
|
||||
|| ! rg -q '^auth \{' "$temporary" \
|
||||
|| ! rg -q '^background \{' "$temporary" \
|
||||
|| ! rg -q '^input-field \{' "$temporary"; then
|
||||
rm -f "$temporary"
|
||||
write_status false "$fallback" true "The lock-screen configuration could not be generated."
|
||||
return 1
|
||||
fi
|
||||
if ! mv "$temporary" "$generated"; then
|
||||
rm -f "$temporary" 2>/dev/null || true
|
||||
write_status false "$fallback" true "The lock-screen configuration could not be generated."
|
||||
return 1
|
||||
fi
|
||||
write_status true "$generated" false ""
|
||||
}
|
||||
|
||||
status() {
|
||||
if [[ -r "$status_file" ]] \
|
||||
&& jq -e '.generated | type == "boolean"' "$status_file" >/dev/null 2>&1 \
|
||||
&& jq -e '.path | type == "string"' "$status_file" >/dev/null 2>&1; then
|
||||
jq -c '{generated,path,fallback:(.fallback == true),error:(.error // "")}' "$status_file"
|
||||
elif [[ -s "$generated" ]]; then
|
||||
jq -nc --arg path "$generated" '{generated:true,path:$path,fallback:false,error:""}'
|
||||
else
|
||||
jq -nc --arg path "$fallback" \
|
||||
'{generated:false,path:$path,fallback:true,error:"The generated lock-screen configuration is unavailable."}'
|
||||
fi
|
||||
}
|
||||
|
||||
case "${1:-status}" in
|
||||
generate)
|
||||
generate
|
||||
;;
|
||||
status)
|
||||
status
|
||||
;;
|
||||
run)
|
||||
if generate >/dev/null 2>&1; then
|
||||
exec hyprlock -c "$generated"
|
||||
fi
|
||||
write_status false "$fallback" true "The lock-screen configuration could not be generated."
|
||||
exec hyprlock -c "$fallback"
|
||||
;;
|
||||
*)
|
||||
printf 'usage: panama-lock [generate|status|run]\n' >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user