diff --git a/hypr/.config/hypr/hypridle.conf b/hypr/.config/hypr/hypridle.conf index 28182d7..ac6fd3a 100644 --- a/hypr/.config/hypr/hypridle.conf +++ b/hypr/.config/hypr/hypridle.conf @@ -1,14 +1,28 @@ -# hypridle.conf — idle stages: screensaver (300s, backend in modules/screensaver.lua) -> Noctalia lock (10min) -> DPMS off. +# hypridle.conf — screensaver only. # Managed by dotfiles (stow hypr). # -# NOTE: this Hyprland (hyprmod Lua runtime, 0.56.x) evaluates `hyprctl dispatch` -# args as Lua — classic `dispatch dpms off` syntax fails. Dispatchers must be -# hl.dsp.* expressions (e.g. hl.dsp.dpms(false)). +# IDLE OWNERSHIP: Noctalia owns dim / lock / screen-off / suspend via its own +# [idle] block in ~/.config/noctalia/config.toml. hypridle is started only for +# the Noctalia shell (see hyprland.lua) and contributes just the tte +# screensaver. Do not re-add lock/DPMS listeners here — that duplication is +# what made two idle managers fight over the same transitions. +# +# lock_cmd goes through scripts/lock.sh, which resolves the active shell from +# env.lua. That keeps a manually started hypridle under Caelestia from calling +# `noctalia ...` (which would silently do nothing). +# +# WARNING — do not add DPMS dispatchers back to this file. This Hyprland +# (hyprmod Lua runtime, 0.56.x) evaluates `hyprctl dispatch` args as Lua, and +# the dpms dispatcher takes a TABLE, not a boolean: +# WRONG (silently becomes a TOGGLE, ignoring the argument): +# hyprctl dispatch 'hl.dsp.dpms(true)' +# RIGHT: +# hyprctl dispatch 'hl.dsp.dpms({action="enable"})' +# A toggle in after_sleep_cmd turns the display OFF on resume, and with +# misc:key_press_enables_dpms = false nothing could wake it again. general { - lock_cmd = pidof -q noctalia || noctalia msg session lock - before_sleep_cmd = loginctl lock-session - after_sleep_cmd = hyprctl dispatch 'hl.dsp.dpms(true)' + lock_cmd = ~/.config/hypr/scripts/lock.sh } # Screensaver via backend wrappers (switch backend in modules/screensaver.lua). @@ -17,16 +31,3 @@ listener { on-timeout = ~/.config/hypr/scripts/screensaver-start.sh on-resume = ~/.config/hypr/scripts/screensaver-stop.sh } - -# 10min: lock via Noctalia (SUPER+L runs the same command). -listener { - timeout = 600 - on-timeout = loginctl lock-session -} - -# 10m30s: power off displays. -listener { - timeout = 630 - on-timeout = hyprctl dispatch 'hl.dsp.dpms(false)' - on-resume = hyprctl dispatch 'hl.dsp.dpms(true)' -} diff --git a/hypr/.config/hypr/hyprland.lua b/hypr/.config/hypr/hyprland.lua index a15e0eb..b4795fa 100644 --- a/hypr/.config/hypr/hyprland.lua +++ b/hypr/.config/hypr/hyprland.lua @@ -62,5 +62,8 @@ if fid_gui then require("hyprland-buntes-monster-gui") end --- For Noctalia Color templates -require("noctalia").apply_theme() +-- For Noctalia Color templates. Noctalia-only: under Caelestia this would +-- otherwise push Noctalia's Catppuccin border colors into the compositor. +if env.shell == "noctalia" then + require("noctalia").apply_theme() +end diff --git a/hypr/.config/hypr/modules/lock.lua b/hypr/.config/hypr/modules/lock.lua new file mode 100644 index 0000000..f9c4891 --- /dev/null +++ b/hypr/.config/hypr/modules/lock.lua @@ -0,0 +1,33 @@ +-- Single source of truth for "how do I lock this session", keyed on the +-- active shell in env.lua. Consumed by two callers: +-- * scripts/lock.sh (hypridle's lock_cmd) - loaded via plain dofile +-- * modules/shell_commands.lua (SUPER+L) - loaded via require +-- +-- Both contexts load this file, so it must NOT use require("env"): Lua's +-- default package.path is cwd-relative, and that only resolves inside +-- ~/.config/hypr. hypridle's cwd is not guaranteed. Resolve by path instead. +local config_home = os.getenv("XDG_CONFIG_HOME") or os.getenv("HOME") .. "/.config" +local prefix = config_home .. "/hypr/" + +local function load_env() + local fid = io.open(prefix .. "env.lua") + if fid then + fid:close() + return dofile(prefix .. "env.lua") + end + return dofile(prefix .. "env_example.lua") +end + +local LOCK = { + noctalia = "noctalia msg session lock", + caelestia = "caelestia shell lock lock", +} + +local env = load_env() + +return { + shell = env.shell, + -- nil for an unknown shell; scripts/lock.sh treats that as a hard error + -- rather than reporting success while nothing is actually locked. + cmd = LOCK[env.shell], +} diff --git a/hypr/.config/hypr/modules/look_and_feel.lua b/hypr/.config/hypr/modules/look_and_feel.lua index de1cae6..60918fa 100644 --- a/hypr/.config/hypr/modules/look_and_feel.lua +++ b/hypr/.config/hypr/modules/look_and_feel.lua @@ -253,5 +253,8 @@ hl.config({ misc = { force_default_wallpaper = -1, disable_hyprland_logo = false, + -- Both *_enables_dpms default to false, so a display left in DPMS-off + -- is unrecoverable without a TTY switch. A keypress should wake it. + key_press_enables_dpms = true, }, }) diff --git a/hypr/.config/hypr/modules/shell_commands.lua b/hypr/.config/hypr/modules/shell_commands.lua index 173e803..57c4174 100644 --- a/hypr/.config/hypr/modules/shell_commands.lua +++ b/hypr/.config/hypr/modules/shell_commands.lua @@ -1,6 +1,10 @@ local env = require("env") +-- Shell-agnostic on purpose: the active shell is resolved inside lock.sh from +-- env.lua, so hypridle's lock_cmd and this bind cannot drift apart. +local LOCK = "~/.config/hypr/scripts/lock.sh" + local commands = { session = "", logout = "", @@ -9,7 +13,7 @@ local commands = { menu = "", settings = "", control_center = "", - lock = "", + lock = LOCK, screensaver = "sh ~/.config/hypr/scripts/screensaver-start.sh force", wallpaper_select = "", keybinds_show = "sh ~/.config/hypr/scripts/show-keybinds.sh", @@ -28,7 +32,6 @@ if env.shell == "noctalia" then commands.menu = "noctalia msg panel-toggle launcher" commands.settings = "noctalia msg settings-toggle" commands.control_center = "noctalia msg panel-toggle control-center" - commands.lock = "noctalia msg session lock" commands.wallpaper_select = "noctalia msg panel-toggle wallpaper" elseif env.shell == "caelestia" then commands.session = "caelestia shell drawers toggle session" @@ -38,7 +41,6 @@ elseif env.shell == "caelestia" then commands.menu = "caelestia shell drawers toggle launcher" commands.settings = "caelestia shell drawers toggle dashboard" commands.control_center = "caelestia shell drawers toggle sidebar" - commands.lock = "caelestia shell lock lock" commands.wallpaper_select = "caelestia shell drawers toggle launcher && sleep 0.1 && wtype \">wallpaper \"" commands.clipboard = "caelestia clipboard" commands.clipboard_delete = "caelestia clipboard -d" diff --git a/hypr/.config/hypr/scripts/lock.sh b/hypr/.config/hypr/scripts/lock.sh new file mode 100755 index 0000000..ba9c405 --- /dev/null +++ b/hypr/.config/hypr/scripts/lock.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# lock.sh — lock the session with the active shell's own lock command. +# The mapping lives in modules/lock.lua (reads env.lua), so neither hypridle's +# lock_cmd nor the SUPER+L bind hardcodes a shell. This is what keeps a +# manually started hypridle under Caelestia from failing on `noctalia ...`. +# +# Usage: lock.sh (no args; safe to call while locked) + +set -euo pipefail + +# Resolve from this script's own location so it works through the +# ~/.config/hypr symlink and does not depend on XDG_CONFIG_HOME. +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +LOCK_LUA="$(cd "$SCRIPT_DIR/.." && pwd)/modules/lock.lua" + +CMD="$(LOCK_LUA="$LOCK_LUA" lua -e 'print(dofile(os.getenv("LOCK_LUA")).cmd)' 2>/dev/null)" || { + echo "lock: cannot read modules/lock.lua" >&2 + exit 1 +} + +# Empty here means env.lua named a shell with no known lock command. Fail loudly +# instead of exiting 0, or a caller would think the session got locked. +[[ -n "$CMD" ]] || { + echo "lock: no lock command for the active shell (check env.lua)" >&2 + exit 1 +} + +# Word-split on purpose: CMD is a command line, not a single path. +# shellcheck disable=SC2086 +exec $CMD diff --git a/hypr/.config/noctalia/config.toml b/hypr/.config/noctalia/config.toml index 823b062..140d051 100644 --- a/hypr/.config/noctalia/config.toml +++ b/hypr/.config/noctalia/config.toml @@ -50,8 +50,10 @@ resume_command = "brightnessctl -r" [idle.behavior.kbd-off] timeout = 300 action = "command" -command = "brightnessctl -sd rgb:kbd_backlight set 0" -resume_command = "brightnessctl -rd rgb:kbd_backlight" +# This ThinkPad exposes the keyboard backlight as tpacpi::kbd_backlight; +# the previous rgb:kbd_backlight matched no device and errored out silently. +command = "brightnessctl -sd tpacpi::kbd_backlight set 0" +resume_command = "brightnessctl -rd tpacpi::kbd_backlight" [idle.behavior.lock] timeout = 900