feat: take the face in the lock screens of hyprland and niri

This commit is contained in:
Felitendo committed 2026-09-26 19:35:04 +02:00
1 parent 2a9b0d3f91
commit c693ca216c
27 files changed
+1850 -1219

No files matched your search

+1
View File
@@ -126,6 +126,7 @@ fu_config_load() {
_fu_kv_lookup "$FU_CONFIG" LockScreen yes; CFG_LOCK=no; fu_is_true "$FU_KV_VALUE" && CFG_LOCK=yes
_fu_kv_lookup "$FU_CONFIG" Sudo no; CFG_SUDO=no; fu_is_true "$FU_KV_VALUE" && CFG_SUDO=yes
_fu_kv_lookup "$FU_CONFIG" Polkit no; CFG_POLKIT=no; fu_is_true "$FU_KV_VALUE" && CFG_POLKIT=yes
_fu_kv_lookup "$FU_CONFIG" LockScreens yes; CFG_LOCKERS=no; fu_is_true "$FU_KV_VALUE" && CFG_LOCKERS=yes
_fu_kv_lookup "$FU_SYSCONFIG" Liveness light; CFG_LIVENESS="$FU_KV_VALUE"
_fu_kv_lookup "$FU_SYSCONFIG" Camera auto; CFG_CAMERA="$FU_KV_VALUE"
+27 -6
View File
@@ -251,6 +251,9 @@ fu_ui_status() {
fi
_fu_row "$(fu_msg "sudo")" "$(_fu_small_onoff "$(fu_pam_enabled sudo && echo yes || echo no)")"
_fu_row "$(fu_msg "Admin prompts")" "$(_fu_small_onoff "$(fu_pam_enabled polkit-1 && echo yes || echo no)")"
if fu_pam_lockers_here; then
_fu_row "$(fu_msg "Lock screens")" "$(_fu_small_onoff "$(fu_pam_lockers_enabled && echo yes || echo no)") ${FU_C_DIM}($(fu_pam_lockers_list))${FU_C_RESET}"
fi
_fu_row "$(fu_msg "Photo check")" "$(fu_value_label Liveness "$CFG_LIVENESS")"
if (( FU_ST_LOCKOUT > 0 )); then
@@ -270,13 +273,15 @@ fu_ui_status() {
# ---------------------------------------------------------------------------
# Settings
# ---------------------------------------------------------------------------
# Format: scope|Key|type|default|label-msgid|choices|needs
# Format: scope|Key|type|default|label-msgid|choices|needs|only
# scope user (this user's file), sys (the system file, through sudo),
# pam (the service of that name, through sudo), or group for a
# heading, with only the label after it
# type bool, choice (steps through the choices) or camera
# needs a bool setting this one does nothing without; it is dimmed while
# that is off
# only lockers: only where the lock screen is a program of its own with a
# PAM file (Hyprland, Niri), see fu_pam_lockers_here
FU_SETTINGS=(
"group|Lock screen"
"user|LockScreen|bool|yes|Unlock with your face"
@@ -285,6 +290,7 @@ FU_SETTINGS=(
"group|Password prompts"
"pam|sudo|bool|no|sudo in a terminal"
"pam|polkit-1|bool|no|Admin prompts"
"pam|lockscreens|bool|yes|Lock screens|||lockers"
"group|Recognition"
"sys|Liveness|choice|light|Photo check|off,light,heavy"
"sys|Strictness|choice|normal|How closely the face has to match|relaxed,normal,strict"
@@ -308,12 +314,17 @@ fu_setting_help() {
fu_msg "On GNOME a small GNOME extension shows the bubble. Turning face unlock on switches it on."
return
;;
hyprland:LockScreen|niri:LockScreen)
fu_msg "Scans when you come back to hyprlock or swaylock and opens them. Any other lock screen scans when you press Enter on the empty password field."
return
;;
esac
case "$1:$2" in
LockScreen:*) fu_msg "Unlocks the lock screen when it sees your face. Off: only your password works there." ;;
ScanOnWake:*) fu_msg "Scans when you press a key or move the mouse on the lock screen, and when the computer wakes up." ;;
ScanOnLock:*) fu_msg "Scans as soon as the screen locks. Off by default: if you lock it yourself, it would unlock again right away." ;;
sudo:*) fu_msg "sudo takes your face instead of the password. No match: you type the password as usual." ;;
lockscreens:*) fu_msg "The lock screen takes your face in its password check. Press Enter on the empty field to scan. Found here: %s." "$(fu_pam_lockers_list)" ;;
polkit-1:*) fu_msg "The password windows of your desktop and apps, for example when you install software. No match: you type the password." ;;
Liveness:heavy) fu_msg "You have to blink or turn your head a little. This also stops printed photos." ;;
Liveness:off) fu_msg "No check at all. Only for trying out a camera." ;;
@@ -378,7 +389,15 @@ _fu_setting_value() {
case "$1" in
user) _fu_kv_lookup "$FU_CONFIG" "$2" "$3"; FU_SETTING_VALUE="$FU_KV_VALUE" ;;
sys) _fu_kv_lookup "$FU_SYSCONFIG" "$2" "$3"; FU_SETTING_VALUE="$FU_KV_VALUE" ;;
pam) if fu_pam_enabled "$2"; then FU_SETTING_VALUE=yes; else FU_SETTING_VALUE=no; fi ;;
pam)
if [[ $2 == lockscreens ]]; then
if fu_pam_lockers_enabled; then FU_SETTING_VALUE=yes; else FU_SETTING_VALUE=no; fi
elif fu_pam_enabled "$2"; then
FU_SETTING_VALUE=yes
else
FU_SETTING_VALUE=no
fi
;;
esac
}
@@ -461,8 +480,9 @@ _fu_setting_change() {
# Remembered, so that turning face unlock off and on again brings
# it back.
case "$key" in
sudo) fu_config_set Sudo "$next" ;;
polkit-1) fu_config_set Polkit "$next" ;;
sudo) fu_config_set Sudo "$next" ;;
polkit-1) fu_config_set Polkit "$next" ;;
lockscreens) fu_config_set LockScreens "$next" ;;
esac
;;
esac
@@ -474,12 +494,13 @@ _fu_setting_change() {
# resolved before the loop.
fu_ui_settings() {
local -a scopes=() keys=() types=() defaults=() labels=() widths=() choices=() needs=() values=() rows=()
local spec scope key type default label choice need locale i j frame row pad dirty=1 cursor=0 shown
local spec scope key type default label choice need only locale i j frame row pad dirty=1 cursor=0 shown
local width=0 wrap cols
locale="$(fu_ui_locale)"
for spec in "${FU_SETTINGS[@]}"; do
IFS='|' read -r scope key type default label choice need <<< "$spec"
IFS='|' read -r scope key type default label choice need only <<< "$spec"
[[ $only == lockers ]] && ! fu_pam_lockers_here && continue
# A heading has its label where the key would be.
[[ $scope == group ]] && label="$key" key=''
scopes+=("$scope"); keys+=("$key"); types+=("$type"); defaults+=("$default")
+71 -12
View File
@@ -1,17 +1,21 @@
# shellcheck shell=bash
#
# Putting face unlock in front of sudo and polkit's admin prompts, and taking
# it out again.
# Putting face unlock in front of sudo, polkit's admin prompts and the lock
# screens of Hyprland and Niri, and taking it out again.
#
# Two services and nothing else. The lock screen does not go through PAM at all
# (see src/agent/lockcontroller.h), and the login screen stays with the
# password: logging in is what unlocks the wallet, and a face has no password
# to hand it.
# The lock screens of Plasma and GNOME do not go through this at all: the
# agent opens them (see src/agent/lockcontroller.h). hyprlock, swaylock and
# the others are programs of their own that only open themselves, so the face
# goes into their password check, and pressing Enter on the empty field is
# how a scan starts. The login screen stays with the password: logging in is
# what unlocks the wallet, and a face has no password to hand it.
#
# The line that goes in:
#
# -auth sufficient /usr/lib/security/pam_face_unlock.so
#
# with "lockscreen" after it for a lock screen (see the PAM module).
#
# "sufficient": a match lets the person in, anything else falls through to the
# lines below as if this one were not there. The dash makes PAM skip it
# quietly if the module ever goes missing, say because the package was removed
@@ -29,6 +33,9 @@
FU_PAM_MARK="# face-unlock: the face first, the password if that does not work"
FU_PAM_WRAPPER_MARK="# Written by face-unlock."
FU_PAM_SERVICES=(sudo polkit-1)
# Lock screens with a PAM file of their own. One that uses "login" or
# "system-auth" directly is left alone: those also let people log in.
FU_PAM_LOCKERS=(hyprlock swaylock gtklock waylock)
# Overridable for the tests only; the root side never takes them from the
# environment (see the top of face-unlock).
@@ -52,8 +59,59 @@ fu_pam_vendor() {
return 1
}
# fu_pam_is_locker <service>
fu_pam_is_locker() {
local s
for s in "${FU_PAM_LOCKERS[@]}"; do
[[ $s == "$1" ]] && return 0
done
return 1
}
# fu_pam_line <service>
fu_pam_line() {
printf -- '-auth sufficient %s\n' "$FU_PAM_MODULE"
if fu_pam_is_locker "$1"; then
printf -- '-auth sufficient %s lockscreen\n' "$FU_PAM_MODULE"
else
printf -- '-auth sufficient %s\n' "$FU_PAM_MODULE"
fi
}
# fu_pam_lockers
# The lock screens installed here, one per line.
fu_pam_lockers() {
local s
for s in "${FU_PAM_LOCKERS[@]}"; do
if [[ -f $(fu_pam_etc "$s") ]] || fu_pam_vendor "$s" > /dev/null; then
printf '%s\n' "$s"
fi
done
}
# fu_pam_lockers_list
# The same, for people: "hyprlock, swaylock".
fu_pam_lockers_list() {
local list
list="$(fu_pam_lockers | paste -sd ',')"
printf '%s\n' "${list//,/, }"
}
# fu_pam_lockers_here
# Whether this desktop's lock screen is a program of its own (Hyprland,
# Niri and the like), and one with a PAM file is installed.
fu_pam_lockers_here() {
[[ $FU_DESKTOP != plasma && $FU_DESKTOP != gnome && -n $(fu_pam_lockers) ]]
}
# fu_pam_lockers_enabled
# Whether every lock screen installed here takes the face, and there is one.
fu_pam_lockers_enabled() {
local s found=0
while IFS= read -r s; do
found=1
fu_pam_enabled "$s" || return 1
done < <(fu_pam_lockers)
(( found ))
}
# fu_pam_enabled <service>
@@ -63,13 +121,13 @@ fu_pam_enabled() {
[[ -r $file ]] && grep -q 'pam_face_unlock\.so' "$file"
}
# fu_pam_insert <file>
# fu_pam_insert <file> <service>
# Prints the file with the line added before its first auth line. Debian and
# Ubuntu have none in sudo's file, only "@include common-auth", and that
# counts as one: after it the password has already been asked for. A file
# with neither (which would be odd for either service) gets it at the end.
fu_pam_insert() {
awk -v mark="$FU_PAM_MARK" -v line="$(fu_pam_line)" '
awk -v mark="$FU_PAM_MARK" -v line="$(fu_pam_line "$2")" '
!done && ($0 ~ /^[[:space:]]*-?auth[[:space:]]/ || $0 ~ /^[[:space:]]*@include[[:space:]]+common-auth([[:space:]]|$)/) {
print mark
print line
@@ -95,13 +153,14 @@ fu_pam_remove_lines() {
' "$1"
}
# fu_pam_wrapper <vendor file> <service>
fu_pam_wrapper() {
local vendor="$1"
printf '#%%PAM-1.0\n'
printf '%s The face first, then everything\n' "$FU_PAM_WRAPPER_MARK"
printf '# %s does for this service. Removed again by\n' "$vendor"
printf '# "face-unlock disable" or from its settings.\n\n'
fu_pam_line
fu_pam_line "$2"
printf 'auth include %s\n' "$vendor"
printf 'account include %s\n' "$vendor"
printf 'password include %s\n' "$vendor"
@@ -133,9 +192,9 @@ fu_pam_enable() {
fu_pam_enabled "$service" && return 0
if [[ -f $file ]]; then
content="$(fu_pam_insert "$file")" || return 1
content="$(fu_pam_insert "$file" "$service")" || return 1
elif vendor="$(fu_pam_vendor "$service")"; then
content="$(fu_pam_wrapper "$vendor")"
content="$(fu_pam_wrapper "$vendor" "$service")"
else
fu_bad "$(fu_msg "There is no PAM configuration for %s on this system." "$service")"
return 1
+17 -9
View File
@@ -9,8 +9,8 @@
# the root side a command of one's own.
#
# --root set <Key> <Value> one line of /etc/face-unlock/config
# --root pam-enable <service> sudo or polkit-1, see pam.sh
# --root pam-disable <service>
# --root pam-enable <service> sudo, polkit-1, a lock screen, or lockscreens
# --root pam-disable <service> for all lock screens installed; see pam.sh
# --root socket-enable start the daemon's socket, and at boot
# --root socket-disable
# --root migrate move over from plasma-face-unlock, see migrate.sh
@@ -69,16 +69,24 @@ fu_root_verb() {
chmod 0644 "$FU_SYSCONFIG"
;;
pam-enable|pam-disable)
local service="${1:-}" known=0 s
for s in "${FU_PAM_SERVICES[@]}"; do
local service="${1:-}" known=0 s rc=0
local -a services=("$service")
for s in "${FU_PAM_SERVICES[@]}" "${FU_PAM_LOCKERS[@]}"; do
[[ $s == "$service" ]] && known=1
done
(( known )) || { fu_bad "$(fu_msg "Not a service this can be used for: %s" "$service")"; return 1; }
if [[ $verb == pam-enable ]]; then
fu_pam_enable "$service"
else
fu_pam_disable "$service"
if [[ $service == lockscreens ]]; then
known=1
mapfile -t services < <(fu_pam_lockers)
fi
(( known )) || { fu_bad "$(fu_msg "Not a service this can be used for: %s" "$service")"; return 1; }
for s in "${services[@]}"; do
if [[ $verb == pam-enable ]]; then
fu_pam_enable "$s" || rc=1
else
fu_pam_disable "$s" || rc=1
fi
done
return $rc
;;
socket-enable)
systemctl enable --now "$FU_UNIT_SOCKET" > /dev/null 2>&1