15 Commits
Author SHA1 Message Date
Felitendo 73f995636b chore: 1.0.1
release / Release and repositories (push) Skipped
release / Install from the APT repository (Kubuntu 26.04) (push) Skipped
release / Install from the APT repository (Debian 13) (push) Skipped
release / Debian package (Kubuntu 26.04) (push) Failing after 1m41s
release / Debian package (Debian 13) (push) Canceled after 0s
release / RPM package (push) Failing after 1m31s
release / Install from the RPM repository (push) Skipped
2026-09-24 01:42:54 +02:00
Felitendo 14119442e7 docs: show the menu and settings as konsole screenshots 2026-09-24 01:39:10 +02:00
Felitendo af72762508 fix: say 1 minute ago, not 1 minutes ago 2026-09-24 01:31:48 +02:00
Felitendo 6e796705d8 docs: drop the distribution versions from the install steps 2026-09-24 01:20:24 +02:00
Felitendo 01aa5dbc74 chore: keep only buy me a coffee in funding 2026-09-24 01:16:02 +02:00
Felitendo 3e0084ae1d docs: add a changelog and the release notes flow 2026-09-24 01:16:02 +02:00
Felitendo 4bb15d9e8f ci: take the release notes from the changelog 2026-09-24 00:16:41 +02:00
Felitendo 49a1a23a31 chore: drop the aur copy, PKGBUILDS has the real one 2026-09-23 23:15:52 +02:00
Felitendo 91dbb6f049 docs: rerender the readme animation at normal speed, lossless at 60 fps 2026-09-23 23:12:17 +02:00
Felitendo 31dae6098f fix: unlock as soon as the face matches 2026-09-23 22:52:33 +02:00
Felitendo 080c1cd0af feat: make the animation speeds a little calmer 2026-09-23 22:48:39 +02:00
Felitendo bcbaaf74bc feat: group the settings and explain the selected one 2026-09-23 22:36:00 +02:00
Felitendo 4db3bcef9c fix: show a camera's name once, not as device: function 2026-09-23 22:36:00 +02:00
Felitendo e371fff7fd feat: close the bubble sooner after the tick 2026-09-23 22:25:29 +02:00
Felitendo 10b4d91eec feat: make the basic photo check the default, no blink needed 2026-09-23 22:20:04 +02:00
30 changed files with 982 additions and 411 deletions

No files matched your search

