Blocare automată a sesiunii la închiderea capacului laptopului

Cineva din grupul de Facebook Linux România a partajat un script Bash util, pe care îl reproduc și aici pentru că merită o găsire mai ușoară.

Ce face scriptul

Monitorizează starea capacului laptopului și blochează automat sesiunea atunci când capacul este închis - dar numai dacă nu este conectat niciun monitor extern. Dacă lucrezi cu laptopul în configurație docked (capac închis, monitor extern conectat), scriptul nu face nimic.

Funcționează atât pe Wayland cât și pe X11, pentru că detectează displayurile externe direct din sysfs-ul kernelului (/sys/class/drm/), fără să depindă de Xorg.

Cum funcționează

La pornire face o verificare imediată, după care lasă să ruleze în fundal două procese: unul care ascultă evenimentele DRM (plug/unplug monitor) prin udevadm, și altul care urmărește D-Bus-ul systemd-logind pentru schimbări ale stării capacului. De fiecare dată când unul dintre ele prinde ceva, scriptul verifică situația și, dacă capacul e închis și nu e niciun display extern conectat, execută loginctl lock-session.

Configurare necesară

Înainte de a folosi scriptul, trebuie să spui systemd-ului să nu suspende singur la închiderea capacului, altfel laptopul adoarme înainte ca scriptul să apuce să evalueze situația. Adaugă în /etc/systemd/logind.conf (sau un fișier separat în /etc/systemd/logind.conf.d/):

HandleLidSwitch=ignore
HandleLidSwitchExternalPower=ignore
HandleLidSwitchDocked=ignore

Apoi aplică cu:

sudo systemctl restart systemd-logind

Scriptul

#!/usr/bin/env bash
# ============================================================
# Locks session when lid closed AND no external display connected.
# Uses native systemd D-Bus and kernel DRM sysfs - works on Wayland & X11.
#
# IMPORTANT CONFIGURATION:
# To prevent systemd from suspending before this script evaluates:
# Set the following in /etc/systemd/logind.conf (or /etc/systemd/logind.conf.d/lid.conf):
# HandleLidSwitch=ignore
# HandleLidSwitchExternalPower=ignore
# HandleLidSwitchDocked=ignore
# Then apply with: sudo systemctl restart systemd-logind
# ============================================================
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$HOME/.local/bin:$HOME/bin"
set -o pipefail

# ---------- Initialisation ----------
UDEV_PID=""
BUSCTL_PID=""

if [[ $EUID -eq 0 ]]; then
  echo "ERROR: Do not run this script as root." >&2
  exit 1
fi

# ---------- Single instance lock ----------
if [[ -z "${XDG_RUNTIME_DIR:-}" || ! -d "$XDG_RUNTIME_DIR" ]]; then
  echo "ERROR: XDG_RUNTIME_DIR unavailable" >&2
  exit 1
fi

LOCK_FILE="$XDG_RUNTIME_DIR/$(basename "$0" .sh).lock"
exec 9>"$LOCK_FILE"
if ! flock -n 9; then
  echo "ERROR: Another instance is running." >&2
  exit 1
fi
printf '%s\n' "$$" >&9

cleanup() {
  [[ -n "$UDEV_PID" ]] && kill -TERM -"$UDEV_PID" 2>/dev/null || true
  [[ -n "$BUSCTL_PID" ]] && kill -TERM -"$BUSCTL_PID" 2>/dev/null || true
}
trap cleanup EXIT INT TERM

# ---------- Lid state: D-Bus (systemd-logind) with fallback ----------
get_lid_state() {
  local state
  if command -v busctl &>/dev/null; then
    state=$(busctl --system get-property org.freedesktop.login1 \
      /org/freedesktop/login1 \
      org.freedesktop.login1.Manager \
      LidClosed 2>/dev/null | awk '{print $2}')
    [[ "$state" == "true" ]]  && { echo "closed"; return; }
    [[ "$state" == "false" ]] && { echo "open";   return; }
  fi
  local proc_state
  proc_state=$(awk '{print $2}' /proc/acpi/button/lid/*/state 2>/dev/null)
  [[ -n "$proc_state" ]] && echo "$proc_state" || echo "open"
}

# ---------- External display detection via DRM sysfs (Wayland-safe) ----------
hdmi_connected() {
  for status_file in /sys/class/drm/card*-*/status; do
    [[ -f "$status_file" ]] || continue
    [[ "$status_file" =~ eDP|LVDS ]] && continue
    if grep -q "^connected$" "$status_file" 2>/dev/null; then
      return 0
    fi
  done
  return 1
}

# ---------- Main lock logic ----------
check_and_lock() {
  local lid_state
  lid_state="$(get_lid_state)"
  [[ "$lid_state" != "closed" ]] && return
  for ((i=0; i<5; i++)); do
    if hdmi_connected; then
      return
    fi
    sleep 0.5
  done
  logger "laptopLid_close: Lid closed with no external display. Locking session."
  loginctl lock-session
}

# ---------- Initial check on script start ----------
check_and_lock

# ---------- Event monitors ----------
(
  udevadm monitor --subsystem-match=drm --property 2>/dev/null | while read -r line; do
    if [[ "$line" == *"HOTPLUG=1"* ]]; then
      check_and_lock
    fi
  done
) &
UDEV_PID=$!

(
  busctl monitor org.freedesktop.login1 2>/dev/null | while read -r line; do
    if [[ "$line" == *"LidClosed"* ]]; then
      check_and_lock
    fi
  done
) &
BUSCTL_PID=$!

# ---------- Wait for background processes ----------
wait

Pornire automată

Salvează scriptul undeva (de exemplu ~/.local/bin/lid-lock.sh), dă-i permisiuni de execuție cu chmod +x, apoi configurează-l ca serviciu systemd user ca să pornească automat la login:

# ~/.config/systemd/user/lid-lock.service
[Unit]
Description=Lid close lock monitor

[Service]
ExecStart=%h/.local/bin/lid-lock.sh
Restart=on-failure

[Install]
WantedBy=default.target

systemctl --user enable --now lid-lock.service


Sursa: grupul Linux România pe Facebook