hypr: unify lockscreen and login screen via hyprlogin
Replace nwg-hello with hyprlogin as the greetd greeter. Both hyprlock and hyprlogin now share a single apex-neon theme fragment (.chezmoitemplates/apex-neon-lock.conf), giving an identical look on lock and login. - Add system/ staging tree for /etc files (not auto-applied by chezmoi) - Add system/install-greeter.sh to render templates and sudo-install to /etc - Add apex-neon shared fragment: palette, font, animations, input-field, clock/date bubbles, weather/location bubbles - Add dot_config/hypr/themes/apex-neon-lock.conf.tmpl ($HOME copy for hyprlock) - Rewrite hyprlock.conf.tmpl to source the shared fragment; move notification bubble here (hyprlock-only, requires quickshell) - Add Quickshell IpcHandler so `qs ipc call notifications count` works - Session fixed to hyprland-uwsm.desktop; greetd config targets greetd.conf
This commit is contained in:
@@ -37,6 +37,9 @@
|
||||
|
||||
pkglist/
|
||||
|
||||
# system/ contains files staged for /etc — installed manually via system/install-greeter.sh
|
||||
system/
|
||||
|
||||
AGENTS.md
|
||||
GEMINI.md
|
||||
CLAUDE.md
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
{{/*
|
||||
Shared apex-neon look for hyprlock + hyprlogin.
|
||||
|
||||
Hyprlang fragment. Sourced by:
|
||||
- ~/.config/hypr/themes/apex-neon-lock.conf (hyprlock, in $HOME)
|
||||
- /etc/hyprlogin/apex-neon.conf (hyprlogin, installed by system/install-greeter.sh)
|
||||
|
||||
Defines: apex-neon palette, font, input-field, clock + date labels.
|
||||
Greeter-only widgets (session picker, layout, username placeholder) stay in hyprlogin.conf.
|
||||
*/ -}}
|
||||
# apex-neon palette (mirrors ~/Dev/Themes/apex/dist/hyprland/apex-neon-colors.conf)
|
||||
$base = rgb(050505)
|
||||
$surface = rgb(141414)
|
||||
$overlay = rgb(262626)
|
||||
$muted = rgb(404040)
|
||||
$text = rgb(ededed)
|
||||
$love = rgb(ff0044)
|
||||
$gold = rgb(ffb700)
|
||||
$pine = rgb(00ff99)
|
||||
$foam = rgb(00eaff)
|
||||
$iris = rgb(9d00ff)
|
||||
|
||||
$font = GeistMono Nerd Font
|
||||
|
||||
animations {
|
||||
enabled = true
|
||||
bezier = linear, 1, 1, 0, 0
|
||||
animation = fadeIn, 1, 5, linear
|
||||
animation = fadeOut, 1, 5, linear
|
||||
animation = inputFieldDots, 1, 2, linear
|
||||
}
|
||||
|
||||
# input — bottom center
|
||||
input-field {
|
||||
monitor =
|
||||
size = 360, 64
|
||||
outline_thickness = 2
|
||||
dots_size = 0.22
|
||||
dots_spacing = 0.35
|
||||
dots_center = true
|
||||
|
||||
inner_color = rgba(05, 05, 05, 0.55)
|
||||
outer_color = rgba(00, 234, 255, 0.85) rgba(157, 0, 255, 0.85) 45deg
|
||||
check_color = rgba(0, 255, 153, 0.9) rgba(0, 234, 255, 0.9) 45deg
|
||||
fail_color = rgba(255, 0, 68, 0.9) rgba(255, 136, 153, 0.9) 45deg
|
||||
|
||||
font_color = rgba(237, 237, 237, 0.95)
|
||||
fade_on_empty = false
|
||||
rounding = 12
|
||||
|
||||
font_family = $font
|
||||
placeholder_text = <i>password</i>
|
||||
|
||||
position = 0, 120
|
||||
halign = center
|
||||
valign = bottom
|
||||
}
|
||||
|
||||
# --- TOP-LEFT: two stacked bubbles ---
|
||||
# For halign=left valign=top, position = top-left corner of the widget (relative
|
||||
# to screen top-left). Shapes and labels share the same convention.
|
||||
|
||||
# ── bubble 1: clock + date ──
|
||||
shape {
|
||||
monitor =
|
||||
size = 440, 200
|
||||
color = rgba(5, 5, 5, 0.55)
|
||||
rounding = 20
|
||||
border_size = 2
|
||||
border_color = rgba(0, 234, 255, 0.55)
|
||||
position = 40, -50
|
||||
halign = left
|
||||
valign = top
|
||||
z_index = -1
|
||||
}
|
||||
|
||||
label {
|
||||
monitor =
|
||||
text = cmd[update:1000] date +"%-H:%M"
|
||||
color = rgba(237, 237, 237, 0.95)
|
||||
font_size = 80
|
||||
font_family = $font
|
||||
position = 100, -70
|
||||
halign = left
|
||||
valign = top
|
||||
}
|
||||
|
||||
label {
|
||||
monitor =
|
||||
text = cmd[update:60000] date +"%A, %B %-d"
|
||||
color = rgba(0, 234, 255, 0.85)
|
||||
font_size = 20
|
||||
font_family = $font
|
||||
position = 130, -180
|
||||
halign = left
|
||||
valign = top
|
||||
}
|
||||
|
||||
# ── bubble 2: weather + location ──
|
||||
shape {
|
||||
monitor =
|
||||
size = 360, 110
|
||||
color = rgba(5, 5, 5, 0.55)
|
||||
rounding = 18
|
||||
border_size = 2
|
||||
border_color = rgba(255, 183, 0, 0.55)
|
||||
position = 40, -280
|
||||
halign = left
|
||||
valign = top
|
||||
z_index = -1
|
||||
}
|
||||
|
||||
# weather: condition + temp. wttr.in geo-locates by IP; silent on failure.
|
||||
label {
|
||||
monitor =
|
||||
text = cmd[update:1800000] curl -fsS --max-time 3 'wttr.in/?format=%c+%t' 2>/dev/null || echo " "
|
||||
color = rgba(255, 183, 0, 0.95)
|
||||
font_size = 22
|
||||
font_family = $font
|
||||
position = 130, -305
|
||||
halign = left
|
||||
valign = top
|
||||
}
|
||||
|
||||
# location: trim wttr's "City, District, Country" to just the city.
|
||||
label {
|
||||
monitor =
|
||||
text = cmd[update:1800000] curl -fsS --max-time 3 'wttr.in/?format=%l' 2>/dev/null | cut -d',' -f1 | sed 's/^./\U&/' || echo " "
|
||||
color = rgba(157, 0, 255, 0.85)
|
||||
font_size = 14
|
||||
font_family = $font
|
||||
position = 185, -348
|
||||
halign = left
|
||||
valign = top
|
||||
}
|
||||
@@ -1,70 +1,45 @@
|
||||
{{- $lock_bg := (index .chezmoi.config.data "lockscreen-wallpaper") -}}
|
||||
{{- if not $lock_bg -}}
|
||||
{{- $lock_bg = "~/.config/wallpaper/lockscreen/current" -}}
|
||||
{{- $fallback_bg := (index .chezmoi.config.data "lockscreen-wallpaper") -}}
|
||||
{{- if not $fallback_bg -}}
|
||||
{{- $fallback_bg = "~/.config/wallpaper/lockscreen/current" -}}
|
||||
{{- end -}}
|
||||
# Shared look (palette, font, input-field, clock, date) comes from the sourced fragment.
|
||||
# Same fragment is installed to /etc/hyprlogin/apex-neon.conf and used by hyprlogin.
|
||||
source = ~/.config/hypr/themes/apex-neon-lock.conf
|
||||
|
||||
general {
|
||||
hide_cursor = true
|
||||
ignore_empty_input = true
|
||||
}
|
||||
|
||||
#auth {
|
||||
# just leave the defaults
|
||||
#}
|
||||
|
||||
##################
|
||||
### BACKGROUND ###
|
||||
##################
|
||||
{{ range .monitors -}}
|
||||
{{- $wp := index . "lockscreen-wallpaper" -}}
|
||||
background {
|
||||
monitor =
|
||||
path = {{ $lock_bg }}
|
||||
monitor = {{ .name }}
|
||||
path = {{ if $wp }}{{ $wp }}{{ else }}{{ $fallback_bg }}{{ end }}
|
||||
}
|
||||
{{ end -}}
|
||||
|
||||
# --- TOP-RIGHT: notification bubble (hyprlock-only; requires quickshell) ---
|
||||
shape {
|
||||
monitor =
|
||||
size = 130, 52
|
||||
color = rgba(5, 5, 5, 0.55)
|
||||
rounding = 16
|
||||
border_size = 2
|
||||
border_color = rgba(255, 0, 68, 0.55)
|
||||
position = -100, -76
|
||||
halign = right
|
||||
valign = top
|
||||
z_index = -1
|
||||
}
|
||||
|
||||
#############
|
||||
### INPUT ###
|
||||
#############
|
||||
input-field {
|
||||
size = 250, 60
|
||||
outline_thickness = 2
|
||||
dots_size = 0.2 # Scale of input-field height, 0.2 - 0.8
|
||||
dots_spacing = 0.35 # Scale of dots' absolute size, 0.0 - 1.0
|
||||
dots_center = true
|
||||
outer_color = rgba(0, 0, 0, 0)
|
||||
inner_color = rgba(0, 0, 0, 0.2)
|
||||
font_color = rgba(255, 0, 132, 0.8)
|
||||
fade_on_empty = false
|
||||
rounding = -1
|
||||
check_color = rgb(204, 136, 34)
|
||||
placeholder_text =
|
||||
hide_input = false
|
||||
position = 0, -200
|
||||
halign = center
|
||||
valign = center
|
||||
}
|
||||
|
||||
############
|
||||
### DATA ###
|
||||
############
|
||||
label {
|
||||
monitor =
|
||||
text = cmd[update:1000] echo "$(date +"%A, %B %d")"
|
||||
color = rgba(242, 243, 244, 0.75)
|
||||
font_size = 22
|
||||
font_family = GeistMono Nerd Font
|
||||
position = 0, 300
|
||||
halign = center
|
||||
valign = center
|
||||
}
|
||||
|
||||
############
|
||||
### TIME ###
|
||||
############
|
||||
label {
|
||||
monitor =
|
||||
text = cmd[update:1000] echo "$(date +"%-I:%M")"
|
||||
color = rgba(242, 243, 244, 0.75)
|
||||
font_size = 95
|
||||
font_family = GeistMono Nerd Font
|
||||
position = 0, 200
|
||||
halign = center
|
||||
valign = center
|
||||
monitor =
|
||||
text = cmd[update:5000] n=$(qs ipc call notifications count 2>/dev/null); { [ -n "$n" ] && [ "$n" != "0" ] && printf ' %s' "$n"; } || true
|
||||
color = rgba(255, 0, 68, 0.95)
|
||||
font_size = 20
|
||||
font_family = GeistMono Nerd Font
|
||||
position = -125, -86
|
||||
halign = right
|
||||
valign = top
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{{ template "apex-neon-lock.conf" . }}
|
||||
@@ -1,4 +1,5 @@
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Notifications
|
||||
import Quickshell.Wayland
|
||||
import QtQuick
|
||||
@@ -12,6 +13,12 @@ Scope {
|
||||
property alias trackedNotifications: server.trackedNotifications
|
||||
property bool dnd: false
|
||||
|
||||
// External access (used by hyprlock notification widget via `qs ipc call notifications count`)
|
||||
IpcHandler {
|
||||
target: "notifications"
|
||||
function count(): int { return server.trackedNotifications.values.length }
|
||||
}
|
||||
|
||||
// Toast IDs currently showing as popups (capped to avoid overflow)
|
||||
readonly property int maxToasts: 4
|
||||
property var toastIds: []
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# /etc/greetd/config.toml — installed from chezmoi via system/install-greeter.sh
|
||||
[terminal]
|
||||
vt = 1
|
||||
|
||||
[default_session]
|
||||
command = "start-hyprland -- --config /etc/hyprlogin/hyprland-greeter.conf"
|
||||
user = "greeter"
|
||||
|
||||
[commands]
|
||||
reboot = ["systemctl", "reboot"]
|
||||
poweroff = ["systemctl", "poweroff"]
|
||||
@@ -0,0 +1 @@
|
||||
{{ template "apex-neon-lock.conf" . }}
|
||||
@@ -0,0 +1,34 @@
|
||||
# Hyprland compositor config for the greeter session (run as user `greeter`)
|
||||
# Installed to /etc/hyprlogin/hyprland-greeter.conf by system/install-greeter.sh.
|
||||
|
||||
{{ range .monitors -}}
|
||||
monitorv2 {
|
||||
output = {{ .name }}
|
||||
mode = {{ .width }}x{{ .height }}@{{ .refresh_rate }}
|
||||
position = {{ .position }}
|
||||
scale = {{ .scale }}
|
||||
{{- if hasKey . "transform" }}
|
||||
transform = {{ .transform }}
|
||||
{{- end }}
|
||||
{{- if hasKey . "vrr" }}
|
||||
vrr = {{ .vrr }}
|
||||
{{- end }}
|
||||
}
|
||||
{{ end }}
|
||||
exec-once = hyprlogin
|
||||
|
||||
misc {
|
||||
disable_hyprland_logo = true
|
||||
disable_splash_rendering = true
|
||||
}
|
||||
|
||||
animations {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
input {
|
||||
kb_layout = us,de
|
||||
kb_options = grp:alt_shift_toggle
|
||||
}
|
||||
|
||||
bind = ALT, Q, killactive,
|
||||
@@ -0,0 +1,62 @@
|
||||
# /etc/hyprlogin/hyprlogin.conf — installed from chezmoi via system/install-greeter.sh
|
||||
# Shared look (palette, font, input-field, clock, date) comes from the sourced fragment.
|
||||
source = /etc/hyprlogin/apex-neon.conf
|
||||
|
||||
general {
|
||||
hide_cursor = false
|
||||
immediate_render = true
|
||||
exit_command = hyprctl dispatch exit
|
||||
fail_timeout = 4000
|
||||
debug_mode = false
|
||||
debug_log_path = /tmp/hyprlogin-debug.log
|
||||
}
|
||||
|
||||
sessions {
|
||||
default_user = {{ .chezmoi.username }}
|
||||
default_session = hyprland-uwsm.desktop
|
||||
}
|
||||
|
||||
{{ range .monitors -}}
|
||||
{{- $wp := index . "lockscreen-wallpaper" -}}
|
||||
{{- if $wp -}}
|
||||
{{- $ext := $wp | regexFind "\\.[^.]+$" -}}
|
||||
background {
|
||||
monitor = {{ .name }}
|
||||
path = /etc/hyprlogin/wallpaper-{{ .name }}{{ $ext }}
|
||||
blur_passes = 0
|
||||
}
|
||||
{{ end -}}
|
||||
{{- end }}
|
||||
|
||||
# greeter-only: username placeholder lives here so hyprlock doesn't carry it
|
||||
input-field {
|
||||
monitor =
|
||||
placeholder_text_username = <i>username</i>
|
||||
fail_text = $FAIL
|
||||
}
|
||||
|
||||
# session indicator (click cycles to next)
|
||||
label {
|
||||
monitor =
|
||||
text = $GREETD_SESSION
|
||||
color = rgba(157, 0, 255, 0.85)
|
||||
font_size = 14
|
||||
font_family = $font
|
||||
onclick = hyprlogin:session_next
|
||||
position = 30, 30
|
||||
halign = left
|
||||
valign = bottom
|
||||
}
|
||||
|
||||
# keyboard layout indicator
|
||||
label {
|
||||
monitor =
|
||||
text = $LAYOUT
|
||||
color = rgba(255, 183, 0, 0.8)
|
||||
font_size = 14
|
||||
font_family = $font
|
||||
onclick = hyprctl switchxkblayout all next
|
||||
position = -30, 30
|
||||
halign = right
|
||||
valign = bottom
|
||||
}
|
||||
Executable
+113
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install hyprlogin + greetd config from chezmoi-tracked sources into /etc.
|
||||
# Run manually after `chezmoi apply` when files under system/ change.
|
||||
#
|
||||
# Usage: ~/.local/share/chezmoi/system/install-greeter.sh [--dry-run]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
dry_run=0
|
||||
[[ "${1:-}" == "--dry-run" ]] && dry_run=1
|
||||
|
||||
source_dir="${CHEZMOI_SOURCE_DIR:-}"
|
||||
if [[ -z "$source_dir" ]] && command -v chezmoi >/dev/null 2>&1; then
|
||||
source_dir="$(chezmoi source-path 2>/dev/null || true)"
|
||||
fi
|
||||
[[ -z "$source_dir" ]] && source_dir="$HOME/.local/share/chezmoi"
|
||||
sys_dir="$source_dir/system"
|
||||
|
||||
if [[ ! -d "$sys_dir" ]]; then
|
||||
echo "no system/ tree in $source_dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
staging="$(mktemp -d -t greeter-staging-XXXXXX)"
|
||||
trap 'rm -rf "$staging"' EXIT
|
||||
|
||||
# Files to install: source.tmpl → /etc target (mode)
|
||||
files=(
|
||||
"etc/greetd/greetd.conf.tmpl /etc/greetd/greetd.conf 0644"
|
||||
"etc/hyprlogin/apex-neon.conf.tmpl /etc/hyprlogin/apex-neon.conf 0644"
|
||||
"etc/hyprlogin/hyprlogin.conf.tmpl /etc/hyprlogin/hyprlogin.conf 0644"
|
||||
"etc/hyprlogin/hyprland-greeter.conf.tmpl /etc/hyprlogin/hyprland-greeter.conf 0644"
|
||||
)
|
||||
|
||||
render() {
|
||||
local src="$1" dst="$2"
|
||||
mkdir -p "$(dirname "$dst")"
|
||||
chezmoi execute-template --source "$source_dir" < "$src" > "$dst"
|
||||
}
|
||||
|
||||
needs_install=0
|
||||
declare -a plan
|
||||
for entry in "${files[@]}"; do
|
||||
read -r rel target mode <<<"$entry"
|
||||
src="$sys_dir/$rel"
|
||||
rendered="$staging/$rel"
|
||||
rendered="${rendered%.tmpl}"
|
||||
render "$src" "$rendered"
|
||||
if [[ ! -f "$target" ]] || ! diff -q "$rendered" "$target" >/dev/null 2>&1; then
|
||||
plan+=("$rendered → $target ($mode)")
|
||||
needs_install=1
|
||||
fi
|
||||
done
|
||||
|
||||
# Wallpapers: per-monitor, driven by chezmoi data.monitors[].lockscreen-wallpaper
|
||||
# Renders a "name|path" line per monitor that has a wallpaper set.
|
||||
wp_tmpl='{{ range .monitors }}{{ $wp := index . "lockscreen-wallpaper" }}{{ if $wp }}{{ .name }}|{{ $wp }}
|
||||
{{ end }}{{ end }}'
|
||||
declare -a wp_plan
|
||||
while IFS='|' read -r mon wp; do
|
||||
[[ -z "$mon" || -z "$wp" ]] && continue
|
||||
wp="${wp/#\~/$HOME}"
|
||||
if [[ ! -f "$wp" ]]; then
|
||||
echo "warning: monitor $mon wallpaper missing: $wp" >&2
|
||||
continue
|
||||
fi
|
||||
ext="${wp##*.}"
|
||||
target="/etc/hyprlogin/wallpaper-${mon}.${ext}"
|
||||
if [[ ! -f "$target" ]] || ! cmp -s "$wp" "$target"; then
|
||||
plan+=("$wp → $target (0644)")
|
||||
wp_plan+=("$wp|$target")
|
||||
needs_install=1
|
||||
fi
|
||||
done < <(chezmoi execute-template --source "$source_dir" <<<"$wp_tmpl")
|
||||
|
||||
if (( ! needs_install )); then
|
||||
echo "nothing to do — /etc files already match chezmoi source"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "would install:"
|
||||
printf ' %s\n' "${plan[@]}"
|
||||
|
||||
if (( dry_run )); then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Back up current greetd config once, the first time, for one-step rollback.
|
||||
if [[ -f /etc/greetd/greetd.conf && ! -f /etc/greetd/greetd.conf.nwg-hello-bak ]]; then
|
||||
echo "backing up existing /etc/greetd/greetd.conf → /etc/greetd/greetd.conf.nwg-hello-bak"
|
||||
sudo cp -a /etc/greetd/greetd.conf /etc/greetd/greetd.conf.nwg-hello-bak
|
||||
fi
|
||||
|
||||
for entry in "${files[@]}"; do
|
||||
read -r rel target mode <<<"$entry"
|
||||
rendered="$staging/${rel%.tmpl}"
|
||||
sudo install -Dm"$mode" "$rendered" "$target"
|
||||
done
|
||||
|
||||
for pair in "${wp_plan[@]:-}"; do
|
||||
[[ -z "$pair" ]] && continue
|
||||
IFS='|' read -r src target <<<"$pair"
|
||||
sudo install -Dm0644 "$src" "$target"
|
||||
done
|
||||
|
||||
cat <<EOF
|
||||
|
||||
done. to apply on the running system (do this from a spare TTY — Ctrl+Alt+F3 — so you don't lock yourself out):
|
||||
sudo systemctl restart greetd
|
||||
|
||||
rollback:
|
||||
sudo cp /etc/greetd/greetd.conf.nwg-hello-bak /etc/greetd/greetd.conf && sudo systemctl restart greetd
|
||||
EOF
|
||||
Reference in New Issue
Block a user