-14
View File
@@ -1,15 +1 @@
# These are supported funding model platforms
github: Felitendo
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: felitendo
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
+135
View File
@@ -0,0 +1,135 @@
#!/usr/bin/env bash
#
# Builds the GitHub release notes for a tag, the way big projects such as
# Immich lay them out: the hand-written entry from CHANGELOG.md (a welcome,
# the highlights), a support section, then every commit since the last
# release, sorted by kind, and a link to the full changelog.
#
# .github/release-notes.sh v1.2.0 the notes
# .github/release-notes.sh --title v1.2.0 the title (checks the entry exists)
#
# Commit authors come from the GitHub API through gh. Without it the list
# goes without them.
set -euo pipefail
cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.."
title=0
if [[ ${1:-} == --title ]]; then
title=1
shift
fi
tag="${1:?usage: release-notes.sh [--title] vX.Y.Z}"
if ! grep -qx "## $tag" CHANGELOG.md; then
echo "CHANGELOG.md has no entry for $tag. Add \"## $tag\" first." >&2
exit 1
fi
if (( title )); then
printf '%s\n' "$tag"
exit 0
fi
repo="${GITHUB_REPOSITORY:-$(git remote get-url origin | sed -E 's#^.*github\.com[:/]##; s#\.git$##')}"
name="${repo#*/}"
# The entry: up to the next one, without the date line (GitHub shows the
# date), one heading level up, outside code blocks.
entry="$(awk -v h="## $tag" '
$0 == h { on = 1; next }
on && /^## / { exit }
!on { next }
/^```/ { code = !code }
!code && /^_[0-9]{4}-[0-9]{2}-[0-9]{2}_$/ { next }
!code && /^###/ { sub(/^#/, "") }
{ print }
' CHANGELOG.md | sed -e '/./,$!d')"
# The commits: from the last release, or from the start. A tag that does not
# exist yet is a preview of what HEAD would become.
if git rev-parse -q --verify "refs/tags/$tag" > /dev/null; then
target="$tag"
prev="$(git describe --tags --abbrev=0 --match 'v[0-9]*' "$tag^" 2>/dev/null || true)"
else
target="$(git rev-parse HEAD)"
prev="$(git describe --tags --abbrev=0 --match 'v[0-9]*' HEAD 2>/dev/null || true)"
fi
declare -A login=()
if command -v gh > /dev/null; then
if [[ -n $prev ]]; then
api=(api "repos/$repo/compare/$prev...$target" --jq '.commits[] | [.sha, (.author.login // "")] | @tsv')
else
api=(api --paginate "repos/$repo/commits?sha=$target&per_page=100" --jq '.[] | [.sha, (.author.login // "")] | @tsv')
fi
while IFS=$'\t' read -r sha who; do
[[ -n $who ]] && login[$sha]="$who"
done < <(gh "${api[@]}" 2>/dev/null || true)
fi
declare -A list=()
count_maint=0
while IFS=$'\t' read -r sha subject body; do
# Version bumps say nothing a reader needs.
[[ $subject =~ ^(chore:\ )?($name\ )?v?[0-9]+\.[0-9]+\.[0-9]+$ ]] && continue
case "$subject" in
*!:*) kind=breaking ;;
feat:* | feat\(*) kind=feat ;;
fix:* | fix\(*) kind=fix ;;
perf:* | perf\(*) kind=enh ;;
docs:* | docs\(*) kind=docs ;;
chore* | ci:* | ci\(* | build* | refactor* | test* | style*) kind=maint ;;
# Older commits without a prefix, sorted by their first word.
Add\ * | Put\ * | Introduce\ *) kind=feat ;;
Fix\ * | Repair\ * | Recover\ * | Stop\ *) kind=fix ;;
*\ *) kind=enh ;;
*) kind=maint ;;
esac
[[ $body == *"BREAKING CHANGE"* ]] && kind=breaking
[[ $subject == "Initial commit" ]] && kind=maint
line="* $subject"
[[ -n ${login[$sha]:-} ]] && line+=" by @${login[$sha]}"
line+=" in https://github.com/$repo/commit/$sha"
list[$kind]+="$line"$'\n'
[[ $kind == maint ]] && count_maint=$((count_maint + 1))
done < <(git log --reverse --no-merges --format='%H%x09%s%x09%b%x1e' "${prev:+$prev..}$target" | tr '\n\036' ' \n' | sed 's/^ //')
changes=''
for pair in "breaking:🚨 Breaking Changes" "feat:🚀 Features" "enh:🌟 Enhancements" "fix:🐛 Bug fixes" "docs:📚 Documentation"; do
kind="${pair%%:*}"
[[ -n ${list[$kind]:-} ]] || continue
changes+="### ${pair#*:}"$'\n'"${list[$kind]}"
done
if [[ -n ${list[maint]:-} ]]; then
changes+=$'\n'"<details><summary>🧰 Maintenance ($count_maint)</summary>"$'\n\n'"${list[maint]}"$'\n'"</details>"$'\n'
fi
if [[ -n $prev ]]; then
full="**Full Changelog**: https://github.com/$repo/compare/$prev...$tag"
else
full="**Full Changelog**: https://github.com/$repo/commits/$tag"
fi
# A release with highlights is a big one: it gets a heading and the support
# section, as in Immich. A patch is a sentence or two and the list.
if grep -q '^## Highlights$' <<< "$entry"; then
cat <<EOF
# 🚀 $name $tag
$entry
## ☕ Support $name
If $name is useful to you, you can buy me a coffee. It keeps these tools going. Found a bug or have an idea? Tell me in the [issues](https://github.com/$repo/issues).
<a href="https://buymeacoffee.com/felitendo"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="48"></a>
----
EOF
else
printf '%s\n\n' "$entry"
fi
printf "## What's Changed\n%s\n\n%s\n" "$changes" "$full"
+10 -2
View File
@@ -55,6 +55,10 @@ jobs:
- name: Check the tag against the Makefile
run: packaging/check-version.sh "${{ github.ref_name }}"
- name: Check CHANGELOG.md has this release
if: startsWith(github.ref, 'refs/tags/v')
run: .github/release-notes.sh --title "${{ github.ref_name }}"
- run: packaging/build-deb.sh
env:
DEB_SUFFIX: ${{ matrix.suffix }}
@@ -130,6 +134,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
# All tags, for the link to the changes since the last release.
fetch-depth: 0
- uses: actions/download-artifact@v8
with:
@@ -141,9 +148,10 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
run: |
.github/release-notes.sh "${{ github.ref_name }}" > notes.md
gh release create "${{ github.ref_name }}" \
--title "${{ github.ref_name }}" \
--generate-notes \
--title "$(.github/release-notes.sh --title "${{ github.ref_name }}")" \
--notes-file notes.md \
incoming/*.deb incoming/*.rpm \
|| gh release upload "${{ github.ref_name }}" \
incoming/*.deb incoming/*.rpm --clobber
+29
View File
@@ -0,0 +1,29 @@
# Changelog
What each release brings, newest first. The [GitHub releases](https://github.com/LoonixTools/plasma-face-unlock/releases) add every commit that went into it.
## v1.0.1
_2026-09-24_
A small update that makes everyday use smoother. You no longer have to blink: the basic photo check is now the default, and it still stops a photo on a phone, a tablet or glossy paper. If you want the blink back, set **Photo check** to `strict` in the settings.
The settings are now grouped and explain the selected one, the animations are a little calmer, and the lock screen unlocks as soon as your face matches.
## v1.0.0
_2026-09-23_
Welcome to the very first release of plasma-face-unlock! It brings Face ID to KDE Plasma: look at the screen and it unlocks. The lock screen, sudo and admin prompts can all take your face instead of a password.
<p align="center">
<img width="480" alt="The bubble drops down over the lock screen, finds the face and shows a green tick" src="https://raw.githubusercontent.com/LoonixTools/plasma-face-unlock/v1.0.0/res/screenshots/unlock.webp">
</p>
### Highlights
- Unlock the lock screen with your face
- sudo and admin prompts
- A bubble like Face ID
- Photos do not get in
- Packages for Arch, Fedora, Debian and Kubuntu
+50
View File
@@ -24,6 +24,56 @@
- `src/pam`: the PAM module for sudo and admin prompts.
- `src/ctl`: the client the bash code talks to the daemon with.
## Releases
Releases look like the ones of big projects such as Immich. `.github/release-notes.sh <tag>` builds
the notes: the entry from `CHANGELOG.md` (welcome and highlights), a support section, every commit
since the last release sorted by its prefix (`feat`, `fix`, `docs`, ...) with author and link, and
the full changelog link. So commit subjects end up in public: keep them clear.
1. Read `git log <last tag>..HEAD` and pick the version: only fixes → patch, something new → minor,
something that breaks or needs the user to act → major.
2. Bump the version and add the entry at the top of `CHANGELOG.md`, in one commit (`chore: 1.4.0`).
3. Push, then push the tag: `git tag v1.4.0 && git push origin main v1.4.0`. The `release` workflow
builds the packages and creates the release. It stops before building when the entry is missing.
4. PKGBUILDS picks up the new release for the AUR on its own.
A minor or major release (has `### Highlights`, gets a heading and the support section):
```markdown
## v1.4.0
_2026-09-24_
Welcome to plasma-face-unlock `v1.4.0`! One or two sentences on what this release is about.
<p align="center">
<img width="480" alt="What the picture shows" src="https://raw.githubusercontent.com/LoonixTools/plasma-face-unlock/v1.4.0/<path>">
</p>
### 🚨 Breaking changes
- Only if there are any: what changed, and what the user has to do.
### Highlights
- First highlight, a few words
- Second highlight
```
A picture under the welcome is optional. To show the menu or another screen of the program, use a
real screenshot of it running in Konsole, in English. Never a text copy of the screen. The same goes
for the README. The highlights stay a plain list: no heading or text per highlight, the list of
commits explains the rest.
A patch release is just a sentence or two, for example: "A small patch. The menu no longer closes
when you press Enter." The list of commits follows on its own.
- Write for users: what they notice, not how the code does it. Friendly and simple.
- Commands and settings they type go in backticks, buttons and labels in bold.
- To change an old release: edit its entry, commit, then
`gh release edit <tag> --title <tag> --notes "$(.github/release-notes.sh <tag>)"`.
## Testing
Never test against the real setup: enrolling and the PAM files belong to the user's machine.
+1 -1
View File
@@ -6,7 +6,7 @@
# installed by the Makefile, which is where the paths come from.
cmake_minimum_required(VERSION 3.22)
project(plasma-face-unlock VERSION 1.0.0 LANGUAGES C CXX)
project(plasma-face-unlock VERSION 1.0.1 LANGUAGES C CXX)
set(PFU_VERSION "${PROJECT_VERSION}" CACHE STRING "Version reported by every binary")
+1 -1
View File
@@ -9,7 +9,7 @@
# Overridable so a packager can pass the version it is actually building
# (`make VERSION=$pkgver`). The literal below is the fallback for builds
# straight from a checkout, and is what a release tag has to carry.
VERSION ?= 1.0.0
VERSION ?= 1.0.1
PREFIX ?= /usr
DESTDIR ?=
+27 -28
View File
@@ -7,7 +7,7 @@
<h3 align="center">Face ID for KDE Plasma.</h3>
<p align="center">
Look at the screen and it unlocks: the lock screen, sudo and admin prompts. A photo of you is not enough.
Look at the screen and it unlocks: the lock screen, sudo and admin prompts. A photo on a phone does not fool it.
</p>
<h5 align="center">
@@ -22,7 +22,7 @@
</p>
<p align="center">
<img src="res/screenshots/unlock.webp" alt="The bubble drops down over the lock screen, the face in it looks around, asks for a blink, and two green rings spin and land around a tick" width="480">
<img src="res/screenshots/unlock.webp" alt="The bubble drops down over the lock screen, the face in it looks around, and two green rings spin and land around a tick" width="480">
</p>
<p align="center">
@@ -41,7 +41,7 @@ your computer, and your face is saved as numbers, never as a picture.
yay -S plasma-face-unlock
```
**Fedora 44**
**Fedora**
```bash
sudo curl -fsSL -o /etc/yum.repos.d/plasma-face-unlock.repo \
@@ -49,29 +49,20 @@ sudo curl -fsSL -o /etc/yum.repos.d/plasma-face-unlock.repo \
sudo dnf install plasma-face-unlock
```
**Debian 13**
**Debian, Kubuntu**
```bash
codename="$(sed -n 's/^VERSION_CODENAME=//p' /etc/os-release)"
sudo install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://loonixtools.github.io/plasma-face-unlock/KEY.gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/plasma-face-unlock.gpg
echo "deb [signed-by=/etc/apt/keyrings/plasma-face-unlock.gpg] https://loonixtools.github.io/plasma-face-unlock/deb/trixie ./" \
echo "deb [signed-by=/etc/apt/keyrings/plasma-face-unlock.gpg] https://loonixtools.github.io/plasma-face-unlock/deb/$codename ./" \
| sudo tee /etc/apt/sources.list.d/plasma-face-unlock.list
sudo apt update && sudo apt install plasma-face-unlock
```
**Kubuntu 26.04**
```bash
sudo install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://loonixtools.github.io/plasma-face-unlock/KEY.gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/plasma-face-unlock.gpg
echo "deb [signed-by=/etc/apt/keyrings/plasma-face-unlock.gpg] https://loonixtools.github.io/plasma-face-unlock/deb/resolute ./" \
| sudo tee /etc/apt/sources.list.d/plasma-face-unlock.list
sudo apt update && sudo apt install plasma-face-unlock
```
Updates then come with your normal system updates.
The packages are built for the current release of each. Updates then come with your normal system
updates.
You need Plasma 6 on Wayland and a camera. Infrared cameras (the Windows Hello kind) work too.
@@ -81,16 +72,22 @@ You need Plasma 6 on Wayland and a camera. Infrared cameras (the Windows Hello k
plasma-face-unlock
```
This opens a menu. Press **1** and follow the setup window: look at the camera, then turn your head
slowly in a circle until the ring is full. Then lock the screen and look at it.
This opens a menu:
| Key | |
|---|---|
| **1** | Turn face unlock on or off |
| **2** | Add another face, for example with glasses |
| **3** | Rename, turn off or delete faces |
| **4** | Settings: sudo, admin prompts, photo check, camera, bubble style, animation speed and more |
| **5** | One test scan that shows what the camera sees. Try this first when something does not work. |
<p align="center">
<img src="res/screenshots/menu.png" alt="The plasma-face-unlock menu in Konsole: face unlock on, one face, lock screen, sudo and admin prompts on" width="560">
</p>
Press **1** and follow the setup window: look at the camera, then turn your head slowly in a circle
until the ring is full. Then lock the screen and look at it. **2** adds another face, for example
with glasses, and **5** runs one test scan that shows what the camera sees. Try that first when
something does not work.
**4** opens the settings. The text at the bottom says what the selected one does:
<p align="center">
<img src="res/screenshots/settings.png" alt="The settings in Konsole, grouped into lock screen, password prompts, recognition and bubble, with the photo check explained at the bottom" width="680">
</p>
Without the menu: `plasma-face-unlock enable`, `disable`, `setup [NAME]`, `faces`, `remove ID`,
`test` and `status`.
@@ -100,8 +97,10 @@ Without the menu: `plasma-face-unlock enable`, `disable`, `setup [NAME]`, `faces
It is a convenience, not a security upgrade. Face ID on a phone sees your face in 3D. A webcam only
sees a flat picture.
- A photo does not get in: the face has to blink or turn a little, and a photo can do neither.
- A phone or tablet held up to the camera is caught by its reflection and its straight edges.
- You do not have to blink. A photo on a phone or tablet, or a glossy print, is still refused:
it gives itself away by its reflection and its straight edges.
- A matte printed photo can get past that. Set the photo check to *strict* in the settings: then
the face has to blink or turn a little, and a photo can do neither.
- A video of you can still get in.
- Five failed tries in a row pause it for 15 minutes, or until you use your password.
- Only root can read your face data. Adding or deleting a face always needs your password.
+11 -7
View File
@@ -12,8 +12,8 @@ plasma-face-unlock - face unlock for KDE Plasma
Look at the screen and it unlocks, the way a phone does. The lock screen, sudo
in a terminal and the admin password prompts of Plasma can all take a face
instead of a password. Before a face counts, it has to show a sign of life, so
holding up a photo of somebody is not enough.
instead of a password. A photo of somebody on a phone or tablet does not get
in, and with the strict photo check a printed one does not either.
A bubble at the top of the screen shows what is going on: it drops down when
the camera starts looking, the face in it looks around, and when it recognises
@@ -100,7 +100,7 @@ threshold count, so the samples cannot drift towards somebody else one
borderline unlock at a time.
A match has to hold for two frames. The face has to look at the screen with
its eyes open (*Only while looking at the screen*), and it has to be the same
its eyes open (*Only when you look at the screen*), and it has to be the same
face that shows the sign of life: a face that jumps in the picture, disappears
or stops matching starts the whole check again.
@@ -128,14 +128,18 @@ Confirm cues are evidence of a real head. *strict* needs one of them:
real turn, measured without the nose's own jitter, and the face has to
narrow no more than a head that turned that far would.
*basic* uses the deny cues only. *off* checks nothing and is only for trying
out a camera.
*basic*, the default, uses the deny cues only: nobody has to blink, and a
phone, a tablet or a glossy print held up is still refused. A matte printed
photo is not, so *strict* is the one to pick when that matters. *off* checks
nothing and is only for trying out a camera.
# HOW SAFE IS THIS
A webcam sees a flat picture. A phone's face unlock builds a depth map with a
projector and an infrared camera; this cannot. What the photo check does:
- a photo on a phone or tablet, or a glossy print, is refused by *basic*, the
default. A matte printed photo can get past *basic*;
- a photo, printed or on a screen, held up and turned any way, is refused by
*strict*;
- a photo curled strongly and turned a lot can pass the head turn cue some of
@@ -176,7 +180,7 @@ scans when somebody comes back:
field is a key like any other. This works while a video or an app keeps the
screen on, too (ext_idle_notifier_v1, input notification);
- when the machine wakes from sleep;
- right after locking, if *Look right after the screen locks* is on. Off by
- right after locking, if *Scan right after locking* is on. Off by
default: whoever locks their screen on purpose is usually still in front of
it.
@@ -224,7 +228,7 @@ emitter has to be switched on with a tool such as linux-enable-ir-emitter.
In infrared a phone screen shows up black, which makes the photo check's job
easier. A face set up with one camera should be set up again for another.
A laptop with its lid shut is not scanned (*Not while the lid is closed*).
A laptop with its lid shut is not scanned (*Not when the lid is closed*).
# FILES
+8 -5
View File
@@ -8,12 +8,14 @@ description of the layout, and two descriptions drift.
|---|---|
| `deb/control`, `deb/copyright` | metadata for the Debian binary package |
| `rpm/plasma-face-unlock.spec` | the RPM spec |
| `aur/PKGBUILD`, `aur/plasma-face-unlock.install` | the AUR package |
| `build-deb.sh`, `build-rpm.sh` | build one package into `dist/` |
| `check-version.sh` | refuses a tag that disagrees with the Makefile |
| `publish-repos.sh` | regenerates the APT and RPM repositories |
| `pages/` | the landing page and the `.repo` file served from GitHub Pages |
The AUR package lives in [Felitendo/PKGBUILDS](https://github.com/Felitendo/PKGBUILDS/tree/main/plasma-face-unlock).
Its CI notices a new GitHub release, updates the checksum and pushes to the AUR.
Unlike the shell-only LoonixTools, this one is compiled. The packages are per
architecture (amd64 and x86_64), and they need the Plasma 6 and Qt 6
development packages to build: Debian 13 (trixie) and current Fedora have
@@ -51,12 +53,13 @@ argument.
1. Bump `VERSION` in the Makefile. The compiled programs get it from there
too (`-DPFU_VERSION`).
2. Commit, then `git tag vX.Y.Z && git push --tags`.
2. Add the release to `CHANGELOG.md`, in the format CLAUDE.md describes.
3. Commit, then `git tag vX.Y.Z && git push --tags`.
The `release` workflow builds both packages in a Debian and a Fedora container,
refuses the tag if it disagrees with the Makefile, attaches the packages to a
GitHub release, and adds them to the APT and RPM repositories on the `gh-pages`
branch.
refuses the tag if it disagrees with the Makefile or has no changelog entry,
attaches the packages to a GitHub release with the entry as its notes, and adds
them to the APT and RPM repositories on the `gh-pages` branch.
## Trying the release path first
-35
View File
@@ -1,35 +0,0 @@
# Maintainer: Felitendo
#
# The PKGBUILD for the AUR, kept here next to the code it builds. The copy in
# github.com/Felitendo/PKGBUILDS is the one CI bumps and pushes.
pkgname=plasma-face-unlock
pkgver=1.0.0
pkgrel=1
pkgdesc="Face ID for KDE Plasma: the lock screen, sudo and admin prompts by face, with a photo check"
arch=('x86_64' 'aarch64')
url="https://github.com/LoonixTools/plasma-face-unlock"
license=('GPL-3.0-or-later' 'MIT' 'Apache-2.0')
depends=('bash' 'coreutils' 'gawk' 'grep' 'sed' 'gettext' 'systemd' 'systemd-libs' 'pam' 'polkit'
'opencv' 'qt6-base' 'qt6-declarative' 'layer-shell-qt' 'ki18n' 'kscreenlocker')
makedepends=('cmake' 'scdoc')
install="${pkgname}.install"
source=("${pkgname}-${pkgver}.tar.gz::${url}/archive/refs/tags/v${pkgver}.tar.gz"
"face_detection_yunet_2023mar.onnx::https://github.com/opencv/opencv_zoo/raw/main/models/face_detection_yunet/face_detection_yunet_2023mar.onnx"
"face_recognition_sface_2021dec.onnx::https://github.com/opencv/opencv_zoo/raw/main/models/face_recognition_sface/face_recognition_sface_2021dec.onnx")
noextract=('face_detection_yunet_2023mar.onnx' 'face_recognition_sface_2021dec.onnx')
sha256sums=('SKIP'
'8f2383e4dd3cfbb4553ea8718107fc0423210dc964f9f4280604804ed2552fa4'
'0ba9fbfa01b5270c96627c4ef784da859931e02f04419c829e83484087c34e79')
build() {
make -C "${pkgname}-${pkgver}" VERSION="$pkgver"
}
check() {
make -C "${pkgname}-${pkgver}" VERSION="$pkgver" test
}
package() {
make -C "${pkgname}-${pkgver}" VERSION="$pkgver" MODELS_SRC="$srcdir" DESTDIR="$pkgdir" install
}
-29
View File
@@ -1,29 +0,0 @@
post_install() {
cat <<'MSG'
plasma-face-unlock is installed but not active yet.
plasma-face-unlock interactive menu
plasma-face-unlock enable set up your face and turn it on
Run it as your own user, not with sudo. It asks for your password when it
needs to, and sudo and admin prompts stay with the password until you switch
them on under Settings.
MSG
}
pre_remove() {
cat <<'MSG'
Before removing this package, run
plasma-face-unlock disable
as each user that turned it on. That takes face unlock back out of sudo and
polkit. (Removing it without that is safe too: the PAM line is written so
that a missing module is skipped, and sudo keeps asking for the password.)
MSG
systemctl disable --now plasma-face-unlockd.socket plasma-face-unlockd.service >/dev/null 2>&1 || true
}
+6 -4
View File
@@ -13,10 +13,12 @@ Description: face unlock for KDE Plasma
instead of a password. A bubble at the top of the screen, above the lock
screen too, shows the face being looked for, recognised or refused.
.
Before a face counts it has to show a sign of life (a blink, or the nose
moving the way a real nose does when the head turns), so a photo held up to
the camera is not enough. It is a convenience, not a security upgrade: a
webcam sees a flat picture, and a video of the person can get through.
A photo on a phone, a tablet or a glossy print is refused by its
reflection and its straight edges. The strict photo check also wants a sign
of life (a blink, or the nose moving the way a real nose does when the head
turns), which stops a printed photo too. It is a convenience, not a security
upgrade: a webcam sees a flat picture, and a video of the person can get
through.
.
Everything runs on the machine. Faces are stored as numbers readable only by
root, never as pictures. Run "plasma-face-unlock disable" before removing
+5 -4
View File
@@ -1,6 +1,6 @@
# Built with `packaging/build-rpm.sh`, which passes the version in rather than
# editing this file: the Makefile is where the version is written down.
%global upstream_version %{?_version}%{!?_version:1.0.0}
%global upstream_version %{?_version}%{!?_version:1.0.1}
Name: plasma-face-unlock
Version: %{upstream_version}
@@ -53,9 +53,10 @@ in a terminal and the admin password prompts of Plasma can take a face instead
of a password. A bubble at the top of the screen, above the lock screen too,
shows the face being looked for, recognised or refused.
Before a face counts it has to show a sign of life (a blink, or the nose moving
the way a real nose does when the head turns), so a photo held up to the camera
is not enough. It is a convenience, not a security upgrade: a webcam sees a
A photo on a phone, a tablet or a glossy print is refused by its reflection and
its straight edges. The strict photo check also wants a sign of life (a blink,
or the nose moving the way a real nose does when the head turns), which stops a
printed photo too. It is a convenience, not a security upgrade: a webcam sees a
flat picture, and a video of the person can get through.
Run "plasma-face-unlock disable" before removing this package, so that sudo
+316 -136
View File
@@ -7,8 +7,8 @@ msgstr ""
"Project-Id-Version: plasma-face-unlock 1.0.0\n"
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/plasma-face-unlock/"
"issues\n"
"POT-Creation-Date: 2026-09-22 18:45+0200\n"
"PO-Revision-Date: 2026-09-22 18:45+0200\n"
"POT-Creation-Date: 2026-09-24 01:31+0200\n"
"PO-Revision-Date: 2026-09-23 22:32+0200\n"
"Last-Translator: Felitendo\n"
"Language-Team: German\n"
"Language: de\n"
@@ -20,7 +20,9 @@ msgstr ""
msgid ""
"Face unlock's service has to be switched on once for this computer. That "
"needs your password."
msgstr "Der Dienst für die Gesichtsentsperrung muss auf diesem Computer einmal eingeschaltet werden. Dafür wird dein Passwort gebraucht."
msgstr ""
"Der Dienst für die Gesichtsentsperrung muss auf diesem Computer einmal "
"eingeschaltet werden. Dafür wird dein Passwort gebraucht."
#: src/plasma-face-unlock:57
#, sh-printf-format
@@ -31,7 +33,9 @@ msgstr "Prüfen mit: systemctl status %s"
msgid ""
"Setting up a face needs the camera picture on screen. Run this inside your "
"Plasma session."
msgstr "Zum Einrichten eines Gesichts muss das Kamerabild auf dem Bildschirm zu sehen sein. Starte das in deiner Plasma-Sitzung."
msgstr ""
"Zum Einrichten eines Gesichts muss das Kamerabild auf dem Bildschirm zu "
"sehen sein. Starte das in deiner Plasma-Sitzung."
#: src/plasma-face-unlock:71
msgid "The setup window is open. Follow it there."
@@ -44,7 +48,9 @@ msgstr "Das Gesicht ist eingerichtet."
#: src/plasma-face-unlock:83
#, sh-printf-format
msgid "Face unlock is still off. Turn it on with [1] or `%s enable`."
msgstr "Die Gesichtsentsperrung ist noch aus. Schalte sie mit [1] oder `%s enable` ein."
msgstr ""
"Die Gesichtsentsperrung ist noch aus. Schalte sie mit [1] oder `%s enable` "
"ein."
#: src/plasma-face-unlock:87
msgid "No face was added."
@@ -54,7 +60,8 @@ msgstr "Es wurde kein Gesicht hinzugefügt."
msgid "First, set up your face."
msgstr "Richte zuerst dein Gesicht ein."
#: src/plasma-face-unlock:100 src/plasma-face-unlock:120 src/lib/menu.sh:338
#: src/plasma-face-unlock:100 src/plasma-face-unlock:120 src/lib/menu.sh:406
#: src/lib/menu.sh:410 src/lib/menu.sh:416 src/lib/menu.sh:418
msgid "Could not save the setting."
msgstr "Einstellung konnte nicht gespeichert werden."
@@ -64,11 +71,15 @@ msgstr "Der Sperrbildschirm-Dienst konnte nicht gestartet werden."
#: src/plasma-face-unlock:106
msgid "No systemd user session, so the lock screen agent was not started."
msgstr "Keine systemd-Benutzersitzung, daher wurde der Sperrbildschirm-Dienst nicht gestartet."
msgstr ""
"Keine systemd-Benutzersitzung, daher wurde der Sperrbildschirm-Dienst nicht "
"gestartet."
#: src/plasma-face-unlock:113
msgid "Face unlock is on. Lock the screen and look at it to try."
msgstr "Die Gesichtsentsperrung ist an. Sperre den Bildschirm und schau ihn an, um es auszuprobieren."
msgstr ""
"Die Gesichtsentsperrung ist an. Sperre den Bildschirm und schau ihn an, um "
"es auszuprobieren."
#: src/plasma-face-unlock:115
msgid "It can do sudo and admin prompts too. See Settings."
@@ -76,22 +87,24 @@ msgstr "Sie kann auch sudo und Admin-Abfragen übernehmen. Siehe Einstellungen."
#: src/plasma-face-unlock:130
msgid "Face unlock is off. Your faces are kept; delete them under Faces."
msgstr "Die Gesichtsentsperrung ist aus. Deine Gesichter bleiben erhalten; löschen kannst du sie unter Gesichter."
msgstr ""
"Die Gesichtsentsperrung ist aus. Deine Gesichter bleiben erhalten; löschen "
"kannst du sie unter Gesichter."
#: src/plasma-face-unlock:144 src/lib/daemon.sh:109
msgid "No face is set up yet."
msgstr "Es ist noch kein Gesicht eingerichtet."
#: src/plasma-face-unlock:148 src/lib/menu.sh:157 src/lib/menu.sh:460
#: src/plasma-face-unlock:148 src/lib/menu.sh:167 src/lib/menu.sh:563
msgid "on"
msgstr "an"
#: src/plasma-face-unlock:148 src/lib/menu.sh:159 src/lib/menu.sh:169
#: src/lib/menu.sh:461
#: src/plasma-face-unlock:148 src/lib/menu.sh:169 src/lib/menu.sh:179
#: src/lib/menu.sh:564
msgid "off"
msgstr "aus"
#: src/plasma-face-unlock:150 src/lib/menu.sh:478
#: src/plasma-face-unlock:150 src/lib/menu.sh:581
#, sh-printf-format
msgid "%s samples, %s learned"
msgstr "%s Aufnahmen, %s dazugelernt"
@@ -121,7 +134,7 @@ msgstr "Gesichtsentsperrung einschalten"
msgid "Turn it off (faces are kept)"
msgstr "Ausschalten (Gesichter bleiben erhalten)"
#: src/plasma-face-unlock:176 src/lib/menu.sh:540
#: src/plasma-face-unlock:176 src/lib/menu.sh:646
msgid "Add a face"
msgstr "Gesicht hinzufügen"
@@ -157,7 +170,9 @@ msgstr "Ohne Befehl wird ein interaktives Menü angezeigt."
msgid ""
"Run this as your own user, not as root. It asks for your password when it "
"needs to."
msgstr "Starte das als dein eigener Benutzer, nicht als root. Wenn nötig, wird nach deinem Passwort gefragt."
msgstr ""
"Starte das als dein eigener Benutzer, nicht als root. Wenn nötig, wird nach "
"deinem Passwort gefragt."
#: src/plasma-face-unlock:221 src/lib/system.sh:89
#, sh-printf-format
@@ -172,17 +187,29 @@ msgstr "nie"
msgid "just now"
msgstr "gerade eben"
#: src/lib/common.sh:173
#: src/lib/common.sh:172
msgid "1 minute ago"
msgstr "vor 1 Minute"
#: src/lib/common.sh:175
#, sh-printf-format
msgid "%d minutes ago"
msgstr "vor %d Minuten"
#: src/lib/common.sh:176
#: src/lib/common.sh:177
msgid "1 hour ago"
msgstr "vor 1 Stunde"
#: src/lib/common.sh:180
#, sh-printf-format
msgid "%d hours ago"
msgstr "vor %d Stunden"
#: src/lib/common.sh:179
#: src/lib/common.sh:182
msgid "1 day ago"
msgstr "vor 1 Tag"
#: src/lib/common.sh:185
#, sh-printf-format
msgid "%d days ago"
msgstr "vor %d Tagen"
@@ -214,13 +241,15 @@ msgstr "Kein Gesicht zu sehen."
#: src/lib/daemon.sh:108
msgid ""
"Face unlock is paused after too many tries. Unlock once with your password."
msgstr "Die Gesichtsentsperrung ist nach zu vielen Versuchen pausiert. Entsperre einmal mit deinem Passwort."
msgstr ""
"Die Gesichtsentsperrung ist nach zu vielen Versuchen pausiert. Entsperre "
"einmal mit deinem Passwort."
#: src/lib/daemon.sh:110
msgid "The camera could not be used."
msgstr "Die Kamera konnte nicht benutzt werden."
#: src/lib/daemon.sh:111 src/lib/menu.sh:233
#: src/lib/daemon.sh:111 src/lib/menu.sh:246
msgid "The recognition models are missing. Reinstall the package."
msgstr "Die Erkennungsmodelle fehlen. Installiere das Paket neu."
@@ -315,175 +344,315 @@ msgstr "Lebenszeichen: %s"
msgid ""
"That was too many tries. Face unlock is paused until you unlock with your "
"password."
msgstr "Das waren zu viele Versuche. Die Gesichtsentsperrung ist pausiert, bis du mit deinem Passwort entsperrst."
msgstr ""
"Das waren zu viele Versuche. Die Gesichtsentsperrung ist pausiert, bis du "
"mit deinem Passwort entsperrst."
#: src/lib/menu.sh:130
#: src/lib/menu.sh:140
msgid "[y/N]"
msgstr "[j/N]"
#: src/lib/menu.sh:149 src/lib/menu.sh:382
#: src/lib/menu.sh:159 src/lib/menu.sh:467
msgid "ON"
msgstr "AN"
#: src/lib/menu.sh:151 src/lib/menu.sh:383
#: src/lib/menu.sh:161 src/lib/menu.sh:468
msgid "OFF"
msgstr "AUS"
#: src/lib/menu.sh:167
msgid "strict (blink or turn your head)"
msgstr "streng (blinzeln oder Kopf drehen)"
#: src/lib/menu.sh:168
msgid "basic (screens and phone edges)"
msgstr "einfach (Bildschirme und Handyränder)"
#: src/lib/menu.sh:170
msgid "normal"
msgstr "normal"
#: src/lib/menu.sh:171
#: src/lib/menu.sh:177 src/lib/menu.sh:181
msgid "strict"
msgstr "streng"
#: src/lib/menu.sh:172
#: src/lib/menu.sh:178
msgid "basic"
msgstr "einfach"
#: src/lib/menu.sh:180 src/lib/menu.sh:185
msgid "normal"
msgstr "normal"
#: src/lib/menu.sh:182
msgid "relaxed"
msgstr "locker"
#: src/lib/menu.sh:173
#: src/lib/menu.sh:183
msgid "island with the face"
msgstr "Insel mit Gesicht"
#: src/lib/menu.sh:174
#: src/lib/menu.sh:184
msgid "small pill with a lock"
msgstr "kleine Pille mit Schloss"
#: src/lib/menu.sh:175
#: src/lib/menu.sh:186
msgid "fast"
msgstr "schnell"
#: src/lib/menu.sh:187
msgid "slow"
msgstr "langsam"
#: src/lib/menu.sh:188
#, sh-printf-format
msgid "%s seconds"
msgstr "%s Sekunden"
#: src/lib/menu.sh:176 src/lib/menu.sh:313
#: src/lib/menu.sh:189 src/lib/menu.sh:381
msgid "automatic"
msgstr "automatisch"
#: src/lib/menu.sh:193
#: src/lib/menu.sh:206
msgid "Face unlock"
msgstr "Gesichtsentsperrung"
#: src/lib/menu.sh:197
#: src/lib/menu.sh:210
msgid "Service"
msgstr "Dienst"
#: src/lib/menu.sh:197
#: src/lib/menu.sh:210
msgid "not running"
msgstr "läuft nicht"
#: src/lib/menu.sh:199
#: src/lib/menu.sh:212
msgid "Turning face unlock on again starts it."
msgstr "Die Gesichtsentsperrung erneut einzuschalten startet ihn."
#: src/lib/menu.sh:210 src/lib/menu.sh:212 src/lib/menu.sh:457
#: src/lib/menu.sh:541
#: src/lib/menu.sh:223 src/lib/menu.sh:225 src/lib/menu.sh:560
#: src/lib/menu.sh:647
msgid "Faces"
msgstr "Gesichter"
#: src/lib/menu.sh:210
#: src/lib/menu.sh:223
msgid "none set up yet"
msgstr "noch keins eingerichtet"
#: src/lib/menu.sh:212
#: src/lib/menu.sh:225
msgid "all turned off"
msgstr "alle ausgeschaltet"
#: src/lib/menu.sh:218
#: src/lib/menu.sh:231
msgid "none found"
msgstr "keine gefunden"
#: src/lib/menu.sh:220 /tmp/tmp.q9gMIu3Tjj/settings.sh:9
#: src/lib/menu.sh:233 /tmp/tmp.3ElRvSMg8S/settings.sh:12
msgid "Camera"
msgstr "Kamera"
#: src/lib/menu.sh:222
#: src/lib/menu.sh:235 /tmp/tmp.3ElRvSMg8S/settings.sh:1
msgid "Lock screen"
msgstr "Sperrbildschirm"
#: src/lib/menu.sh:223
#: src/lib/menu.sh:236
msgid "sudo"
msgstr "sudo"
#: src/lib/menu.sh:224
#: src/lib/menu.sh:237 /tmp/tmp.3ElRvSMg8S/settings.sh:7
msgid "Admin prompts"
msgstr "Admin-Abfragen"
#: src/lib/menu.sh:225 /tmp/tmp.q9gMIu3Tjj/settings.sh:6
#: src/lib/menu.sh:238 /tmp/tmp.3ElRvSMg8S/settings.sh:9
msgid "Photo check"
msgstr "Foto-Prüfung"
#: src/lib/menu.sh:228
#: src/lib/menu.sh:241
msgid "Paused"
msgstr "Pausiert"
#: src/lib/menu.sh:228
#: src/lib/menu.sh:241
#, sh-printf-format
msgid "for %d more minutes, or until the password is used"
msgstr "noch %d Minuten, oder bis das Passwort benutzt wird"
#: src/lib/menu.sh:230
#: src/lib/menu.sh:243
msgid "Last unlock"
msgstr "Letzte Entsperrung"
#: src/lib/menu.sh:309
#: src/lib/menu.sh:287
msgid ""
"Unlocks the lock screen when it sees your face. Off: only your password "
"works there."
msgstr ""
"Entsperrt den Sperrbildschirm, wenn er dein Gesicht sieht. Aus: Dort geht "
"nur dein Passwort."
#: src/lib/menu.sh:288
msgid ""
"Scans when you press a key or move the mouse on the lock screen, and when "
"the computer wakes up."
msgstr ""
"Scannt, wenn du auf dem Sperrbildschirm eine Taste drückst oder die Maus "
"bewegst, und wenn der Computer aufwacht."
#: src/lib/menu.sh:289
msgid ""
"Scans as soon as the screen locks. Off by default: if you lock it yourself, "
"it would unlock again right away."
msgstr ""
"Scannt, sobald der Bildschirm gesperrt wird. Normalerweise aus: Sperrst du "
"selbst, würde er gleich wieder entsperren."
#: src/lib/menu.sh:290
msgid ""
"sudo takes your face instead of the password. No match: you type the "
"password as usual."
msgstr ""
"sudo nimmt dein Gesicht statt des Passworts. Passt es nicht, tippst du wie "
"gewohnt dein Passwort."
#: src/lib/menu.sh:291
msgid ""
"The password windows of Plasma and apps, for example when you install "
"software. No match: you type the password."
msgstr ""
"Die Passwort-Fenster von Plasma und Apps, zum Beispiel beim Installieren. "
"Passt das Gesicht nicht, tippst du dein Passwort."
#: src/lib/menu.sh:292
msgid ""
"You have to blink or turn your head a little. This also stops printed photos."
msgstr ""
"Du musst blinzeln oder den Kopf leicht drehen. Das hält auch ausgedruckte "
"Fotos auf."
#: src/lib/menu.sh:293
msgid "No check at all. Only for trying out a camera."
msgstr "Keine Prüfung. Nur zum Ausprobieren einer Kamera."
#: src/lib/menu.sh:294
msgid ""
"Stops photos on a phone, a tablet or glossy paper. You do not have to blink. "
"A matte printed photo can get through."
msgstr ""
"Hält Fotos auf Handy, Tablet oder Hochglanzpapier auf. Blinzeln ist nicht "
"nötig. Ein matter Ausdruck kann durchkommen."
#: src/lib/menu.sh:295
msgid ""
"Fewer wrong matches, but it may not know you with glasses or in bad light."
msgstr ""
"Seltener Verwechslungen, aber mit Brille oder bei schlechtem Licht erkennt "
"es dich vielleicht nicht."
#: src/lib/menu.sh:296
msgid "Knows you more easily, but also somebody who looks a lot like you."
msgstr "Erkennt dich leichter, aber auch jemanden, der dir sehr ähnlich sieht."
#: src/lib/menu.sh:297
msgid "The default. Right for most people."
msgstr "Der Standard. Passt für die meisten."
#: src/lib/menu.sh:298
msgid ""
"Your eyes have to be open and on the screen. So it does not unlock while you "
"look away or sleep."
msgstr ""
"Deine Augen müssen offen sein und auf den Bildschirm schauen. So entsperrt "
"es nicht, wenn du wegschaust oder schläfst."
#: src/lib/menu.sh:299
msgid ""
"Automatic takes the first normal camera. After a change, set up your face "
"again. An infrared camera needs its light on (linux-enable-ir-emitter)."
msgstr ""
"Automatisch nimmt die erste normale Kamera. Nach einem Wechsel dein Gesicht "
"neu einrichten. Eine Infrarot-Kamera braucht ihr Licht (linux-enable-ir-"
"emitter)."
#: src/lib/menu.sh:300
msgid "How long the camera looks for your face before it gives up."
msgstr "Wie lange die Kamera nach deinem Gesicht sucht, bevor sie aufgibt."
#: src/lib/menu.sh:301
msgid ""
"After a sure match it keeps how you look now. So a new haircut or glasses "
"need no new setup."
msgstr ""
"Nach einem sicheren Treffer merkt es sich, wie du gerade aussiehst. So musst "
"du nach neuer Frisur oder Brille nichts neu einrichten."
#: src/lib/menu.sh:302
msgid ""
"No scan while the laptop is closed, for example at a desk with an external "
"screen."
msgstr ""
"Kein Scan bei zugeklapptem Laptop, zum Beispiel am Schreibtisch mit externem "
"Bildschirm."
#: src/lib/menu.sh:303
msgid "The bubble at the top of the screen shows what the camera is doing."
msgstr "Die Bubble oben am Bildschirm zeigt, was die Kamera gerade macht."
#: src/lib/menu.sh:304
msgid "A small pill with a lock that opens."
msgstr "Eine kleine Pille mit einem Schloss, das aufgeht."
#: src/lib/menu.sh:305
msgid "An island with a face that looks around, then rings and a tick."
msgstr ""
"Eine Insel mit einem Gesicht, das sich umschaut, danach Ringe und ein Haken."
#: src/lib/menu.sh:306
msgid "How fast the bubble moves."
msgstr "Wie schnell sich die Bubble bewegt."
#: src/lib/menu.sh:307
msgid "Shows the bubble when sudo or an admin prompt scans your face, too."
msgstr ""
"Zeigt die Bubble auch, wenn sudo oder eine Admin-Abfrage dein Gesicht scannt."
#: src/lib/menu.sh:377
#, sh-printf-format
msgid "automatic (%s)"
msgstr "automatisch (%s)"
#: src/lib/menu.sh:319
#: src/lib/menu.sh:387
msgid "(infrared)"
msgstr "(Infrarot)"
#: src/lib/menu.sh:323
#: src/lib/menu.sh:391
msgid "(not connected)"
msgstr "(nicht angeschlossen)"
#: src/lib/menu.sh:379 src/lib/menu.sh:542
#: src/lib/menu.sh:464 src/lib/menu.sh:648
msgid "Settings"
msgstr "Einstellungen"
#: src/lib/menu.sh:380
msgid "Up/Down: select, Space or Right: change, q: back"
msgstr "Hoch/Runter: wählen, Leertaste oder Rechts: ändern, q: zurück"
#: src/lib/menu.sh:465
msgid "↑↓ select ←→ or Space: change q: back"
msgstr "↑↓ auswählen ←→ oder Leertaste: ändern q: zurück"
#: src/lib/menu.sh:381
msgid "Settings marked * are for the whole computer and ask for your password."
msgstr "Mit * markierte Einstellungen gelten für den ganzen Computer und fragen nach deinem Passwort."
#: src/lib/menu.sh:466
msgid "for all users, asks for your password"
msgstr "für alle Nutzer, fragt nach deinem Passwort"
#: src/lib/menu.sh:458
#: src/lib/menu.sh:561
msgid "Up/Down: select, Space: on/off, r: rename, d: delete, a: add, q: back"
msgstr "Hoch/Runter: wählen, Leertaste: an/aus, r: umbenennen, d: löschen, a: hinzufügen, q: zurück"
msgstr ""
"Hoch/Runter: wählen, Leertaste: an/aus, r: umbenennen, d: löschen, a: "
"hinzufügen, q: zurück"
#: src/lib/menu.sh:459
#: src/lib/menu.sh:562
msgid "No face is set up yet. Press a to add one."
msgstr "Es ist noch kein Gesicht eingerichtet. Drück a, um eins hinzuzufügen."
#: src/lib/menu.sh:502
#: src/lib/menu.sh:607
msgid "New name:"
msgstr "Neuer Name:"
#: src/lib/menu.sh:509
#: src/lib/menu.sh:614
#, sh-printf-format
msgid "Delete \"%s\"?"
msgstr "„%s“ löschen?"
#: src/lib/menu.sh:539
#: src/lib/menu.sh:645
msgid "Turn face unlock on or off"
msgstr "Gesichtsentsperrung ein- oder ausschalten"
#: src/lib/menu.sh:543
#: src/lib/menu.sh:649
msgid "Try it"
msgstr "Ausprobieren"
#: src/lib/menu.sh:544
#: src/lib/menu.sh:650
msgid "Quit"
msgstr "Beenden"
@@ -499,7 +668,8 @@ msgstr "Auf diesem System gibt es keine PAM-Konfiguration für %s."
#: src/lib/system.sh:47
msgid "This needs root, and neither sudo, run0 nor doas is installed."
msgstr "Dafür braucht es root, aber weder sudo noch run0 noch doas ist installiert."
msgstr ""
"Dafür braucht es root, aber weder sudo noch run0 noch doas ist installiert."
#: src/lib/system.sh:56
msgid "This command has to run as root."
@@ -516,86 +686,98 @@ msgid "Not a service this can be used for: %s"
msgstr "Dafür kann es nicht verwendet werden: %s"
#: src/lib/menu.sh
msgid "Unlock the lock screen"
msgstr "Sperrbildschirm entsperren"
msgid "Unlock with your face"
msgstr "Mit dem Gesicht entsperren"
#: src/lib/menu.sh
msgid "Look when somebody comes back to the screen"
msgstr "Schauen, wenn jemand zum Bildschirm zurückkommt"
msgid "Scan when you come back"
msgstr "Scannen, wenn du zurückkommst"
#: src/lib/menu.sh
msgid "Look right after the screen locks"
msgstr "Gleich nach dem Sperren schauen"
msgid "Scan right after locking"
msgstr "Gleich nach dem Sperren scannen"
#: src/lib/menu.sh
msgid "Use for sudo in a terminal"
msgstr "Für sudo im Terminal verwenden"
msgid "Password prompts"
msgstr "Passwort-Abfragen"
#: src/lib/menu.sh
msgid "Use for admin prompts"
msgstr "Für Admin-Abfragen verwenden"
msgid "sudo in a terminal"
msgstr "sudo im Terminal"
#: src/lib/menu.sh
msgid "How closely a face has to match"
msgstr "Wie genau ein Gesicht passen muss"
msgid "Recognition"
msgstr "Erkennung"
#: src/lib/menu.sh
msgid "Only while looking at the screen"
msgstr "Nur beim Blick auf den Bildschirm"
msgid "How closely the face has to match"
msgstr "Wie genau das Gesicht passen muss"
#: src/lib/menu.sh
msgid "How long one look lasts"
msgstr "Wie lange ein Blick dauert"
msgid "Only when you look at the screen"
msgstr "Nur wenn du auf den Bildschirm schaust"
#: src/lib/menu.sh
msgid "How long a scan lasts"
msgstr "Wie lange ein Scan dauert"
#: src/lib/menu.sh
msgid "Learn from every unlock"
msgstr "Bei jeder Entsperrung dazulernen"
#: src/lib/menu.sh
msgid "Not while the lid is closed"
msgstr "Nicht bei geschlossenem Deckel"
msgid "Not when the lid is closed"
msgstr "Nicht bei zugeklapptem Laptop"
#: src/lib/menu.sh
msgid "Show the bubble at the top"
msgstr "Die Bubble oben anzeigen"
msgid "Bubble"
msgstr "Bubble"
#: src/lib/menu.sh
msgid "Bubble style"
msgstr "Bubble-Stil"
msgid "Show the bubble"
msgstr "Bubble anzeigen"
#: src/lib/menu.sh
msgid "Bubble for sudo and admin prompts too"
msgstr "Bubble auch für sudo und Admin-Abfragen"
msgid "Style"
msgstr "Stil"
#: src/agent/bubblecontroller.cpp:91
#: src/lib/menu.sh
msgid "Animation speed"
msgstr "Animationsgeschwindigkeit"
#: src/lib/menu.sh
msgid "Also for sudo and admin prompts"
msgstr "Auch bei sudo und Admin-Abfragen"
#: src/agent/bubblecontroller.cpp:98
msgid "Blink once"
msgstr "Einmal blinzeln"
#: src/agent/bubblecontroller.cpp:93
#: src/agent/bubblecontroller.cpp:100
msgid "Look at the screen"
msgstr "Auf den Bildschirm schauen"
#: src/agent/bubblecontroller.cpp:95
#: src/agent/bubblecontroller.cpp:102
msgid "Move closer"
msgstr "Näher kommen"
#: src/agent/bubblecontroller.cpp:97
#: src/agent/bubblecontroller.cpp:104
msgid "Too dark"
msgstr "Zu dunkel"
#: src/agent/bubblecontroller.cpp:117
#: src/agent/bubblecontroller.cpp:124
msgid "Use your password"
msgstr "Nutze dein Passwort"
#: src/agent/bubblecontroller.cpp:123
#: src/agent/bubblecontroller.cpp:130
msgid "Not recognized"
msgstr "Nicht erkannt"
#: src/agent/bubblecontroller.cpp:125
#: src/agent/bubblecontroller.cpp:132
msgid "Try again and blink"
msgstr "Nochmal, und blinzeln"
#: src/agent/bubblecontroller.cpp:127
#: src/agent/bubblecontroller.cpp:134
msgid "Camera unavailable"
msgstr "Kamera nicht verfügbar"
@@ -653,7 +835,8 @@ msgstr "Die Kamera konnte nicht benutzt werden: %1"
#: src/agent/enrollcontroller.cpp:197
msgid "The face recognition models are missing. Reinstall the package."
msgstr "Die Modelle für die Gesichtserkennung fehlen. Installiere das Paket neu."
msgstr ""
"Die Modelle für die Gesichtserkennung fehlen. Installiere das Paket neu."
#: src/agent/enrollcontroller.cpp:199
msgid "That took too long. Try again, in good light."
@@ -661,11 +844,15 @@ msgstr "Das hat zu lange gedauert. Versuch es nochmal, bei gutem Licht."
#: src/agent/enrollcontroller.cpp:201
msgid "The face unlock service is not running. Turn face unlock on first."
msgstr "Der Dienst für die Gesichtsentsperrung läuft nicht. Schalte die Gesichtsentsperrung zuerst ein."
msgstr ""
"Der Dienst für die Gesichtsentsperrung läuft nicht. Schalte die "
"Gesichtsentsperrung zuerst ein."
#: src/agent/enrollcontroller.cpp:203
msgid "The camera is busy with another scan. Try again in a moment."
msgstr "Die Kamera ist gerade mit einem anderen Scan beschäftigt. Versuch es gleich nochmal."
msgstr ""
"Die Kamera ist gerade mit einem anderen Scan beschäftigt. Versuch es gleich "
"nochmal."
#: src/agent/enrollcontroller.cpp:205
msgid "The face could not be added (%1)."
@@ -701,7 +888,9 @@ msgstr "Blinzle oder dreh den Kopf ein wenig."
#: src/pam/pam_plasma_face_unlock.c:312
msgid "Face unlock is paused after too many tries. Use your password."
msgstr "Die Gesichtsentsperrung ist nach zu vielen Versuchen pausiert. Nutze dein Passwort."
msgstr ""
"Die Gesichtsentsperrung ist nach zu vielen Versuchen pausiert. Nutze dein "
"Passwort."
#: src/pam/pam_plasma_face_unlock.c:316
msgid "Face not recognised."
@@ -731,47 +920,38 @@ msgstr "Die Einrichtung wurde nicht abgeschlossen"
msgid ""
"Look at the camera, then move your head slowly in a circle. Your face is "
"turned into numbers on this computer and never stored as a picture."
msgstr "Schau in die Kamera und beweg dann den Kopf langsam im Kreis. Dein Gesicht wird auf diesem Computer in Zahlen umgewandelt und nie als Bild gespeichert."
msgstr ""
"Schau in die Kamera und beweg dann den Kopf langsam im Kreis. Dein Gesicht "
"wird auf diesem Computer in Zahlen umgewandelt und nie als Bild gespeichert."
#: src/agent/qml/Enroll.qml:123
msgid ""
"Lock the screen and look at it to try it out. You can add a second look, "
"with glasses for example, from the menu."
msgstr "Sperre den Bildschirm und schau ihn an, um es auszuprobieren. Einen zweiten Look, zum Beispiel mit Brille, kannst du im Menü hinzufügen."
msgstr ""
"Sperre den Bildschirm und schau ihn an, um es auszuprobieren. Einen zweiten "
"Look, zum Beispiel mit Brille, kannst du im Menü hinzufügen."
#: src/agent/qml/Enroll.qml:156
#: src/agent/qml/Enroll.qml:157
msgid "Name (optional)"
msgstr "Name (optional)"
#: src/agent/qml/Enroll.qml:171
#: src/agent/qml/Enroll.qml:172
msgid "Cancel"
msgstr "Abbrechen"
#: src/agent/qml/Enroll.qml:177
#: src/agent/qml/Enroll.qml:178
msgid "Finish now"
msgstr "Jetzt abschließen"
#: src/agent/qml/Enroll.qml:182
#: src/agent/qml/Enroll.qml:183
msgid "Try again"
msgstr "Nochmal versuchen"
#: src/agent/qml/Enroll.qml:182
#: src/agent/qml/Enroll.qml:183
msgid "Get started"
msgstr "Los geht's"
#: src/agent/qml/Enroll.qml:187
#: src/agent/qml/Enroll.qml:188
msgid "Done"
msgstr "Fertig"
#: src/lib/menu.sh
msgid "Animation speed"
msgstr "Animationsgeschwindigkeit"
#: src/lib/menu.sh
msgid "fast"
msgstr "schnell"
#: src/lib/menu.sh
msgid "slow"
msgstr "langsam"
+175 -53
View File
@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: plasma-face-unlock 1.0.0\n"
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/plasma-face-unlock/"
"issues\n"
"POT-Creation-Date: 2026-09-22 23:12+0200\n"
"POT-Creation-Date: 2026-09-24 01:31+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -55,8 +55,8 @@ msgstr ""
msgid "First, set up your face."
msgstr ""
#: src/plasma-face-unlock:100 src/plasma-face-unlock:120 src/lib/menu.sh:352
#: src/lib/menu.sh:356 src/lib/menu.sh:362 src/lib/menu.sh:364
#: src/plasma-face-unlock:100 src/plasma-face-unlock:120 src/lib/menu.sh:406
#: src/lib/menu.sh:410 src/lib/menu.sh:416 src/lib/menu.sh:418
msgid "Could not save the setting."
msgstr ""
@@ -84,16 +84,16 @@ msgstr ""
msgid "No face is set up yet."
msgstr ""
#: src/plasma-face-unlock:148 src/lib/menu.sh:167 src/lib/menu.sh:476
#: src/plasma-face-unlock:148 src/lib/menu.sh:167 src/lib/menu.sh:563
msgid "on"
msgstr ""
#: src/plasma-face-unlock:148 src/lib/menu.sh:169 src/lib/menu.sh:179
#: src/lib/menu.sh:477
#: src/lib/menu.sh:564
msgid "off"
msgstr ""
#: src/plasma-face-unlock:150 src/lib/menu.sh:494
#: src/plasma-face-unlock:150 src/lib/menu.sh:581
#, sh-printf-format
msgid "%s samples, %s learned"
msgstr ""
@@ -123,7 +123,7 @@ msgstr ""
msgid "Turn it off (faces are kept)"
msgstr ""
#: src/plasma-face-unlock:176 src/lib/menu.sh:559
#: src/plasma-face-unlock:176 src/lib/menu.sh:646
msgid "Add a face"
msgstr ""
@@ -174,17 +174,29 @@ msgstr ""
msgid "just now"
msgstr ""
#: src/lib/common.sh:173
#: src/lib/common.sh:172
msgid "1 minute ago"
msgstr ""
#: src/lib/common.sh:175
#, sh-printf-format
msgid "%d minutes ago"
msgstr ""
#: src/lib/common.sh:176
#: src/lib/common.sh:177
msgid "1 hour ago"
msgstr ""
#: src/lib/common.sh:180
#, sh-printf-format
msgid "%d hours ago"
msgstr ""
#: src/lib/common.sh:179
#: src/lib/common.sh:182
msgid "1 day ago"
msgstr ""
#: src/lib/common.sh:185
#, sh-printf-format
msgid "%d days ago"
msgstr ""
@@ -323,30 +335,26 @@ msgstr ""
msgid "[y/N]"
msgstr ""
#: src/lib/menu.sh:159 src/lib/menu.sh:396
#: src/lib/menu.sh:159 src/lib/menu.sh:467
msgid "ON"
msgstr ""
#: src/lib/menu.sh:161 src/lib/menu.sh:397
#: src/lib/menu.sh:161 src/lib/menu.sh:468
msgid "OFF"
msgstr ""
#: src/lib/menu.sh:177
msgid "strict (blink or turn your head)"
#: src/lib/menu.sh:177 src/lib/menu.sh:181
msgid "strict"
msgstr ""
#: src/lib/menu.sh:178
msgid "basic (screens and phone edges)"
msgid "basic"
msgstr ""
#: src/lib/menu.sh:180 src/lib/menu.sh:185
msgid "normal"
msgstr ""
#: src/lib/menu.sh:181
msgid "strict"
msgstr ""
#: src/lib/menu.sh:182
msgid "relaxed"
msgstr ""
@@ -372,7 +380,7 @@ msgstr ""
msgid "%s seconds"
msgstr ""
#: src/lib/menu.sh:189 src/lib/menu.sh:327
#: src/lib/menu.sh:189 src/lib/menu.sh:381
msgid "automatic"
msgstr ""
@@ -392,8 +400,8 @@ msgstr ""
msgid "Turning face unlock on again starts it."
msgstr ""
#: src/lib/menu.sh:223 src/lib/menu.sh:225 src/lib/menu.sh:473
#: src/lib/menu.sh:560
#: src/lib/menu.sh:223 src/lib/menu.sh:225 src/lib/menu.sh:560
#: src/lib/menu.sh:647
msgid "Faces"
msgstr ""
@@ -409,11 +417,11 @@ msgstr ""
msgid "none found"
msgstr ""
#: src/lib/menu.sh:233 /tmp/tmp.PgsbSPb4Sv/settings.sh:9
#: src/lib/menu.sh:233 /tmp/tmp.3ElRvSMg8S/settings.sh:12
msgid "Camera"
msgstr ""
#: src/lib/menu.sh:235
#: src/lib/menu.sh:235 /tmp/tmp.3ElRvSMg8S/settings.sh:1
msgid "Lock screen"
msgstr ""
@@ -421,11 +429,11 @@ msgstr ""
msgid "sudo"
msgstr ""
#: src/lib/menu.sh:237
#: src/lib/menu.sh:237 /tmp/tmp.3ElRvSMg8S/settings.sh:7
msgid "Admin prompts"
msgstr ""
#: src/lib/menu.sh:238 /tmp/tmp.PgsbSPb4Sv/settings.sh:6
#: src/lib/menu.sh:238 /tmp/tmp.3ElRvSMg8S/settings.sh:9
msgid "Photo check"
msgstr ""
@@ -442,57 +450,163 @@ msgstr ""
msgid "Last unlock"
msgstr ""
#: src/lib/menu.sh:323
#: src/lib/menu.sh:287
msgid ""
"Unlocks the lock screen when it sees your face. Off: only your password "
"works there."
msgstr ""
#: src/lib/menu.sh:288
msgid ""
"Scans when you press a key or move the mouse on the lock screen, and when "
"the computer wakes up."
msgstr ""
#: src/lib/menu.sh:289
msgid ""
"Scans as soon as the screen locks. Off by default: if you lock it yourself, "
"it would unlock again right away."
msgstr ""
#: src/lib/menu.sh:290
msgid ""
"sudo takes your face instead of the password. No match: you type the "
"password as usual."
msgstr ""
#: src/lib/menu.sh:291
msgid ""
"The password windows of Plasma and apps, for example when you install "
"software. No match: you type the password."
msgstr ""
#: src/lib/menu.sh:292
msgid ""
"You have to blink or turn your head a little. This also stops printed photos."
msgstr ""
#: src/lib/menu.sh:293
msgid "No check at all. Only for trying out a camera."
msgstr ""
#: src/lib/menu.sh:294
msgid ""
"Stops photos on a phone, a tablet or glossy paper. You do not have to blink. "
"A matte printed photo can get through."
msgstr ""
#: src/lib/menu.sh:295
msgid ""
"Fewer wrong matches, but it may not know you with glasses or in bad light."
msgstr ""
#: src/lib/menu.sh:296
msgid "Knows you more easily, but also somebody who looks a lot like you."
msgstr ""
#: src/lib/menu.sh:297
msgid "The default. Right for most people."
msgstr ""
#: src/lib/menu.sh:298
msgid ""
"Your eyes have to be open and on the screen. So it does not unlock while you "
"look away or sleep."
msgstr ""
#: src/lib/menu.sh:299
msgid ""
"Automatic takes the first normal camera. After a change, set up your face "
"again. An infrared camera needs its light on (linux-enable-ir-emitter)."
msgstr ""
#: src/lib/menu.sh:300
msgid "How long the camera looks for your face before it gives up."
msgstr ""
#: src/lib/menu.sh:301
msgid ""
"After a sure match it keeps how you look now. So a new haircut or glasses "
"need no new setup."
msgstr ""
#: src/lib/menu.sh:302
msgid ""
"No scan while the laptop is closed, for example at a desk with an external "
"screen."
msgstr ""
#: src/lib/menu.sh:303
msgid "The bubble at the top of the screen shows what the camera is doing."
msgstr ""
#: src/lib/menu.sh:304
msgid "A small pill with a lock that opens."
msgstr ""
#: src/lib/menu.sh:305
msgid "An island with a face that looks around, then rings and a tick."
msgstr ""
#: src/lib/menu.sh:306
msgid "How fast the bubble moves."
msgstr ""
#: src/lib/menu.sh:307
msgid "Shows the bubble when sudo or an admin prompt scans your face, too."
msgstr ""
#: src/lib/menu.sh:377
#, sh-printf-format
msgid "automatic (%s)"
msgstr ""
#: src/lib/menu.sh:333
#: src/lib/menu.sh:387
msgid "(infrared)"
msgstr ""
#: src/lib/menu.sh:337
#: src/lib/menu.sh:391
msgid "(not connected)"
msgstr ""
#: src/lib/menu.sh:393 src/lib/menu.sh:561
#: src/lib/menu.sh:464 src/lib/menu.sh:648
msgid "Settings"
msgstr ""
#: src/lib/menu.sh:394
msgid "Up/Down: select, Space or Right: change, q: back"
#: src/lib/menu.sh:465
msgid "↑↓ select ←→ or Space: change q: back"
msgstr ""
#: src/lib/menu.sh:395
msgid "Settings marked * are for the whole computer and ask for your password."
#: src/lib/menu.sh:466
msgid "for all users, asks for your password"
msgstr ""
#: src/lib/menu.sh:474
#: src/lib/menu.sh:561
msgid "Up/Down: select, Space: on/off, r: rename, d: delete, a: add, q: back"
msgstr ""
#: src/lib/menu.sh:475
#: src/lib/menu.sh:562
msgid "No face is set up yet. Press a to add one."
msgstr ""
#: src/lib/menu.sh:520
#: src/lib/menu.sh:607
msgid "New name:"
msgstr ""
#: src/lib/menu.sh:527
#: src/lib/menu.sh:614
#, sh-printf-format
msgid "Delete \"%s\"?"
msgstr ""
#: src/lib/menu.sh:558
#: src/lib/menu.sh:645
msgid "Turn face unlock on or off"
msgstr ""
#: src/lib/menu.sh:562
#: src/lib/menu.sh:649
msgid "Try it"
msgstr ""
#: src/lib/menu.sh:563
#: src/lib/menu.sh:650
msgid "Quit"
msgstr ""
@@ -525,35 +639,39 @@ msgid "Not a service this can be used for: %s"
msgstr ""
#: src/lib/menu.sh
msgid "Unlock the lock screen"
msgid "Unlock with your face"
msgstr ""
#: src/lib/menu.sh
msgid "Look when somebody comes back to the screen"
msgid "Scan when you come back"
msgstr ""
#: src/lib/menu.sh
msgid "Look right after the screen locks"
msgid "Scan right after locking"
msgstr ""
#: src/lib/menu.sh
msgid "Use for sudo in a terminal"
msgid "Password prompts"
msgstr ""
#: src/lib/menu.sh
msgid "Use for admin prompts"
msgid "sudo in a terminal"
msgstr ""
#: src/lib/menu.sh
msgid "How closely a face has to match"
msgid "Recognition"
msgstr ""
#: src/lib/menu.sh
msgid "Only while looking at the screen"
msgid "How closely the face has to match"
msgstr ""
#: src/lib/menu.sh
msgid "How long one look lasts"
msgid "Only when you look at the screen"
msgstr ""
#: src/lib/menu.sh
msgid "How long a scan lasts"
msgstr ""
#: src/lib/menu.sh
@@ -561,15 +679,19 @@ msgid "Learn from every unlock"
msgstr ""
#: src/lib/menu.sh
msgid "Not while the lid is closed"
msgid "Not when the lid is closed"
msgstr ""
#: src/lib/menu.sh
msgid "Show the bubble at the top"
msgid "Bubble"
msgstr ""
#: src/lib/menu.sh
msgid "Bubble style"
msgid "Show the bubble"
msgstr ""
#: src/lib/menu.sh
msgid "Style"
msgstr ""
#: src/lib/menu.sh
@@ -577,7 +699,7 @@ msgid "Animation speed"
msgstr ""
#: src/lib/menu.sh
msgid "Bubble for sudo and admin prompts too"
msgid "Also for sudo and admin prompts"
msgstr ""
#: src/agent/bubblecontroller.cpp:98
+5 -3
View File
@@ -5,8 +5,9 @@
# the shell code (pfu_msg), the agent (i18n in C++ and QML) and the PAM module
# (dgettext), so a word is translated once wherever it shows up.
#
# The settings labels live in an array in menu.sh and reach gettext at run
# time, so xgettext cannot see them; they are pulled out here first.
# The settings labels and headings live in an array in menu.sh and reach
# gettext at run time, so xgettext cannot see them; they are pulled out here
# first.
set -euo pipefail
@@ -17,7 +18,8 @@ trap 'rm -rf -- "$tmp"' EXIT
version="$(make -s version)"
# The labels, as calls xgettext understands, pointing back at menu.sh.
awk -F'|' '/^\t"(user|sys|pam)\|/ { sub(/"$/, "", $5); print "pfu_msg \"" $5 "\"" }' src/lib/menu.sh > "$tmp/settings.sh"
awk -F'|' '/^\t"(user|sys|pam)\|/ { sub(/"$/, "", $5); print "pfu_msg \"" $5 "\"" }
/^\t"group\|/ { sub(/"$/, "", $2); print "pfu_msg \"" $2 "\"" }' src/lib/menu.sh > "$tmp/settings.sh"
common=(--from-code=UTF-8 --add-comments=TRANSLATORS --package-name=plasma-face-unlock --package-version="$version"
--msgid-bugs-address=https://github.com/LoonixTools/plasma-face-unlock/issues)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

After

Width:  |  Height:  |  Size: 2.3 MiB

+4 -4
View File
@@ -10,10 +10,10 @@
namespace
{
// How long a result stays up before the bubble closes: long enough to read
// (on success after the face has resolved, about a second), short enough not
// to be in the way.
constexpr int SuccessHoldMs = 1500;
// How long a result stays up before the bubble closes: long enough to read,
// short enough not to be in the way. On success the tick is drawn after
// about 730 ms and only gets a short moment on its own.
constexpr int SuccessHoldMs = 1100;
constexpr int FailureHoldMs = 1700;
constexpr int LockoutHoldMs = 3000;
// A scan that never reports back (the daemon went away half way) must not
+4 -4
View File
@@ -23,9 +23,6 @@ constexpr int GraceAfterLockMs = 1500;
// person has kept still for this long and then touched something again. That
// way typing the password does not start a scan with every key.
constexpr int CalmBeforeRetryMs = 2000;
// Show the tick before the screen goes: long enough to see, short enough
// not to feel like waiting.
constexpr int UnlockAfterSuccessMs = 450;
// A camera needs a moment after the machine wakes before it delivers frames.
constexpr int ScanAfterWakeMs = 1000;
} // namespace
@@ -188,8 +185,11 @@ void LockController::onScanFinished(const QJsonObject &result)
const QString reason = result.value(u"reason").toString();
if (result.value(u"ok").toBool()) {
// Straight away, the way a phone does it: the lock screen takes a
// moment to go, and the bubble stays above the desktop, so the rings
// and the tick play on over it.
m_bubble->succeeded();
QTimer::singleShot(int(UnlockAfterSuccessMs * m_config->pace()), this, &LockController::unlock);
unlock();
return;
}
+3 -1
View File
@@ -36,7 +36,9 @@ void UserConfig::load()
m_bubbleStyle = kv.value(QStringLiteral("BubbleStyle"), QStringLiteral("full")) == u"minimal" ? QStringLiteral("minimal") : QStringLiteral("full");
m_bubbleForPrompts = kv.boolean(QStringLiteral("BubbleForPrompts"), true);
const QString speed = kv.value(QStringLiteral("AnimationSpeed"), QStringLiteral("normal"));
m_pace = speed == u"fast" ? 0.6 : speed == u"slow" ? 1.5 : 1.0;
// The durations in the code are brisk, like on a phone. On a big screen
// they read better a little longer, so normal stretches them.
m_pace = speed == u"fast" ? 1.0 : speed == u"slow" ? 2.0 : 1.3;
if (QFileInfo::exists(path()) && !m_watcher.files().contains(path())) {
m_watcher.addPath(path());
+4 -3
View File
@@ -55,8 +55,9 @@ public:
{
return m_bubbleForPrompts;
}
// How long the bubble's animations take, as a multiple of normal: the
// animation speed setting (fast 0.6, normal 1, slow 1.5).
// How long the bubble's animations take, as a multiple of the durations
// in the code: the animation speed setting (fast 1, normal 1.3,
// slow 2).
qreal pace() const
{
return m_pace;
@@ -77,6 +78,6 @@ private:
bool m_bubble = true;
QString m_bubbleStyle = QStringLiteral("full");
bool m_bubbleForPrompts = true;
qreal m_pace = 1;
qreal m_pace = 1.3;
QString m_styleOverride;
};
+16
View File
@@ -37,6 +37,21 @@ QString sysName(const QString &device)
return QString::fromUtf8(file.readAll()).trimmed();
}
// UVC names a node "<device>: <function>", often twice the same or with the
// second cut short ("HP HD Camera: HP HD Camera"). The function tells a
// laptop's two cameras apart ("HP HD Camera: HP IR Camera"), so that part is
// kept unless it only repeats the first.
QString shortName(const QString &name)
{
const qsizetype colon = name.indexOf(u": ");
if (colon < 0) {
return name;
}
const QString device = name.left(colon);
const QString function = name.mid(colon + 2).trimmed();
return function.isEmpty() || device.startsWith(function) ? device : function;
}
// Opens the node just long enough to ask what it is. Neither this nor the
// format list below starts streaming, so it does not switch the light on.
bool probe(const QString &path, CameraInfo *info)
@@ -85,6 +100,7 @@ bool probe(const QString &path, CameraInfo *info)
if (info->name.isEmpty()) {
info->name = QString::fromUtf8(reinterpret_cast<const char *>(cap.card));
}
info->name = shortName(info->name);
info->infrared = grey && !colour;
}
return ok;
+3 -1
View File
@@ -30,7 +30,9 @@ struct Settings {
// A /dev/video path, "auto", or for testing "file:<video>" and
// "images:<directory>".
QString camera = QStringLiteral("auto");
LivenessMode liveness = LivenessMode::Heavy;
// Basic by default: screens and phones are caught without asking for a
// blink. Strict adds the blink or head turn that stops a printed photo.
LivenessMode liveness = LivenessMode::Light;
Strictness strictness = Strictness::Normal;
// Only a face that looks at the screen with its eyes open counts.
bool attention = true;
+6
View File
@@ -168,12 +168,18 @@ pfu_time_ago() {
if [[ $delta -lt 60 ]]; then
pfu_msg "just now"
elif [[ $delta -lt 120 ]]; then
pfu_msg "1 minute ago"
elif [[ $delta -lt 3600 ]]; then
n=$(( delta / 60 ))
pfu_msg "%d minutes ago" "$n"
elif [[ $delta -lt 7200 ]]; then
pfu_msg "1 hour ago"
elif [[ $delta -lt 86400 ]]; then
n=$(( delta / 3600 ))
pfu_msg "%d hours ago" "$n"
elif [[ $delta -lt 172800 ]]; then
pfu_msg "1 day ago"
else
n=$(( delta / 86400 ))
pfu_msg "%d days ago" "$n"
+1 -1
View File
@@ -126,7 +126,7 @@ pfu_config_load() {
_pfu_kv_lookup "$PFU_CONFIG" Sudo no; CFG_SUDO=no; pfu_is_true "$PFU_KV_VALUE" && CFG_SUDO=yes
_pfu_kv_lookup "$PFU_CONFIG" Polkit no; CFG_POLKIT=no; pfu_is_true "$PFU_KV_VALUE" && CFG_POLKIT=yes
_pfu_kv_lookup "$PFU_SYSCONFIG" Liveness heavy; CFG_LIVENESS="$PFU_KV_VALUE"
_pfu_kv_lookup "$PFU_SYSCONFIG" Liveness light; CFG_LIVENESS="$PFU_KV_VALUE"
_pfu_kv_lookup "$PFU_SYSCONFIG" Camera auto; CFG_CAMERA="$PFU_KV_VALUE"
return 0
}
+162 -75
View File
@@ -174,8 +174,8 @@ _pfu_small_onoff() {
# How a setting's value reads in the menu.
pfu_value_label() {
case "$1:$2" in
Liveness:heavy) pfu_msg "strict (blink or turn your head)" ;;
Liveness:light) pfu_msg "basic (screens and phone edges)" ;;
Liveness:heavy) pfu_msg "strict" ;;
Liveness:light) pfu_msg "basic" ;;
Liveness:off) pfu_msg "off" ;;
Strictness:normal) pfu_msg "normal" ;;
Strictness:strict) pfu_msg "strict" ;;
@@ -250,29 +250,85 @@ pfu_ui_status() {
# ---------------------------------------------------------------------------
# Settings
# ---------------------------------------------------------------------------
# Format: scope|Key|type|default|label-msgid|choices
# scope user (this user's file), sys (the system file, through sudo) or
# pam (the service of that name, through sudo)
# type bool, choice (cycles through the choices) or camera
# Format: scope|Key|type|default|label-msgid|choices|needs
# 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
PFU_SETTINGS=(
"user|LockScreen|bool|yes|Unlock the lock screen"
"user|ScanOnWake|bool|yes|Look when somebody comes back to the screen"
"user|ScanOnLock|bool|no|Look right after the screen locks"
"pam|sudo|bool|no|Use for sudo in a terminal"
"pam|polkit-1|bool|no|Use for admin prompts"
"sys|Liveness|choice|heavy|Photo check|heavy,light,off"
"sys|Strictness|choice|normal|How closely a face has to match|normal,strict,relaxed"
"sys|Attention|bool|yes|Only while looking at the screen"
"group|Lock screen"
"user|LockScreen|bool|yes|Unlock with your face"
"user|ScanOnWake|bool|yes|Scan when you come back||LockScreen"
"user|ScanOnLock|bool|no|Scan right after locking||LockScreen"
"group|Password prompts"
"pam|sudo|bool|no|sudo in a terminal"
"pam|polkit-1|bool|no|Admin prompts"
"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"
"sys|Attention|bool|yes|Only when you look at the screen"
"sys|Camera|camera|auto|Camera"
"sys|ScanSeconds|choice|5|How long one look lasts|3,4,5,6,8,10"
"sys|ScanSeconds|choice|5|How long a scan lasts|3,4,5,6,8,10"
"sys|Adapt|bool|yes|Learn from every unlock"
"sys|SkipLidClosed|bool|yes|Not while the lid is closed"
"user|Bubble|bool|yes|Show the bubble at the top"
"user|BubbleStyle|choice|full|Bubble style|full,minimal"
"user|AnimationSpeed|choice|normal|Animation speed|normal,fast,slow"
"user|BubbleForPrompts|bool|yes|Bubble for sudo and admin prompts too"
"sys|SkipLidClosed|bool|yes|Not when the lid is closed"
"group|Bubble"
"user|Bubble|bool|yes|Show the bubble"
"user|BubbleStyle|choice|full|Style|full,minimal|Bubble"
"user|AnimationSpeed|choice|normal|Animation speed|slow,normal,fast|Bubble"
"user|BubbleForPrompts|bool|yes|Also for sudo and admin prompts||Bubble"
)
# pfu_setting_help <Key> <value>
# What a setting does, shown under the list for the selected one.
pfu_setting_help() {
case "$1:$2" in
LockScreen:*) pfu_msg "Unlocks the lock screen when it sees your face. Off: only your password works there." ;;
ScanOnWake:*) pfu_msg "Scans when you press a key or move the mouse on the lock screen, and when the computer wakes up." ;;
ScanOnLock:*) pfu_msg "Scans as soon as the screen locks. Off by default: if you lock it yourself, it would unlock again right away." ;;
sudo:*) pfu_msg "sudo takes your face instead of the password. No match: you type the password as usual." ;;
polkit-1:*) pfu_msg "The password windows of Plasma and apps, for example when you install software. No match: you type the password." ;;
Liveness:heavy) pfu_msg "You have to blink or turn your head a little. This also stops printed photos." ;;
Liveness:off) pfu_msg "No check at all. Only for trying out a camera." ;;
Liveness:*) pfu_msg "Stops photos on a phone, a tablet or glossy paper. You do not have to blink. A matte printed photo can get through." ;;
Strictness:strict) pfu_msg "Fewer wrong matches, but it may not know you with glasses or in bad light." ;;
Strictness:relaxed) pfu_msg "Knows you more easily, but also somebody who looks a lot like you." ;;
Strictness:*) pfu_msg "The default. Right for most people." ;;
Attention:*) pfu_msg "Your eyes have to be open and on the screen. So it does not unlock while you look away or sleep." ;;
Camera:*) pfu_msg "Automatic takes the first normal camera. After a change, set up your face again. An infrared camera needs its light on (linux-enable-ir-emitter)." ;;
ScanSeconds:*) pfu_msg "How long the camera looks for your face before it gives up." ;;
Adapt:*) pfu_msg "After a sure match it keeps how you look now. So a new haircut or glasses need no new setup." ;;
SkipLidClosed:*) pfu_msg "No scan while the laptop is closed, for example at a desk with an external screen." ;;
Bubble:*) pfu_msg "The bubble at the top of the screen shows what the camera is doing." ;;
BubbleStyle:minimal) pfu_msg "A small pill with a lock that opens." ;;
BubbleStyle:*) pfu_msg "An island with a face that looks around, then rings and a tick." ;;
AnimationSpeed:*) pfu_msg "How fast the bubble moves." ;;
BubbleForPrompts:*) pfu_msg "Shows the bubble when sudo or an admin prompt scans your face, too." ;;
esac
}
# _pfu_wrap <width> <text>
# Word wrap, into PFU_WRAP_LINES.
PFU_WRAP_LINES=()
_pfu_wrap() {
local width="$1" word line=''
local -a words
PFU_WRAP_LINES=()
read -r -a words <<< "$2"
for word in "${words[@]}"; do
if [[ -n $line ]] && (( ${#line} + 1 + ${#word} > width )); then
PFU_WRAP_LINES+=("$line")
line="$word"
else
line="${line:+$line }$word"
fi
done
[[ -n $line ]] && PFU_WRAP_LINES+=("$line")
return 0
}
# _pfu_setting_value <scope> <Key> <default>
# The current value, in PFU_SETTING_VALUE.
PFU_SETTING_VALUE=''
@@ -285,34 +341,32 @@ _pfu_setting_value() {
esac
}
# _pfu_next_choice <current> <a,b,c>
_pfu_next_choice() {
local current="$1" list="$2" first='' found=0 c
local -a choices
IFS=',' read -r -a choices <<< "$list"
for c in "${choices[@]}"; do
[[ -z $first ]] && first="$c"
if (( found )); then
printf '%s\n' "$c"
return
fi
[[ $c == "$current" ]] && found=1
done
printf '%s\n' "$first"
}
# _pfu_next_camera <current>
_pfu_next_camera() {
local current="$1" i
pfu_cameras_load
local -a all=(auto "${PFU_CAM_PATHS[@]}")
# _pfu_step <current> <step> <choice>...
# The choice <step> (1 or -1) away from the current one, round at the ends.
_pfu_step() {
local current="$1" step="$2" i
shift 2
local -a all=("$@")
for i in "${!all[@]}"; do
if [[ ${all[i]} == "$current" ]]; then
printf '%s\n' "${all[(i + 1) % ${#all[@]}]}"
printf '%s\n' "${all[(i + step + ${#all[@]}) % ${#all[@]}]}"
return
fi
done
printf 'auto\n'
printf '%s\n' "${all[0]}"
}
# _pfu_next_choice <current> <a,b,c> <step>
_pfu_next_choice() {
local -a choices
IFS=',' read -r -a choices <<< "$2"
_pfu_step "$1" "$3" "${choices[@]}"
}
# _pfu_next_camera <current> <step>
_pfu_next_camera() {
pfu_cameras_load
_pfu_step "$1" "$2" auto "${PFU_CAM_PATHS[@]}"
}
_pfu_camera_label() {
@@ -337,14 +391,14 @@ _pfu_camera_label() {
printf '%s %s' "$value" "$(pfu_msg "(not connected)")"
}
# _pfu_setting_change <scope> <Key> <type> <current> <choices>
# _pfu_setting_change <scope> <Key> <type> <current> <choices> <step>
_pfu_setting_change() {
local scope="$1" key="$2" type="$3" current="$4" choices="$5" next
local scope="$1" key="$2" type="$3" current="$4" choices="$5" step="$6" next
case "$type" in
bool) if pfu_is_true "$current"; then next=no; else next=yes; fi ;;
choice) next="$(_pfu_next_choice "$current" "$choices")" ;;
camera) next="$(_pfu_next_camera "$current")" ;;
choice) next="$(_pfu_next_choice "$current" "$choices" "$step")" ;;
camera) next="$(_pfu_next_camera "$current" "$step")" ;;
esac
case "$scope" in
@@ -374,25 +428,42 @@ _pfu_setting_change() {
}
# pfu_ui_settings
# A cursor list rather than a numbered menu. The frame is assembled in memory
# and written once, and everything constant is resolved before the loop.
# A cursor list in groups, with what the selected setting does under it. The
# frame is assembled in memory and written once, and everything constant is
# resolved before the loop.
pfu_ui_settings() {
local count=${#PFU_SETTINGS[@]}
local -a scopes=() keys=() types=() defaults=() labels=() choices=() values=()
local spec scope key type default label choice locale i frame row pad dirty=1 cursor=0 shown
local -a scopes=() keys=() types=() defaults=() labels=() 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 width=0 wrap cols
locale="$(pfu_ui_locale)"
for spec in "${PFU_SETTINGS[@]}"; do
IFS='|' read -r scope key type default label choice <<< "$spec"
scopes+=("$scope"); keys+=("$key"); types+=("$type"); defaults+=("$default"); choices+=("$choice")
IFS='|' read -r scope key type default label choice need <<< "$spec"
# A heading has its label where the key would be.
[[ $scope == group ]] && label="$key" key=''
scopes+=("$scope"); keys+=("$key"); types+=("$type"); defaults+=("$default")
choices+=("$choice"); needs+=("$need")
pfu_msg_into "$locale" "$label"
labels+=("$PFU_MSG_RESULT")
if [[ $scope != group ]]; then
rows+=($(( ${#keys[@]} - 1 )))
(( ${#PFU_MSG_RESULT} > width )) && width=${#PFU_MSG_RESULT}
fi
done
local count=${#rows[@]}
local title hint legend l_on l_off
cols="$(tput cols 2>/dev/null)" || cols=80
[[ $cols =~ ^[0-9]+$ ]] || cols=80
wrap=$(( cols - 4 ))
(( wrap > 72 )) && wrap=72
local rule
printf -v rule '%*s' "$wrap" ''
rule="${rule// /─}"
local title hint shared l_on l_off
pfu_msg_into "$locale" "Settings"; title="$PFU_MSG_RESULT"
pfu_msg_into "$locale" "Up/Down: select, Space or Right: change, q: back"; hint="$PFU_MSG_RESULT"
pfu_msg_into "$locale" "Settings marked * are for the whole computer and ask for your password."; legend="$PFU_MSG_RESULT"
pfu_msg_into "$locale" "↑↓ select ←→ or Space: change q: back"; hint="$PFU_MSG_RESULT"
pfu_msg_into "$locale" "for all users, asks for your password"; shared="$PFU_MSG_RESULT"
pfu_msg_into "$locale" "ON"; l_on="$PFU_MSG_RESULT"
pfu_msg_into "$locale" "OFF"; l_off="$PFU_MSG_RESULT"
@@ -404,17 +475,26 @@ pfu_ui_settings() {
while true; do
if (( dirty )); then
PFU_KV_CACHE=()
for i in "${!keys[@]}"; do
declare -A current=()
for i in "${rows[@]}"; do
_pfu_setting_value "${scopes[i]}" "${keys[i]}" "${defaults[i]}"
values[i]="$PFU_SETTING_VALUE"
current[${keys[i]}]="$PFU_SETTING_VALUE"
done
dirty=0
fi
frame="$clearseq"$'\n'"${PFU_C_BOLD}${PFU_C_BLUE} ${title}${PFU_C_RESET}"$'\n\n'
frame="$clearseq"$'\n'"${PFU_C_BOLD}${PFU_C_BLUE} ${title}${PFU_C_RESET}"$'\n'
local marker selected="${PFU_C_BLUE}▸${PFU_C_RESET} " star
local selected=${rows[cursor]} marker before after dim
for i in "${!keys[@]}"; do
if [[ ${scopes[i]} == group ]]; then
frame+=$'\n'" ${PFU_C_BOLD}${labels[i]}${PFU_C_RESET}"
# The next row says whose settings these are.
[[ ${scopes[i + 1]} != user ]] && frame+=" ${PFU_C_DIM}${shared}${PFU_C_RESET}"
frame+=$'\n'
continue
fi
case "${types[i]}" in
bool)
if pfu_is_true "${values[i]}"; then
@@ -426,22 +506,27 @@ pfu_ui_settings() {
camera) shown="$(_pfu_camera_label "${values[i]}")" ;;
*) shown="$(pfu_value_label "${keys[i]}" "${values[i]}")" ;;
esac
star=' '
[[ ${scopes[i]} != user ]] && star='*'
pad=$(( 44 - ${#labels[i]} ))
(( pad < 0 )) && pad=0
if (( i == cursor )); then marker="$selected"; else marker=' '; fi
printf -v row ' %s%s%s%*s %s' "$marker" "${labels[i]}" "${PFU_C_DIM}${star}${PFU_C_RESET}" "$pad" '' "$shown"
# Arrows on the selected choice: left and right step through it.
before=' ' after=''
if (( i == selected )) && [[ ${types[i]} != bool ]]; then
before="${PFU_C_BLUE}◂${PFU_C_RESET} " after=" ${PFU_C_BLUE}▸${PFU_C_RESET}"
fi
dim=''
[[ -n ${needs[i]} ]] && ! pfu_is_true "${current[${needs[i]}]}" && dim="$PFU_C_DIM"
pad=$(( width + 2 - ${#labels[i]} ))
if (( i == selected )); then marker="${PFU_C_BLUE}▸${PFU_C_RESET} "; else marker=' '; fi
printf -v row ' %s%s%s%s%*s%s%s%s' "$marker" "$dim" "${labels[i]}" "$PFU_C_RESET" "$pad" '' "$before" "$dim$shown$PFU_C_RESET" "$after"
frame+="$row"$'\n'
# A gap between the groups: the lock screen, other prompts, how it
# checks, the bubble.
case "${keys[i]}" in
ScanOnLock|polkit-1|SkipLidClosed) frame+=$'\n' ;;
esac
done
frame+=$'\n'" ${PFU_C_DIM}${legend}${PFU_C_RESET}"$'\n'
frame+=" ${PFU_C_DIM}${hint}${PFU_C_RESET}"$'\n'
# What the selected setting does, in a box of fixed height so the
# screen does not jump while moving through the list.
_pfu_wrap "$wrap" "$(pfu_setting_help "${keys[selected]}" "${values[selected]}")"
frame+=$'\n'" ${PFU_C_DIM}${rule}${PFU_C_RESET}"$'\n'
for j in 0 1 2; do
frame+=" ${PFU_WRAP_LINES[j]:-}"$'\n'
done
frame+=$'\n'" ${PFU_C_DIM}${hint}${PFU_C_RESET}"$'\n'
_pfu_ui_take_notices
frame+="$PFU_UI_NOTICE_TEXT"
printf '%s' "$frame"
@@ -451,8 +536,10 @@ pfu_ui_settings() {
case "$key" in
up|k) cursor=$(( (cursor - 1 + count) % count )) ;;
down|j) cursor=$(( (cursor + 1) % count )) ;;
space|enter|right|l)
_pfu_setting_change "${scopes[cursor]}" "${keys[cursor]}" "${types[cursor]}" "${values[cursor]}" "${choices[cursor]}"
space|enter|right|l|left|h)
local step=1
[[ $key == left || $key == h ]] && step=-1
_pfu_setting_change "${scopes[selected]}" "${keys[selected]}" "${types[selected]}" "${values[selected]}" "${choices[selected]}" "$step"
dirty=1
;;
q|Q|escape) return 0 ;;