1 Commits
Author SHA1 Message Date
Renovate Bot fe2b38e78f Update actions/checkout action to v7 2026-09-11 02:02:36 +00:00
32 changed files with 180 additions and 374 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
build-nixos:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: "Install Nix ❄️"
uses: cachix/install-nix-action@v31
+3 -3
View File
@@ -13,15 +13,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Install nix
uses: DeterminateSystems/nix-installer-action@v23
uses: DeterminateSystems/nix-installer-action@v22
with:
github-token: ${{ secrets.GH_TOKEN_FOR_UPDATES }}
extra_nix_config: |
experimental-features = nix-command flakes
- name: Update flake.lock
uses: DeterminateSystems/update-flake-lock@v29
uses: DeterminateSystems/update-flake-lock@v28
with:
token: ${{ secrets.GT_TOKEN_FOR_UPDATES }}
pr-title: "[chore] Update flake.lock"
Generated
+18 -18
View File
@@ -76,11 +76,11 @@
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1789127271,
"narHash": "sha256-vL/Fpo7Zxa7wvO8V5FAToUBvP+K3XsMB1wUglTtd/J0=",
"lastModified": 1788335262,
"narHash": "sha256-3jBEq8avfzlrsY4UpW4d5TOmm7ocseZfnitEJhqauyc=",
"owner": "nixos",
"repo": "nixos-hardware",
"rev": "24cfdc1f9344b90a1eee329a3906e2f39f4d0f1e",
"rev": "44d95795ee2d475b3d687325e26dcf4ca9104557",
"type": "github"
},
"original": {
@@ -96,11 +96,11 @@
]
},
"locked": {
"lastModified": 1789182507,
"narHash": "sha256-9nt4W0HNNP84B+b37yRs9Zxat1ZR5guJxEZDEjAgex4=",
"lastModified": 1787377438,
"narHash": "sha256-Sxu1NLTD/Ern6hFGLlZmtKCSct3YQXZI/lls8RE1XeM=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "b1d1b60084970f9d1e2b72662639dab6d039be71",
"rev": "65258d5c65a250189fde2e35f490d15e064c4c62",
"type": "github"
},
"original": {
@@ -131,11 +131,11 @@
},
"nixUnstable": {
"locked": {
"lastModified": 1789073787,
"narHash": "sha256-xfX/toC2QV707s06GbP4II/TxYF0fNQj7s5/LClNDKc=",
"lastModified": 1788372231,
"narHash": "sha256-7aqErvrAEz/5OcPA5p3M+tVOqeYc4oJXkNIPXfCqxAw=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "aff8a0b28396750446e5537a96461bc4facdb287",
"rev": "9387b3fcc0c23c86661636da63faabad4235a0a6",
"type": "github"
},
"original": {
@@ -153,11 +153,11 @@
]
},
"locked": {
"lastModified": 1789164534,
"narHash": "sha256-DoYGPM6QpnYBLWj9gGw6ZwAzIX+HrAVov1BoT+8Jixo=",
"lastModified": 1784642409,
"narHash": "sha256-hcbDqFuySAJawljt5r0sKBCJKYnbtGD0T/ZIozH1Dq0=",
"owner": "nix-community",
"repo": "nixos-wsl",
"rev": "72c92b11bb8289e6651c7fef29cc0a885fd6a255",
"rev": "eaeb18da90024448a60eb1ec7132eafa4003404e",
"type": "github"
},
"original": {
@@ -211,11 +211,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1789114715,
"narHash": "sha256-ugpsyk3NM2s87vXfUiIIiibbJ4Pp0JPS5p/3mfs+q+c=",
"lastModified": 1788297115,
"narHash": "sha256-Z+vUNbfd2FIKkWOTkcT7RYlh3oFCnig/d2eXD1SWf2E=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "21a67dc470149f337cecafbe965d8d252a390518",
"rev": "a3116115851d68b8952a2a4221cc25a84e56b532",
"type": "github"
},
"original": {
@@ -245,11 +245,11 @@
]
},
"locked": {
"lastModified": 1788914643,
"narHash": "sha256-4GuMPW90JSxXWDPUB9M+1m7fYbe3H0apOd86/zBQ2Kw=",
"lastModified": 1788337237,
"narHash": "sha256-gkSH8VUtCo6hnysNmb9DbTuDepH2t5pv+QWjP75xKAk=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "13616fff713a9f94055c66f15687ebdc17a335df",
"rev": "fbf759290e0cb0a98dfc813a4eb7d53ad1dacb57",
"type": "github"
},
"original": {
-1
View File
@@ -1,5 +1,4 @@
{ lib, config, pkgs, ... }: {
imports = [ ./network-watchdog.nix ];
#environment.sessionVariables.SOPS_AGE_KEY_FILE = keyFilePath;
systemd.user.startServices = lib.mkIf pkgs.stdenv.isLinux "sd-switch";
-52
View File
@@ -1,52 +0,0 @@
{ lib, config, pkgs, ... }:
let
cfg = config.services.network-watchdog;
watchdog = "${pkgs.custom.network-watchdog}/bin/network-watchdog";
units = map (name: "${name}.service") cfg.units;
in {
# User services that only make sense while online: GUI clients that hold a
# socket to a server (jellyfin-mpv-shim, nextcloud-client). Their upstream
# units have no network ordering (network-online.target is a system target,
# out of reach for user units), so they happily start while offline —
# silently dead — and nothing notices a connection dropped by a network
# blip. Gate each start on the network being up, restart on failure, and let
# a watchdog catch the "up but not connected" state that systemd can't see.
# See pkgs.custom.network-watchdog.
options.services.network-watchdog.units = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [ "jellyfin-mpv-shim" ];
description = "User services (no .service suffix) to gate on the network and keep connected.";
};
config = lib.mkIf (cfg.units != [ ]) {
systemd.user.services = lib.genAttrs cfg.units (name: {
Service = {
ExecStartPre = "${watchdog} wait-online ${name}.service";
TimeoutStartSec = "infinity"; # stay in start-pre until the network is back
Restart = lib.mkDefault "on-failure";
RestartSec = lib.mkDefault 5;
};
}) // {
network-watchdog = {
Unit = {
Description = "Restart network services that are up but not connected";
After = units;
};
Service = {
Type = "oneshot";
ExecStart = "${watchdog} check ${lib.concatStringsSep " " units}";
};
};
};
systemd.user.timers.network-watchdog = {
Unit.Description = "Periodic health check for network services";
Timer = {
OnActiveSec = "60s";
OnUnitActiveSec = "60s";
};
Install.WantedBy = [ "timers.target" ];
};
};
}
+32 -1
View File
@@ -1,6 +1,7 @@
{ lib, config, pkgs, ... }:
let
configuredMpv = config.programs.mpv.finalPackage;
shimWatchdog = pkgs.custom.jellyfin-mpv-shim-watchdog;
in{
config = lib.mkIf (config.syscfg.make.gui) {
@@ -82,6 +83,36 @@ in{
programs.yt-dlp.enable = true;
services.network-watchdog.units = [ "jellyfin-mpv-shim" ];
# The upstream jellyfin-mpv-shim service has no Restart and no network
# ordering: it happily starts while offline (silently dead) and never
# recovers a connection dropped by a network blip. Gate its start on the
# network being up, and let a watchdog notice the "up but not connected"
# state that systemd can't see. See pkgs.custom.jellyfin-mpv-shim-watchdog.
systemd.user.services.jellyfin-mpv-shim.Service = {
ExecStartPre = "${shimWatchdog}/bin/jellyfin-mpv-shim-watchdog wait-online";
TimeoutStartSec = "infinity"; # stay in start-pre until the network is back
Restart = "on-failure";
RestartSec = 5;
};
systemd.user.services.jellyfin-mpv-shim-watchdog = {
Unit = {
Description = "Restart jellyfin-mpv-shim when it is up but not connected";
After = [ "jellyfin-mpv-shim.service" ];
};
Service = {
Type = "oneshot";
ExecStart = "${shimWatchdog}/bin/jellyfin-mpv-shim-watchdog check";
};
};
systemd.user.timers.jellyfin-mpv-shim-watchdog = {
Unit.Description = "Periodic health check for jellyfin-mpv-shim";
Timer = {
OnActiveSec = "60s";
OnUnitActiveSec = "60s";
};
Install.WantedBy = [ "timers.target" ];
};
};
}
@@ -1,8 +1,6 @@
{ lib, config, ... }: {
config = lib.mkIf (config.syscfg.make.gui) {
services.easyeffects.enable = true;
xdg.configFile."pipewire/pipewire-pulse.conf.d/desktop.conf".text = ''
context.modules = [
{ name = libpipewire-module-loopback
+1 -19
View File
@@ -1,25 +1,7 @@
{ lib, config, pkgs, ... }: {
config = lib.mkIf (config.syscfg.make.gui) {
services.nextcloud-client = {
enable = true;
startInBackground = true;
};
services.network-watchdog.units = [ "nextcloud-client" ];
systemd.user.services.keepassxc = lib.mkIf (config.usercfg.wm != "X11") {
Unit = {
Description = "KeePassXC";
After = [ "graphical-session.target" ];
PartOf = [ "graphical-session.target" ];
};
Service = {
ExecStart = "${pkgs.keepassxc}/bin/keepassxc";
Restart = "on-failure";
RestartSec = 5;
};
Install.WantedBy = [ "graphical-session.target" ];
};
services.nextcloud-client.enable = true;
home.packages = with pkgs; [
thunar
@@ -3,7 +3,7 @@
.cpubar { color: $base0C; }
.gpubar { color: $base0E; }
.membar { color: $base08; }
.batbar { color: $base0A; } // overridden by .bat-<state> below
.batbar { color: $base0B; } // overridden by .bat-<state> below
.cpubar, .gpubar, .membar, .batbar { @include background-base2; margin: $gaps-window 0; }
// Window chrome
@@ -66,9 +66,7 @@
.gpu-ring-label, .vram-usage-label, .swap-section-label { margin-top: 2pt; }
.cpu-core-label { font-size: 0.7em; @include color-active; }
.bat-ring-label { font-size: 0.85em; font-weight: bold; @include color-body; }
.bat-stat-value { font-size: 1.05em; font-weight: bold; @include color-body; }
.bat-stat-label { font-size: 0.62em; margin-top: 2pt; @include color-active; }
.bat-ring-label { font-size: 0.7em; @include color-body; }
.ram-total-label { font-size: 0.72em; @include color-active; }
// GPU stats row
@@ -94,18 +92,7 @@
margin: $popup-scale * 4pt;
}
// Battery colour by state (scripts/sys/battery → .bat-<state>):
// light blue on mains (charging / full / idle), on battery green, orange below
// 25 %, red at ≤15 %
.batbar, .bat-ring { color: $base0A; }
.bat-charging, .bat-full, .bat-idle { color: $base0B; }
// Battery colour by state (scripts/sys/battery → .bat-<state>)
.batbar, .bat-ring { color: $base0B; }
.bat-charging { color: $base0C; }
.bat-low { color: $base08; }
.bat-critical { color: $base0F; }
// Battery history plot: one vertical bar per bucket, same colours as the ring
.bat-hist { min-height: 44px; margin-top: $popup-scale * 8pt; }
.bat-hist-bar { @include background-base2; margin: 0 2px; }
.bat-hist-bar trough * { background-color: $base0A; }
.hist-charging trough *, .hist-idle trough * { background-color: $base0B; }
.hist-low trough * { background-color: $base08; }
.hist-critical trough * { background-color: $base0F; }
@@ -1,9 +1,8 @@
(deflisten gpu :initial '{"gfx_pct":0,"mem_pct":0,"media_pct":0,"sclk":0,"mclk":0,"sclk_pct":0,"mclk_pct":0,"vclk":0,"vclk_pct":0,"temp":0,"power":0,"vram_used":0,"vram_total":1}' "scripts/sys/gpu")
(deflisten memory :initial '{"total":1,"used":0,"cached":0,"human":{"total":"0G","used":"0G","cached":"0G"},"swap":{"total":0,"used":0,"human":{"total":"0G","used":"0G"}}}' "scripts/sys/memory")
; state: charging | discharging | low (<25%) | critical (≤15%) | full | idle | none — styled via .bat-<state>
; history: 6 h of {t, p, s} buckets (p = -1 when empty)
(deflisten battery :initial '{"visible":false,"percentage":0,"wattage":"","status":"","time":"","state":"none","rate":"","history":[]}' "scripts/sys/battery")
; state: charging | discharging | low | full | idle | none — styled via .bat-<state>
(deflisten battery :initial '{"visible":false,"percentage":0,"wattage":"","status":"","state":"none"}' "scripts/sys/battery")
(defwidget sys-mod []
(module
@@ -39,18 +39,6 @@ detach() {
systemd-run --user --collect --quiet "${unit[@]}" -- "$cmd" "$@"
}
# eww kills a click handler that has not returned within the widget's
# :timeout (200 ms) — less than the eww client calls most handlers make
# (~50 ms each), let alone bluetoothctl or a station list fetch. Re-run
# this script detached and return at once. Call it with the original
# arguments, before anything with side effects.
# detach_self "$@"
detach_self() {
[[ -n ${EWW_DETACHED-} ]] && return 0
EWW_DETACHED=1 setsid -f "$0" "$@"
exit 0
}
# Event multiplexer for listener scripts. Producers run in the background
# and write one line per event into a fifo; the main loop re-evaluates the
# state on every event, coalescing bursts into a single refresh.
@@ -5,7 +5,6 @@ active() { rfkill list wifi 2>/dev/null | grep -q "Soft blocked: yes"; }
case ${1:-} in
status) active && echo true || echo false ;;
*)
detach_self "$@"
if active; then
rfkill unblock all
state=false
@@ -3,7 +3,6 @@
case ${1:-} in
status) dunstctl is-paused ;;
*)
detach_self "$@"
dunstctl set-paused toggle
state=$(dunstctl is-paused)
eww update do-not-disturb="$state" 2>/dev/null
@@ -1,8 +1,6 @@
#!/usr/bin/env bash
# Connect or disconnect the bluetooth device with MAC $1.
mac=$1
[[ -n $mac ]] || exit 64
detach_self "$@"
if bluetoothctl info "$mac" 2>/dev/null | grep -q "Connected: yes"; then
bluetoothctl disconnect "$mac"
else
@@ -4,7 +4,6 @@
# Needs the polkit rule from modules/nixos/system/network/wireguard.
name=$1
[[ -n $name ]] || exit 64
detach_self "$@"
if [[ -d /sys/class/net/$name ]]; then
systemctl stop "wireguard-$name.service"
else
@@ -8,7 +8,6 @@ active() { systemctl --user is-active --quiet "$unit" || pgrep -x wlsunset >/dev
case ${1:-} in
status) active && echo true || echo false ;;
*)
detach_self "$@"
if active; then
systemctl --user stop "$unit" 2>/dev/null
pkill -x wlsunset
@@ -11,7 +11,6 @@
# reply can never spawn a second daemon).
panel=$1
[[ -n $panel ]] || exit 64
detach_self "$@"
# One toggle at a time; the lock dies with this process. fd 9 is closed for
# every eww call so nothing can inherit it.
@@ -5,7 +5,6 @@ active() { [[ $(powerprofilesctl get 2>/dev/null) == power-saver ]]; }
case ${1:-} in
status) active && echo true || echo false ;;
*)
detach_self "$@"
if active; then
powerprofilesctl set balanced 2>/dev/null
state=false
@@ -1,7 +1,6 @@
#!/usr/bin/env bash
# Toggle the full-screen power menu on the focused monitor. The open's exit
# status is ignored: it exceeds the client's 100 ms reply deadline.
detach_self "$@"
eww close popup 2>/dev/null
eww update active-panel= 2>/dev/null
screen=$(eww-bar focused)
@@ -216,10 +216,10 @@ status_loop() {
}
case ${1:-} in
start) detach_self "$@"; start "$2" ;;
stop) detach_self "$@"; stop ;;
toggle) detach_self "$@"; if playing; then stop; else start "$(current_id)"; fi ;;
vol) detach_self "$@"; set_volume "$2" ;;
mute) detach_self "$@"; toggle_mute ;;
start) start "$2" ;;
stop) stop ;;
toggle) if playing; then stop; else start "$(current_id)"; fi ;;
vol) set_volume "$2" ;;
mute) toggle_mute ;;
*) status_loop ;;
esac
@@ -8,7 +8,6 @@ active() { hyprctl getoption decoration:screen_shader 2>/dev/null | grep -qF "$s
case ${1:-} in
status) active && echo true || echo false ;;
*)
detach_self "$@"
if active; then
hyprctl eval 'hl.config({ decoration = { screen_shader = "" } })' >/dev/null
hyprctl eval 'hl.config({ render = { use_fp16 = 2 } })' >/dev/null
@@ -1,24 +1,17 @@
#!/usr/bin/env bash
# Battery state from sysfs; refreshes on power_supply udev events and every
# 30 s. Emits a `state` (charging / discharging / low / critical / full /
# idle) that the CSS maps to a colour, instead of hard-coding one here.
#
# Every refresh also appends a sample to $XDG_STATE_HOME/eww/battery.log,
# which feeds the drain rate (%/h) and the history plot.
# 30 s. Emits a `state` (charging / discharging / low / full / idle) that the
# CSS maps to a colour, instead of hard-coding one here.
bat=""
for d in /sys/class/power_supply/*; do
[[ -r $d/type && $(<"$d/type") == Battery ]] && { bat=$d; break; }
done
if [[ -z $bat ]]; then
echo '{"visible":false,"percentage":0,"wattage":"","status":"","time":"","state":"none","rate":"","history":[]}'
echo '{"visible":false,"percentage":0,"wattage":"","status":"","state":"none"}'
exit 0
fi
log=${XDG_STATE_HOME:-$HOME/.local/state}/eww/battery.log
hist_hours=6 buckets=24
mkdir -p "${log%/*}" && touch "$log"
attr() { # first readable, non-empty attribute of the list, else 0
local a v
for a in "$@"; do
@@ -29,83 +22,39 @@ attr() { # first readable, non-empty attribute of the list, else 0
hours_to_clock() { awk -v h="$1" 'BEGIN { printf "%d:%02d", int(h), int((h - int(h)) * 60 + 0.5) }'; }
# Append "epoch pct state" and, once the head is an hour past the plot window,
# drop everything older than the window.
log_sample() {
local now=$1 first
printf '%s %s %s\n' "$now" "$2" "$3" >> "$log"
read -r first _ < "$log"
((first < now - (hist_hours + 1) * 3600)) || return 0
awk -v min=$((now - hist_hours * 3600)) '$1 >= min' "$log" > "$log.tmp" && mv "$log.tmp" "$log"
}
# %/h over the last 30 min of samples in the current state, signed; empty
# until there are 5 min of them.
drain_rate() {
awk -v now="$1" -v st="$2" '
$1 >= now - 1800 && $3 == st { if (!n++) { t0 = $1; p0 = $2 } t1 = $1; p1 = $2 }
END { if (n > 1 && t1 - t0 >= 300) printf "%.1f", (p1 - p0) * 3600 / (t1 - t0) }' "$log"
}
# The plot window split into buckets: mean % of the bucket's samples (-1 when
# it has none), the state of its last sample, and its start time as label.
plot() {
awk -v now="$1" -v hours="$hist_hours" -v n="$buckets" '
BEGIN { w = hours * 3600 / n; t0 = now - hours * 3600 }
$1 >= t0 { b = int(($1 - t0) / w); if (b >= n) b = n - 1; sum[b] += $2; cnt[b]++; st[b] = $3 }
END {
printf "["
for (b = 0; b < n; b++) {
p = -1; s = "none"
if (cnt[b]) {
p = sprintf("%.1f", sum[b] / cnt[b])
s = st[b] == "c" ? "charging" : st[b] == "d" ? "discharging" : "idle"
if (st[b] == "d" && p < 25) s = "low"
if (st[b] == "d" && p <= 15) s = "critical"
}
printf "%s{\"t\":\"%s\",\"p\":%s,\"s\":\"%s\"}", (b ? "," : ""), strftime("%H:%M", t0 + b * w), p, s
}
printf "]"
}' "$log"
}
state() {
local now cap status rate now_e full power pct st time="" wattage state
now=$(printf '%(%s)T' -1)
local cap status rate now full power time="" wattage state
cap=$(attr capacity)
status=$(attr status)
rate=$(attr power_now current_now) # µW or µA
now_e=$(attr energy_now charge_now) # µWh or µAh — same unit as rate, so now_e/rate = hours
now=$(attr energy_now charge_now) # µWh or µAh — same unit as rate, so now/rate = hours
full=$(attr energy_full charge_full)
if [[ -r $bat/power_now ]]; then power=$rate; else power=$((rate * $(attr voltage_now) / 1000000)); fi
wattage=$(awk -v p="$power" 'BEGIN { printf "%.1f W", p / 1000000 }')
pct=$(awk -v n="$now_e" -v f="$full" -v c="$cap" 'BEGIN { printf "%.1f", (f > 0 ? 100 * n / f : c) }')
if ((rate > 0)); then
case $status in
Discharging) time=$(hours_to_clock "$(awk -v n="$now_e" -v r="$rate" 'BEGIN { print n / r }')") ;;
Charging) time=$(hours_to_clock "$(awk -v n="$now_e" -v f="$full" -v r="$rate" 'BEGIN { print (f - n) / r }')") ;;
Discharging) time=$(hours_to_clock "$(awk -v n="$now" -v r="$rate" 'BEGIN { print n / r }')") ;;
Charging) time=$(hours_to_clock "$(awk -v n="$now" -v f="$full" -v r="$rate" 'BEGIN { print (f - n) / r }')") ;;
esac
fi
case $status in
Charging)
state=charging st=c
state=charging
status="Charging${time:+, $time left}"
;;
Discharging)
state=discharging st=d
((cap < 25)) && state=low
((cap <= 15)) && state=critical
state=discharging
((cap <= 20)) && state=low
status=${time:+$time left}
;;
Full)
state=full st=i
state=full
status="Fully charged"
;;
*) state=idle st=i ;;
*) state=idle ;;
esac
log_sample "$now" "$pct" "$st"
printf '{"visible":true,"percentage":%s,"wattage":"%s","status":"%s","time":"%s","state":"%s","rate":"%s","history":%s}' \
"$cap" "$wattage" "$(jstr "$status")" "$time" "$state" "$(drain_rate "$now" "$st")" "$(plot "$now")"
printf '{"visible":true,"percentage":%s,"wattage":"%s","status":"%s","state":"%s"}' \
"$cap" "$wattage" "$(jstr "$status")" "$state"
}
refresh() { emit "$(state)"; }
@@ -173,46 +173,20 @@
; --- Battery ---
(defwidget bat-stat [value label]
(box :orientation "v" :halign "center" :valign "center" :space-evenly false
(label :class "bat-stat-value" :text value)
(label :class "bat-stat-label" :text label)))
; left: draw while on battery, charge rate while charging; right: time left / to full
(defwidget bat-sys-win []
(defwidget bat-sys-win []
(box :orientation "v" :space-evenly false :class "sys-section" :visible {battery.visible}
(section-header :title "Battery" :accent "bat-accent")
(box :orientation "h" :space-evenly true :valign "center"
(bat-stat
:value {battery.state == "charging"
? (battery.rate == "" ? "—" : "${battery.rate} %/h")
: (battery.state == "discharging" || battery.state == "low" || battery.state == "critical"
? battery.wattage : "—")}
:label {battery.state == "charging" ? "charging" : "draw"})
(overlay
(circular-progress
:width 72 :height 72
:value {battery.percentage}
:start-at 0
:clockwise true
:thickness 7
:class "bat-ring bat-${battery.state}"
:tooltip "Battery ${round(battery.percentage, 0)}%\n${battery.status} @ ${battery.wattage}")
(label :class "bat-ring-label"
:text "${round(battery.percentage, 0)}%"))
(bat-stat
:value {battery.time == "" ? "—" : battery.time}
:label {battery.state == "charging" ? "to full" : "left"}))
; 6 h history, one bar per 15 min bucket, coloured like the ring
(box :orientation "h" :space-evenly true :class "bat-hist"
(for b in {battery.history}
(progress :orientation "v" :flipped true
:value {b.p < 0 ? 0 : b.p}
:class "bat-hist-bar hist-${b.s}"
:tooltip {b.p < 0 ? b.t : "${b.t} ${round(b.p, 0)}%"})))
(box :orientation "h" :space-evenly false
(label :class "gpu-stat-label" :halign "start" :hexpand true :text "-6h")
(label :class "gpu-stat-label" :halign "end" :text "now"))
(overlay
(circular-progress
:width 60 :height 60
:value {battery.percentage}
:start-at 0
:clockwise true
:thickness 6
:class "bat-ring bat-${battery.state}"
:tooltip "Battery ${round(battery.percentage, 0)}%\n${battery.status} @ ${battery.wattage}")
(label :class "bat-ring-label"
:text "${round(battery.percentage, 0)}%"))
)
)
@@ -20,8 +20,6 @@ let
in {
config = lib.mkIf (config.usercfg.wm == "Wayland") {
systemd.user.services.kanshi.Unit.After = lib.mkForce [ config.services.kanshi.systemdTarget "awww.service" ];
services.kanshi = {
enable = true;
systemdTarget = "graphical-session.target";
+10 -2
View File
@@ -24,15 +24,23 @@
startupScript = pkgs.writeShellScriptBin "hyprland-start" ''
# The eww bar is not started here: eww.service opens it with the
# graphical session and kanshi places it per profile (see apps/eww).
${pkgs.awww}/bin/awww-daemon &
${pkgs.awww}/bin/awww restore &
sleep 2
keepassxc &
firefox &
easyeffects --gapplication-service &
sleep 2
nextcloud &
# telegram-desktop &
# discord &
'';
in {
config = lib.mkIf (config.usercfg.wm == "Wayland") {
services.awww.enable = true;
wayland.windowManager.hyprland = {
enable = true;
xwayland.enable = true;
+2
View File
@@ -108,6 +108,7 @@
bspc desktop -f 0
telegram-desktop &
nextcloud &
#flameshot &
sleep 2
@@ -116,6 +117,7 @@
ibus-daemon -drx
easyeffects --gapplication-service &
bspc desktop -f 0
'';
+1 -2
View File
@@ -13,7 +13,6 @@ in {
# wireplumber.enable = true;
};
programs.dconf.enable = true;
environment.systemPackages = with pkgs; [ alsa-utils ];
environment.systemPackages = with pkgs; [ easyeffects alsa-utils ];
};
}
+1 -1
View File
@@ -8,5 +8,5 @@
eww-bar = pkgs.callPackage ./eww-bar { };
network-watchdog = pkgs.callPackage ./network-watchdog { };
jellyfin-mpv-shim-watchdog = pkgs.callPackage ./jellyfin-mpv-shim-watchdog { };
}
+21 -49
View File
@@ -56,9 +56,6 @@ log() { printf 'eww-bar: %s\n' "$*" >&2; }
ewwc() { timeout 5 eww --no-daemonize "$@" 9>&-; }
ping_daemon() { ewwc ping >/dev/null 2>&1; }
# Empty when the daemon did not answer within the client's 100 ms deadline —
# its main thread is busy (a popup takes seconds to build) — as well as when
# nothing is open. Only a non-empty listing is authoritative.
list_windows() { ewwc active-windows 2>/dev/null || true; }
bar_open() { list_windows | grep -qx 'bar: bar'; }
@@ -72,28 +69,6 @@ wait_for_window() {
return 1
}
# window_state NAME [SECONDS] → open | closed (listed without NAME) |
# busy (nothing listed within SECONDS: main thread stuck, or nothing open)
window_state() {
local i n=$((${2:-5} * 5)) out
for ((i = 0; i < n; i++)); do
out=$(list_windows)
if [[ -n $out ]]; then
if grep -qx "$1: $1" <<<"$out"; then echo open; else echo closed; fi
return 0
fi
sleep 0.2
done
echo busy
}
# The compositor's view: does the daemon own any layer surface? Tells a
# daemon that is merely not answering from one with nothing on screen.
daemon_has_layer() {
hyprctl -j layers 2>/dev/null \
| jq -e --argjson pid "$(main_pid)" '[.. | objects | select(.pid? == $pid)] | length > 0' >/dev/null 2>&1
}
# ---------------------------------------------------------------- daemon ---
# nixpkgs wraps eww (wrapGAppsHook), so the running process is `.eww-wrapped`;
@@ -151,13 +126,13 @@ wait_for_daemon() {
start_unit() {
systemctl --user reset-failed "$unit" >/dev/null 2>&1 || true
systemctl --user start --no-block "$unit"
systemctl --user start "$unit"
}
restart_unit() {
log "restarting $unit"
systemctl --user reset-failed "$unit" >/dev/null 2>&1 || true
systemctl --user restart --no-block "$unit"
systemctl --user restart "$unit"
}
ensure() {
@@ -219,7 +194,7 @@ close_popups() {
# open_bar [--force] [SPEC]
open_bar() {
local force=0 spec screen state
local force=0 spec screen
if [[ ${1:-} == --force ]]; then force=1; shift; fi
spec=${1:-$(saved_screen)}
spec=${spec:-$default_screen}
@@ -235,18 +210,17 @@ open_bar() {
# client deadline), so the result is verified instead.
close_popups
ewwc open bar --screen "$screen" >/dev/null 2>&1 || true
state=$(window_state bar 10)
# Only an answered "no bar" means the screen was refused; busy just needs time.
if [[ $state == closed && $screen != "$default_screen" ]]; then
log "cannot open the bar on '$screen', using screen $default_screen"
screen=$default_screen
ewwc open bar --screen "$screen" >/dev/null 2>&1 || true
state=$(window_state bar 10)
fi
if [[ $state != open ]]; then
log "the bar does not come up ($state), restarting $unit"
restart_unit
exit 1
if ! wait_for_window bar 5; then
if [[ $screen != "$default_screen" ]]; then
log "cannot open the bar on '$screen', using screen $default_screen"
screen=$default_screen
ewwc open bar --screen "$screen" >/dev/null 2>&1 || true
fi
if ! wait_for_window bar 5; then
log "the bar does not come up, restarting $unit"
restart_unit
exit 1
fi
fi
printf '%s\n' "$spec" >"$screen_file"
printf '%s\n' "$screen" >"$current_file"
@@ -327,16 +301,14 @@ check() {
strike "daemon does not answer"
return 0
fi
case $(window_state bar 10) in
open) rm -f "$fail_file"; return 0 ;;
# Nothing listed for 10 s while something is on screen: the main loop is
# frozen or still building a popup. Leave the windows alone; a second
# miss restarts the unit.
busy) if daemon_has_layer; then strike "UI unresponsive"; return 0; fi ;;
esac
# Listed without the bar, or nothing on screen at all: it really is gone.
if wait_for_window bar 3; then
rm -f "$fail_file"
return 0
fi
# Either nothing is open or the main loop is frozen (empty reply). Try to
# open the bar: a frozen daemon still won't list it afterwards.
open_bar || true
if wait_for_window bar 5; then
if wait_for_window bar 3; then
rm -f "$fail_file"
log "bar was missing, reopened"
else
@@ -0,0 +1,51 @@
{ writeShellScriptBin
, coreutils
, findutils
, gnugrep
, iproute2
, systemd
}:
# jellyfin-mpv-shim keeps a persistent socket to the Jellyfin server. When the
# machine drops off the network that socket dies, but the process stays alive
# and the unit stays "active" — running yet deaf to cast requests, the "up but
# not working, restart it by hand" case. systemd can't see this: nothing
# crashes and the shim speaks no health protocol, so we probe the real thing —
# is its MainPID still holding a live (established, non-loopback) connection?
#
# check (default) one tick, run by a 60s timer: restart if online but
# not connected, rate-limited to one restart per 5 min
# wait-online block until a real address exists; used as ExecStartPre so the
# unit only starts once online
writeShellScriptBin "jellyfin-mpv-shim-watchdog" ''
set -u
service="jellyfin-mpv-shim.service"
stamp="''${XDG_RUNTIME_DIR:-/run/user/$(${coreutils}/bin/id -u)}/jellyfin-mpv-shim-watchdog.stamp"
# A global-scope address on a non-loopback interface true for a LAN IP too,
# so this needs no server URL and works whether Jellyfin is local or remote.
network_up() { ${iproute2}/bin/ip -o addr show scope global up 2>/dev/null | ${gnugrep}/bin/grep -q .; }
if [ "''${1:-check}" = wait-online ]; then
while ! network_up; do ${coreutils}/bin/sleep 5; done
exit 0
fi
# Healthy: MainPID owns an established socket to something other than loopback.
pid="$(${systemd}/bin/systemctl --user show -p MainPID --value "$service" 2>/dev/null)"
if [ "''${pid:-0}" != 0 ] && ${iproute2}/bin/ss -tnpH state established 2>/dev/null \
| ${gnugrep}/bin/grep "pid=$pid," \
| ${gnugrep}/bin/grep -qv -e '127\.0\.0\.1' -e '\[::1\]'; then
exit 0
fi
# Not connected: only worth restarting once the network is actually back...
network_up || exit 0
# ...and not if we already restarted within the last 5 minutes.
[ -e "$stamp" ] && [ -n "$(${findutils}/bin/find "$stamp" -mmin -5 2>/dev/null)" ] && exit 0
: > "$stamp"
echo "jellyfin-mpv-shim-watchdog: online but no live connection -> restarting $service"
${systemd}/bin/systemctl --user restart "$service"
''
-69
View File
@@ -1,69 +0,0 @@
{ writeShellScriptBin
, coreutils
, findutils
, gnugrep
, iproute2
, systemd
}:
# For user services that only make sense while online (jellyfin-mpv-shim,
# nextcloud-client, ...). They keep a persistent socket to their server; when
# the machine drops off the network that socket dies, but the process stays
# alive and the unit stays "active" — running yet deaf, the "up but not
# working, restart it by hand" case. systemd can't see this: nothing crashes
# and the programs speak no health protocol, so we probe the real thing — is
# the MainPID still holding a live (established, non-loopback) connection?
#
# wait-online UNIT block until a default route exists; used as ExecStartPre
# so UNIT only starts once online. Also stamps the start
# time so `check` leaves a freshly (re)started UNIT alone.
# check UNIT... one tick, run by a 60s timer: restart every UNIT that is
# active but not connected, at most once per 2 minutes.
#
# Wired up by the services.network-watchdog home-manager module.
writeShellScriptBin "network-watchdog" ''
set -u
stampdir="''${XDG_RUNTIME_DIR:-/run/user/$(${coreutils}/bin/id -u)}/network-watchdog"
${coreutils}/bin/mkdir -p "$stampdir"
usage() { echo "usage: network-watchdog wait-online UNIT | check UNIT..." >&2; exit 64; }
# Online = a default route. An address alone is not enough: wg0 and virtual
# bridges carry global-scope addresses whether or not there is a way out.
network_up() { { ${iproute2}/bin/ip -4 route show default; ${iproute2}/bin/ip -6 route show default; } 2>/dev/null | ${gnugrep}/bin/grep -q .; }
# Healthy: MainPID owns an established socket to something other than loopback.
connected() {
pid="$(${systemd}/bin/systemctl --user show -p MainPID --value "$1" 2>/dev/null)"
[ "''${pid:-0}" != 0 ] && ${iproute2}/bin/ss -tnpH state established 2>/dev/null \
| ${gnugrep}/bin/grep "pid=$pid," \
| ${gnugrep}/bin/grep -qv -e '127\.0\.0\.1' -e '\[::1\]'
}
# Stamped within the last 2 minutes: still connecting, or already restarted.
recently_started() { [ -n "$(${findutils}/bin/find "$stampdir/$1" -mmin -2 2>/dev/null)" ]; }
[ $# -ge 1 ] || usage
cmd="$1"; shift
case "$cmd" in
wait-online)
[ $# -eq 1 ] || usage
while ! network_up; do ${coreutils}/bin/sleep 5; done
: > "$stampdir/$1"
;;
check)
# Nothing can be connected while offline, and restarting wouldn't help.
network_up || exit 0
for unit; do
# Not "active" (inactive, failed, or still in wait-online): systemd's job, not ours.
${systemd}/bin/systemctl --user is-active --quiet "$unit" || continue
recently_started "$unit" && continue
connected "$unit" && continue
echo "network-watchdog: $unit is online but has no live connection -> restarting"
${systemd}/bin/systemctl --user restart "$unit" # its wait-online refreshes the stamp
done
;;
*) usage ;;
esac
''
+1 -1
View File
@@ -5,7 +5,7 @@
[ "nvme" "xhci_pci" "ahci" "usb_storage" "usbhid" "sd_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "v4l2loopback" "kvm-amd" ];
boot.kernelPackages = pkgs.linuxPackages_latest;
#boot.kernelPackages = pkgs.linuxPackages_latest;
boot.extraModulePackages = with config.boot.kernelPackages;
[ v4l2loopback ];
boot.extraModprobeConfig = ''