From 9aa3bc8353e500ffd04e6371c358c24191a19a98 Mon Sep 17 00:00:00 2001 From: fy59 Date: Fri, 9 Oct 2026 20:22:41 +0200 Subject: [PATCH] feat(quickshell): replace swaylock with native lockscreen --- .assets/pkglist-pacman.txt | 1 - README.md | 2 +- bin/.local/bin/labfy-lock | 133 +++++++++ bin/.local/bin/labfy-lock-settings | 30 ++ .../quickshell-lock-style-v2-synthetic.png | Bin 0 -> 47368 bytes .../quickshell-lock-style-v3-synthetic.png | Bin 0 -> 53540 bytes .../quickshell-lock-style-v4-synthetic.png | Bin 0 -> 53520 bytes docs/desktop.md | 2 +- docs/installation.md | 6 +- docs/systemd.md | 9 + portal/README.md | 2 +- .../quickshell/labfy-lock/Appearance.qml | 50 ++++ .../.config/quickshell/labfy-lock/AuthGate.js | 6 + .../quickshell/labfy-lock/LockContent.qml | 281 ++++++++++++++++++ .../quickshell/labfy-lock/LockSurface.qml | 55 ++++ .../labfy-lock/NotificationCardBody.qml | 45 +++ .../.config/quickshell/labfy-lock/Preview.qml | 83 ++++++ .../.config/quickshell/labfy-lock/README.md | 222 ++++++++++++++ .../quickshell/labfy-lock/preview-cover.png | Bin 0 -> 2167 bytes .../.config/quickshell/labfy-lock/shell.qml | 179 +++++++++++ .../.config/quickshell/labfy-sway/Bar.qml | 23 +- .../.config/quickshell/labfy-sway/README.md | 3 +- .../controlcenter/ControlCenter.qml | 2 +- .../notifications/LockProjection.js | 19 ++ .../notifications/NotificationStore.qml | 14 + .../quickshell/labfy-sway/overview/backend.py | 24 +- sway/.config/sway/bind | 2 +- system/pam.d/README.md | 41 +++ system/pam.d/labfy-lock | 6 + systemd/.config/systemd/user/swayidle.service | 3 +- tests/overview/test_backend.py | 9 + tests/qml/tst_session_exit_gate.qml | 4 +- tests/quickshell/test_labfy_lock.py | 101 +++++++ tests/quickshell/test_lock_auth_gate.js | 14 + tests/quickshell/test_lock_projection.js | 21 ++ 35 files changed, 1375 insertions(+), 17 deletions(-) create mode 100755 bin/.local/bin/labfy-lock create mode 100755 bin/.local/bin/labfy-lock-settings create mode 100644 docs/assets/screenshots/quickshell-lock-style-v2-synthetic.png create mode 100644 docs/assets/screenshots/quickshell-lock-style-v3-synthetic.png create mode 100644 docs/assets/screenshots/quickshell-lock-style-v4-synthetic.png create mode 100644 quickshell/.config/quickshell/labfy-lock/Appearance.qml create mode 100644 quickshell/.config/quickshell/labfy-lock/AuthGate.js create mode 100644 quickshell/.config/quickshell/labfy-lock/LockContent.qml create mode 100644 quickshell/.config/quickshell/labfy-lock/LockSurface.qml create mode 100644 quickshell/.config/quickshell/labfy-lock/NotificationCardBody.qml create mode 100644 quickshell/.config/quickshell/labfy-lock/Preview.qml create mode 100644 quickshell/.config/quickshell/labfy-lock/README.md create mode 100644 quickshell/.config/quickshell/labfy-lock/preview-cover.png create mode 100644 quickshell/.config/quickshell/labfy-lock/shell.qml create mode 100644 quickshell/.config/quickshell/labfy-sway/notifications/LockProjection.js create mode 100644 system/pam.d/README.md create mode 100644 system/pam.d/labfy-lock create mode 100644 tests/quickshell/test_labfy_lock.py create mode 100644 tests/quickshell/test_lock_auth_gate.js create mode 100644 tests/quickshell/test_lock_projection.js diff --git a/.assets/pkglist-pacman.txt b/.assets/pkglist-pacman.txt index 72f2f0414..faec6f6de 100644 --- a/.assets/pkglist-pacman.txt +++ b/.assets/pkglist-pacman.txt @@ -180,7 +180,6 @@ strace sudo swaybg swayidle -swaylock swaync system-config-printer tcpdump diff --git a/README.md b/README.md index 447715208..a75ccbd84 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ cd "$HOME/.dotfiles" packages=(atuin bat bin btop cliphist eza fastfetch fzf gtk-3.0 gtklock \ hyprwhspr icons kitty mako nvim opencode portal qt5ct qt6ct \ - quickshell starship sway swaylock systemd themes tmux uwsm wallpapers wofi yazi \ + quickshell starship sway systemd themes tmux uwsm wallpapers wofi yazi \ ytmusic-tui zsh) stow --no --verbose=2 --target="$HOME" "${packages[@]}" diff --git a/bin/.local/bin/labfy-lock b/bin/.local/bin/labfy-lock new file mode 100755 index 000000000..574b450fa --- /dev/null +++ b/bin/.local/bin/labfy-lock @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +"""Lance le verrou QuickShell dédié et attend la confirmation du compositeur.""" + +import fcntl +import json +import os +import pwd +from pathlib import Path +import secrets +import stat +import subprocess +import sys +import time + + +TIMEOUT = 8.0 +CONFIG = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config")) / "quickshell/labfy-lock" + + +def live(pid): + try: + os.kill(pid, 0) + # Orphaned lockers may stay as zombies until the parent reaps them. + stat = Path(f"/proc/{pid}/stat").read_text().rsplit(") ", 1)[1] + return not stat.startswith("Z ") + except (OSError, ValueError): + return False + + +def start_ticks(pid): + try: + # /proc stat comm is parenthesized and may itself contain spaces. + return int(Path(f"/proc/{pid}/stat").read_text().rsplit(") ", 1)[1].split()[19]) + except (OSError, ValueError, IndexError): + return None + + +def avatar_path(username): + path = Path("/var/lib/AccountsService/icons") / username + try: + info = path.lstat() + if not stat.S_ISREG(info.st_mode) or info.st_uid != 0 or not 0 < info.st_size <= 2 * 1024 * 1024: + return "" + with path.open("rb") as stream: + header = stream.read(24) + if header[:8] != b"\x89PNG\r\n\x1a\n" or header[12:16] != b"IHDR": + return "" + width = int.from_bytes(header[16:20], "big") + height = int.from_bytes(header[20:24], "big") + return str(path) if 0 < width <= 512 and 0 < height <= 512 else "" + except OSError: + return "" + + +def state(pid): + try: + result = subprocess.run( + ["quickshell", "ipc", "--pid", str(pid), "call", "lock", "state"], + capture_output=True, text=True, timeout=0.8, check=False, + ) + if result.returncode == 0: + return result.stdout.strip().strip('"') + except (OSError, subprocess.TimeoutExpired): + pass + return "" + + +def main(): + if not CONFIG.joinpath("shell.qml").is_file(): + print("Configuration labfy-lock absente.", file=sys.stderr) + return 1 + runtime = Path(os.environ.get("XDG_RUNTIME_DIR", "")) + if not runtime.is_dir() or runtime.stat().st_uid != os.getuid(): + print("Répertoire runtime privé indisponible.", file=sys.stderr) + return 1 + lockfile = runtime / "labfy-lock.launch" + capture_guard = runtime / "labfy-lock.capture-guard" + fd = os.open(lockfile, os.O_CREAT | os.O_RDWR | os.O_NOFOLLOW, 0o600) + try: + with os.fdopen(fd, "r+", encoding="utf-8") as guard: + fcntl.flock(guard, fcntl.LOCK_EX) + try: + record = json.load(guard) + except (ValueError, OSError): + record = {} + pid = record.get("pid") + generation = record.get("generation") + ticks = record.get("start_ticks") + if (not isinstance(pid, int) or pid <= 0 or not isinstance(generation, str) + or not isinstance(ticks, int) or not live(pid) or start_ticks(pid) != ticks): + generation = secrets.token_hex(16) + # WHY: Overview must stop scheduled captures before any lock + # surface, including the authentication field, can be mapped. + capture_guard.write_text("active\n") + capture_guard.chmod(0o600) + account = pwd.getpwuid(os.getuid()) + env = dict(os.environ, LABFY_LOCK_GENERATION=generation, + LABFY_LOCK_USER=account.pw_name, + LABFY_LOCK_DISPLAY=account.pw_gecos.split(",", 1)[0] or account.pw_name, + LABFY_LOCK_AVATAR=avatar_path(account.pw_name)) + # CONTRACT: the locker survives the wrapper; no secret goes in argv, + # environment, output, or a persistent file. + process = subprocess.Popen( + ["quickshell", "--path", str(CONFIG)], env=env, + stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, start_new_session=True, + close_fds=True, + ) + pid = process.pid + guard.seek(0) + guard.truncate() + json.dump({"pid": pid, "generation": generation, + "start_ticks": start_ticks(pid)}, guard) + guard.flush() + os.fsync(guard.fileno()) + deadline = time.monotonic() + TIMEOUT + expected = generation + ":secure" + while time.monotonic() < deadline: + if not live(pid): + print("Le verrouilleur s’est arrêté avant confirmation.", file=sys.stderr) + return 1 + if state(pid) == expected: + return 0 + time.sleep(0.08) + print("Verrouillage non confirmé par le compositeur.", file=sys.stderr) + return 1 + except OSError as error: + print(f"Démarrage du verrou impossible : {error}", file=sys.stderr) + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/bin/.local/bin/labfy-lock-settings b/bin/.local/bin/labfy-lock-settings new file mode 100755 index 000000000..5b2eedbac --- /dev/null +++ b/bin/.local/bin/labfy-lock-settings @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +"""Préférence média du verrouilleur, à lancer depuis le bureau déverrouillé.""" + +import json +import os +from pathlib import Path +import sys +import tempfile + + +def main(): + if len(sys.argv) != 3 or sys.argv[1] != "media" or sys.argv[2] not in ("hidden", "generic", "metadata"): + print("Usage : labfy-lock-settings media hidden|generic|metadata", file=sys.stderr) + return 2 + directory = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config")) / "quickshell/labfy-lock" + directory.mkdir(mode=0o700, parents=True, exist_ok=True) + target = directory / "preferences.json" + with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8", dir=directory, + prefix=".preferences-", delete=False) as stream: + temporary = Path(stream.name) + os.fchmod(stream.fileno(), 0o600) + json.dump({"version": 1, "media": sys.argv[2]}, stream) + stream.flush() + os.fsync(stream.fileno()) + os.replace(temporary, target) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/docs/assets/screenshots/quickshell-lock-style-v2-synthetic.png b/docs/assets/screenshots/quickshell-lock-style-v2-synthetic.png new file mode 100644 index 0000000000000000000000000000000000000000..ffec288e01a8525f902562c2df9a9f07e915dee1 GIT binary patch literal 47368 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 G@BRz%i&5YJ literal 0 HcmV?d00001 diff --git a/docs/assets/screenshots/quickshell-lock-style-v3-synthetic.png b/docs/assets/screenshots/quickshell-lock-style-v3-synthetic.png new file mode 100644 index 0000000000000000000000000000000000000000..268f1027227351e77929f765768a6ef9d9c96830 GIT binary patch literal 53540 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

{88y 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 LxM+jJqvgl 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 Uz9N_2za$u0NhOI=F{7aW20?)LQ2+n{ literal 0 HcmV?d00001 diff --git a/docs/desktop.md b/docs/desktop.md index 24dc2edeb..2a1fe715e 100644 --- a/docs/desktop.md +++ b/docs/desktop.md @@ -58,6 +58,6 @@ leurs règles existantes restent en vigueur. installer de paquet. `cliphist-text.service`, `cliphist-image.service`, `swayidle.service`, `wlsunset.service`, les agents SSH et keyring restent des unités utilisateur distinctes. Les raccourcis Sway utilisent notamment -`pactl`, `brightnessctl`, `grim`, `slurp`, `wl-copy`, `wofi`, `swaylock` et +`pactl`, `brightnessctl`, `grim`, `slurp`, `wl-copy`, `wofi`, `labfy-lock` et `yazi`. Le détail de QuickShell et des dépendances figure dans son [README](../quickshell/.config/quickshell/labfy-sway/README.md). diff --git a/docs/installation.md b/docs/installation.md index 21f1f6d54..5d25d3f05 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -71,7 +71,7 @@ Les commandes suivantes sont destinées à Bash : packages=( atuin bat bin btop cliphist eza fastfetch fzf gtk-3.0 gtklock hyprwhspr icons kitty mako nvim opencode portal qt5ct qt6ct - quickshell starship sway swaylock systemd themes tmux uwsm wallpapers wofi yazi + quickshell starship sway systemd themes tmux uwsm wallpapers wofi yazi ytmusic-tui zsh ) @@ -93,6 +93,10 @@ changements de fuseau effectués par `timedatectl`. Le service utilisateur aucune règle `sudoers` propre au fuseau n'est nécessaire. Le backend batterie exige TLP et `/usr/local/sbin/batlimit-set`, déployé séparément avec les droits root selon `bin/root/README.md`. +Avant le premier verrouillage par `labfy-lock`, installer la politique +`system/pam.d/labfy-lock` dans `/etc/pam.d/labfy-lock` avec les droits root, +selon [la procédure PAM](../system/pam.d/README.md). Le package Stow +historique `swaylock` n'est pas requis. ## 6. Effectuer le dry-run diff --git a/docs/systemd.md b/docs/systemd.md index e4b266b4a..36f3971a1 100644 --- a/docs/systemd.md +++ b/docs/systemd.md @@ -42,6 +42,15 @@ socket privée et ne modifie pas `swayidle.service`. Voir [portal/README.md](../portal/README.md) pour les cookies, la récupération après déconnexion et le retour arrière. +`swayidle.service` utilise `/home/fy59/.local/bin/labfy-lock` pour le délai +de verrouillage de 300 s et `before-sleep`, après la pause média. Le wrapper +attend la confirmation `secure` avant de rendre la main à `swayidle -w`. +Les autres délais et commandes d'inactivité sont conservés. La politique +PAM dédiée provient de `system/pam.d/labfy-lock` et s'installe en tant que +`/etc/pam.d/labfy-lock` avant un nouveau déploiement du verrou. La procédure +historique de retour à `swaylock` est documentée dans +[labfy-lock/README.md](../quickshell/.config/quickshell/labfy-lock/README.md). + ## Supports amovibles USB/SD et MTP `labfy-removable-media.service` appartient au package Stow `systemd` et son diff --git a/portal/README.md b/portal/README.md index 3d254d203..14b15a6b1 100644 --- a/portal/README.md +++ b/portal/README.md @@ -85,7 +85,7 @@ MIME et ne remplace pas les portails d'autres interfaces. ## Vidéo Firefox et inhibition de l'inactivité (diagnostic du 9 octobre 2026) -Dans cette session SwayFX, `swayidle.service` lance `swaylock` après 300 s, +Lors de ce diagnostic, `swayidle.service` lançait `swaylock` après 300 s, éteint les sorties après 360 s et met les lecteurs en pause après 420 s. Le verrouillage manuel et la protection `before-sleep` sont indépendants. Le symptôme historique n'a pas été observé à l'instant exact du verrouillage ; diff --git a/quickshell/.config/quickshell/labfy-lock/Appearance.qml b/quickshell/.config/quickshell/labfy-lock/Appearance.qml new file mode 100644 index 000000000..66a0b413c --- /dev/null +++ b/quickshell/.config/quickshell/labfy-lock/Appearance.qml @@ -0,0 +1,50 @@ +import QtQuick +import Quickshell +import Quickshell.Io + +QtObject { + id: appearance + // CONTRACT: this reads only the effective state written by the existing + // appearance controller. It never runs a second controller or writes it. + readonly property string path: (Quickshell.env("XDG_STATE_HOME") || Quickshell.env("HOME") + "/.local/state") + + "/labfy-appearance/effective.json" + property FileView stateFile: FileView { path: appearance.path; blockLoading: false; printErrors: false } + property FileView preferenceFile: FileView { + path: (Quickshell.env("XDG_CONFIG_HOME") || Quickshell.env("HOME") + "/.config") + + "/quickshell/labfy-lock/preferences.json" + printErrors: false + } + property FileView paletteFile: FileView { + path: (Quickshell.env("XDG_CONFIG_HOME") || Quickshell.env("HOME") + "/.config") + + "/quickshell/labfy-sway/theme/catppuccin.json" + blockLoading: false + printErrors: false + } + readonly property var state: { + try { + const value = JSON.parse(stateFile.text()); + return value && typeof value === "object" ? value : {}; + } catch (_) { return {}; } + } + readonly property string wallpaper: typeof state.effectiveWallpaper === "string" + && state.effectiveWallpaper.startsWith("/") ? "file://" + state.effectiveWallpaper : "" + readonly property string mediaVisibility: { + // CONTRACT: an explicit privacy preference wins; without one, show + // available local artwork and metadata as requested by the V3 layout. + try { + const value = JSON.parse(preferenceFile.text()); + return value.version === 1 && ["hidden", "generic", "metadata"].includes(value.media) + ? value.media : "metadata"; + } catch (_) { return "metadata"; } + } + readonly property var palette: { + try { + const palettes = JSON.parse(paletteFile.text()); + return palettes[state.effectiveFlavor] || palettes.mocha || {}; + } catch (_) { return {}; } + } + readonly property color base: palette.base || "#1e1e2e" + readonly property color surface: palette.surface0 || "#313244" + readonly property color foreground: palette.text || "#ffffff" + readonly property color accent: palette[state.effectiveAccent] || "#b4befe" +} diff --git a/quickshell/.config/quickshell/labfy-lock/AuthGate.js b/quickshell/.config/quickshell/labfy-lock/AuthGate.js new file mode 100644 index 000000000..923742ec3 --- /dev/null +++ b/quickshell/.config/quickshell/labfy-lock/AuthGate.js @@ -0,0 +1,6 @@ +// INVARIANT: only an explicit success for the still-current, non-aborted +// conversation while compositor secure may release the session lock. +function mayUnlock(active, aborting, secure, result, success) { + return active === true && aborting === false && secure === true + && result === success; +} diff --git a/quickshell/.config/quickshell/labfy-lock/LockContent.qml b/quickshell/.config/quickshell/labfy-lock/LockContent.qml new file mode 100644 index 000000000..4364f12b2 --- /dev/null +++ b/quickshell/.config/quickshell/labfy-lock/LockContent.qml @@ -0,0 +1,281 @@ +import QtQuick +import QtQuick.Controls +import Quickshell +import Quickshell.Services.UPower + +Item { + id: content + required property var controller + required property var hostSurface + required property var player + required property var notificationData + required property var battery + property bool preview: false + property var previewState: ({}) + property alias field: answer + + Appearance { id: appearance } + SystemClock { id: clock; precision: SystemClock.Minutes } + + readonly property string mediaMode: preview && previewState.mediaMode + ? previewState.mediaMode : appearance.mediaVisibility + readonly property bool lightTheme: appearance.state.effectiveFlavor === "latte" + readonly property bool mediaShown: !!player && mediaMode !== "hidden" + readonly property bool notificationsShown: !!notificationData && notificationData.total > 0 + readonly property bool compact: height < 680 || width < 900 + readonly property int cardWidth: Math.min(compact ? 340 : 380, width - 32) + // CONTRACT: artwork remains local-only. Long but bounded file URIs from + // MPRIS caches must reach Image; Image.status decides whether they work. + readonly property string coverUri: mediaMode === "metadata" && player + && typeof player.trackArtUrl === "string" + && player.trackArtUrl.startsWith("file://") && player.trackArtUrl.length <= 4096 + ? player.trackArtUrl : "" + readonly property bool coverUsable: coverUri && coverImage.status !== Image.Error + readonly property string dateText: { + const value = Qt.locale("fr_FR").toString(clock.date, "dddd d MMMM"); + return value.charAt(0).toUpperCase() + value.slice(1); + } + + Rectangle { anchors.fill: parent; color: appearance.base } + // CONTRACT: only the existing effective wallpaper is rendered; a failed + // load retains an opaque palette background, never a desktop capture. + Image { + anchors.fill: parent + source: content.preview ? "" : appearance.wallpaper + asynchronous: true + fillMode: Image.PreserveAspectCrop + visible: status === Image.Ready + sourceSize.width: Math.min(content.width * 1.5, 2560) + sourceSize.height: Math.min(content.height * 1.5, 1600) + } + Rectangle { anchors.fill: parent; color: content.lightTheme ? "#b2eff1f5" : "#991e1e2e" } + + // INVARIANT: one bounded central column preserves the requested reading + // order on every output; optional cards collapse without moving auth into + // a corner. The reserved feedback area absorbs PAM and Caps Lock changes. + Column { + id: stack + width: content.cardWidth + anchors.horizontalCenter: parent.horizontalCenter + y: Math.max(36, (content.height - height) / 2) + spacing: content.compact ? 4 : 6 + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: content.preview && content.previewState.time ? content.previewState.time : Qt.formatTime(clock.date, "HH:mm") + color: appearance.foreground + font.family: "JetBrainsMono Nerd Font" + font.pixelSize: content.compact ? 62 : 76 + font.weight: Font.Light + } + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: content.preview && content.previewState.date ? content.previewState.date : content.dateText + color: appearance.foreground + opacity: 0.82 + font.pixelSize: content.compact ? 15 : 18 + } + Item { width: 1; height: content.compact ? 5 : 9 } + Item { + width: parent.width; height: content.compact ? 46 : 58 + Rectangle { + anchors.horizontalCenter: parent.horizontalCenter + width: parent.height; height: parent.height; radius: 4 + color: appearance.surface + border.color: appearance.accent + clip: true + Image { + id: avatarImage + anchors.fill: parent + asynchronous: true + source: !content.preview && controller.avatar ? "file://" + controller.avatar : "" + fillMode: Image.PreserveAspectCrop + sourceSize.width: 128; sourceSize.height: 128 + visible: status === Image.Ready + } + Text { + anchors.centerIn: parent + text: ""; color: appearance.foreground + font.family: "JetBrainsMono Nerd Font"; font.pixelSize: 29 + visible: !avatarImage.visible + } + } + } + Text { + width: parent.width; horizontalAlignment: Text.AlignHCenter + text: controller.displayName; color: appearance.foreground + font.pixelSize: content.compact ? 16 : 18; elide: Text.ElideRight + } + Text { + width: parent.width; height: visible ? 15 : 0 + horizontalAlignment: Text.AlignHCenter + visible: controller.displayName !== controller.loginUser + text: controller.loginUser; color: appearance.foreground; opacity: 0.72 + font.pixelSize: 11; elide: Text.ElideRight + } + Text { + width: parent.width; height: visible ? 15 : 0 + horizontalAlignment: Text.AlignHCenter + visible: !!content.battery && content.battery.isPresent + text: visible ? Math.round(content.battery.percentage * 100) + " % · " + + (content.battery.state === UPowerDeviceState.Charging ? "En charge" + : content.battery.state === UPowerDeviceState.Discharging ? "Sur batterie" : "Sur secteur") : "" + color: appearance.foreground; opacity: 0.68; font.pixelSize: 11 + } + Item { width: 1; height: content.compact ? 1 : 4 } + Item { + id: auth + width: parent.width; height: 118 + Text { + width: parent.width; height: 22 + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: controller.attempt && controller.activeSurface === hostSurface + && hostSurface.secure && controller.pamResponseRequired + ? (controller.prompt || "Réponse attendue") : "Déverrouillage" + color: appearance.foreground; font.pixelSize: 13 + elide: Text.ElideRight + } + TextField { + id: answer + y: 25; width: parent.width; height: 42 + color: appearance.foreground + horizontalAlignment: TextInput.AlignHCenter + background: Rectangle { + radius: 4; color: appearance.surface + border.color: answer.activeFocus ? appearance.accent : "#777783" + border.width: 1 + } + echoMode: controller.responseVisible ? TextInput.Normal : TextInput.Password + enabled: !content.preview && controller.activeSurface === hostSurface + && hostSurface.secure && !controller.aborting + placeholderText: content.preview ? "Saisie désactivée dans l’aperçu" + : controller.attempt ? "Réponse" : "Appuyer sur Entrée pour commencer" + onAccepted: controller.submit(text) + Keys.onEscapePressed: controller.cancel() + Keys.onPressed: event => { + if (event.key === Qt.Key_CapsLock) { + controller.capsObserved = true; + controller.capsOn = !controller.capsOn; + } + } + Component.onCompleted: if (!content.preview && controller.activeSurface === hostSurface) forceActiveFocus() + } + // CONTRACT: this fixed area contains keyboard, Caps Lock and PAM + // feedback; their appearance never moves the password field. + Text { + y: 70; width: parent.width; height: 17 + text: (controller.keyboardLayout || "Disposition inconnue") + + (controller.capsObserved && controller.capsOn ? " · Verr. maj. activé" : "") + color: appearance.foreground; opacity: 0.76; font.pixelSize: 11 + horizontalAlignment: Text.AlignHCenter; elide: Text.ElideRight + } + Text { + y: 88; width: parent.width; height: 30 + text: controller.feedback + color: controller.feedbackIsError + ? (appearance.palette.red || "#f38ba8") : appearance.foreground + opacity: controller.feedbackIsError ? 1 : 0.76 + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignTop + wrapMode: Text.WordWrap; elide: Text.ElideRight + } + MouseArea { + anchors.fill: parent + enabled: !content.preview && controller.activeSurface !== hostSurface + onClicked: controller.chooseSurface(hostSurface) + } + } + // CONTRACT: media keeps its established visibility policy. Valid local + // artwork gets a stable visible slot; failed loads use the small fallback. + Rectangle { + id: mediaCard + width: parent.width; height: visible ? (content.compact ? 82 : 98) : 0 + radius: 4; visible: content.mediaShown + color: content.lightTheme ? "#dceff1f5" : "#d41e1e2e" + border.color: appearance.surface; border.width: 1 + Rectangle { + x: 10; anchors.verticalCenter: parent.verticalCenter + width: content.coverUsable ? 72 : 32 + height: width; radius: 4 + color: appearance.surface; clip: true + Image { + id: coverImage + anchors.fill: parent + source: content.coverUri + asynchronous: true; fillMode: Image.PreserveAspectCrop + sourceSize.width: 144; sourceSize.height: 144 + } + Text { + visible: !content.coverUsable + anchors.centerIn: parent; text: "" + font.family: "JetBrainsMono Nerd Font"; font.pixelSize: 20 + color: appearance.foreground; opacity: 0.55 + } + } + Column { + x: content.coverUsable ? 94 : 52 + y: content.compact ? 8 : 10 + width: parent.width - x - 10; spacing: content.compact ? 2 : 5 + Text { + width: parent.width; elide: Text.ElideRight + text: content.mediaMode === "metadata" && content.player + ? content.player.trackTitle : "Lecture média" + color: appearance.foreground; font.pixelSize: 13 + } + Text { + width: parent.width; elide: Text.ElideRight + text: content.mediaMode === "metadata" && content.player + ? content.player.trackArtist : "" + color: appearance.foreground; opacity: 0.72; font.pixelSize: 12 + } + Row { + spacing: 8 + Repeater { + model: [ + { label: "⏮", ability: "canGoPrevious", action: "previous" }, + { label: content.player && content.player.isPlaying ? "⏸" : "▶", + ability: "canTogglePlaying", action: "togglePlaying" }, + { label: "⏭", ability: "canGoNext", action: "next" } + ] + Rectangle { + required property var modelData + readonly property bool allowed: content.player + && content.player[modelData.ability] === true + visible: allowed + width: visible ? 34 : 0; height: 26; radius: 4 + color: appearance.surface + Text { anchors.centerIn: parent; text: parent.modelData.label; color: appearance.foreground } + MouseArea { + anchors.fill: parent; enabled: parent.allowed && !content.preview + onClicked: if (content.player && parent.allowed) + content.player[parent.modelData.action]() + } + } + } + } + } + Rectangle { + x: 10; y: parent.height - 5; width: parent.width - 20; height: 2 + visible: content.mediaMode === "metadata" && content.player + && content.player.positionSupported && content.player.length > 0 + color: "#45475a" + Rectangle { + height: parent.height + width: content.player && content.player.length > 0 + ? parent.width * Math.max(0, Math.min(1, content.player.position / content.player.length)) : 0 + color: appearance.accent + } + } + } + // INVARIANT: notification projection contains public app counters only; + // no summary, body or history is introduced by this visual card. + Rectangle { + width: parent.width; height: visible ? (content.compact ? 83 : 100) : 0 + radius: 4; visible: content.notificationsShown + color: content.lightTheme ? "#dceff1f5" : "#d41e1e2e" + border.color: appearance.surface; border.width: 1 + NotificationCardBody { anchors.fill: parent; projection: content.notificationData; foreground: appearance.foreground } + } + } +} diff --git a/quickshell/.config/quickshell/labfy-lock/LockSurface.qml b/quickshell/.config/quickshell/labfy-lock/LockSurface.qml new file mode 100644 index 000000000..532c04bfd --- /dev/null +++ b/quickshell/.config/quickshell/labfy-lock/LockSurface.qml @@ -0,0 +1,55 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Services.UPower +import Quickshell.Services.Mpris + +WlSessionLockSurface { + id: surface + required property var controller + required property bool secure + property alias field: content.field + color: "#1e1e2e" + readonly property var battery: UPower.displayDevice + readonly property string notificationPath: (Quickshell.env("XDG_RUNTIME_DIR") || "") + + "/labfy-lock-notifications.json" + FileView { id: notificationFile; path: surface.notificationPath; printErrors: false } + readonly property var notificationData: { + try { + const raw = notificationFile.text(); + if (raw.length > 4096) return null; + const data = JSON.parse(raw); + if (data.version !== 1 || !Number.isSafeInteger(data.total) || data.total < 0 + || data.total > 200 || !Array.isArray(data.apps)) return null; + return { total: data.total, apps: data.apps.slice(0, 4).filter(item => item + && typeof item.name === "string" && /^[^\r\n\t]{1,48}$/.test(item.name) + && Number.isSafeInteger(item.count) && item.count >= 0 && item.count <= 200 + && typeof item.icon === "string" && /^[A-Za-z0-9_.-]{0,64}$/.test(item.icon)) }; + } catch (_) { return null; } + } + readonly property var player: { + const items = Mpris.players.values.filter(item => item + && item.dbusName !== "org.mpris.MediaPlayer2.playerctld" + && item.trackTitle && item.trackTitle.trim().length); + items.sort((a, b) => a.dbusName.localeCompare(b.dbusName)); + return items.find(item => item.playbackState === MprisPlaybackState.Playing) + || items.find(item => item.playbackState === MprisPlaybackState.Paused) || null; + } + Component.onCompleted: controller.addSurface(surface) + Component.onDestruction: controller.removeSurface(surface) + + // CONTRACT: the shared visual component owns presentation and input focus; + // the lock, PAM conversation, and privacy-filtered data stay on this surface. + LockContent { + id: content + anchors.fill: parent + controller: surface.controller + hostSurface: surface + player: surface.player + notificationData: surface.notificationData + battery: surface.battery + } +} diff --git a/quickshell/.config/quickshell/labfy-lock/NotificationCardBody.qml b/quickshell/.config/quickshell/labfy-lock/NotificationCardBody.qml new file mode 100644 index 000000000..25c53cb58 --- /dev/null +++ b/quickshell/.config/quickshell/labfy-lock/NotificationCardBody.qml @@ -0,0 +1,45 @@ +import QtQuick +import Quickshell + +Item { + id: body + required property var projection + required property color foreground + Column { + anchors.fill: parent; anchors.margins: 9; spacing: 5 + Text { + width: parent.width; color: body.foreground; font.pixelSize: 13 + text: "Notifications · " + (body.projection ? body.projection.total : 0) + } + Repeater { + // CONTRACT: show at most two public app counters, never private + // notification bodies or extra fields from the projection. + model: body.projection ? body.projection.apps.slice(0, 2) : [] + Row { + required property var modelData + width: parent.width; spacing: 8 + Item { + width: 16; height: 16 + Image { + id: appIcon + anchors.fill: parent + source: modelData.icon ? Quickshell.iconPath(modelData.icon, true) : "" + sourceSize.width: 32; sourceSize.height: 32 + visible: status === Image.Ready + } + Text { + anchors.centerIn: parent + visible: !appIcon.visible + text: ""; color: body.foreground; opacity: 0.68 + font.family: "JetBrainsMono Nerd Font"; font.pixelSize: 12 + } + } + Text { + width: parent.width - 24; color: body.foreground + font.pixelSize: 12; elide: Text.ElideRight + text: modelData.name + " · " + modelData.count + } + } + } + } +} diff --git a/quickshell/.config/quickshell/labfy-lock/Preview.qml b/quickshell/.config/quickshell/labfy-lock/Preview.qml new file mode 100644 index 000000000..e305d111a --- /dev/null +++ b/quickshell/.config/quickshell/labfy-lock/Preview.qml @@ -0,0 +1,83 @@ +import QtQuick +import Quickshell +import Quickshell.Wayland + +// TEST_ONLY: this file cannot acquire a session lock and never creates PAM. +// All preview content is synthetic; the production wrapper selects shell.qml. +ShellRoot { + PanelWindow { + id: preview + screen: Quickshell.screens[0] + anchors { left: true; right: true; top: true; bottom: true } + exclusionMode: ExclusionMode.Ignore + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.namespace: "labfy-lock-preview-non-locked" + color: "#1e1e2e" + + readonly property string scenario: Quickshell.env("LABFY_LOCK_PREVIEW_SCENARIO") || "both" + readonly property bool mediaPresent: ["both", "media", "long", "noart"].includes(scenario) + // TEST_ONLY: the override lets the fixture exercise long/invalid URIs + // using a copy of its own synthetic cover; production ignores it. + readonly property string artUri: Quickshell.env("LABFY_LOCK_PREVIEW_ART_URI") + || Qt.resolvedUrl("preview-cover.png").toString() + readonly property bool notificationsPresent: ["both", "notifications", "long"].includes(scenario) + readonly property bool batteryPresent: Quickshell.env("LABFY_LOCK_PREVIEW_BATTERY") !== "absent" + readonly property string feedback: Quickshell.env("LABFY_LOCK_PREVIEW_FEEDBACK") === "error" + ? "Authentification refusée. Appuyez sur Entrée pour réessayer." + : Quickshell.env("LABFY_LOCK_PREVIEW_FEEDBACK") === "pam" + ? "Message PAM de démonstration." : "" + readonly property bool feedbackIsError: Quickshell.env("LABFY_LOCK_PREVIEW_FEEDBACK") === "error" + readonly property bool capsOn: Quickshell.env("LABFY_LOCK_PREVIEW_CAPS") === "on" + readonly property bool promptMode: Quickshell.env("LABFY_LOCK_PREVIEW_FEEDBACK") === "prompt" + readonly property var dummySurface: ({ secure: preview.promptMode }) + readonly property var dummyController: ({ + displayName: "Compte de démonstration", loginUser: "demo", avatar: "", + attempt: preview.promptMode, + activeSurface: preview.promptMode ? preview.dummySurface : null, + secure: preview.promptMode, + pamResponseRequired: preview.promptMode, prompt: preview.promptMode ? "Mot de passe" : "", + responseVisible: false, aborting: false, keyboardLayout: "Français", + capsObserved: preview.capsOn, capsOn: preview.capsOn, + feedback: preview.feedback, feedbackIsError: preview.feedbackIsError, + submit: function() {}, cancel: function() {}, chooseSurface: function() {} + }) + readonly property var dummyPlayer: mediaPresent ? ({ + trackTitle: scenario === "long" ? "Un titre synthétique très long pour vérifier que le texte reste dans la carte sans déplacer les commandes" : "Morceau de démonstration", + trackArtist: "Artiste fictif", + trackArtUrl: scenario === "noart" ? "" : preview.artUri, + isPlaying: true, + canGoPrevious: true, canTogglePlaying: true, canGoNext: true, + positionSupported: false, length: 0 + }) : null + readonly property var dummyNotifications: notificationsPresent ? ({ + total: 3, apps: [ + { name: scenario === "long" ? "Application de démonstration au nom volontairement très long" : "Messagerie fictive", count: 2, icon: "mail-unread" }, + { name: "Agenda fictif", count: 1, icon: "x-office-calendar" } + ] + }) : null + readonly property var dummyBattery: batteryPresent ? ({ isPresent: true, percentage: 0.73, state: -1 }) : null + + LockContent { + anchors.fill: parent + preview: true + previewState: ({ time: "18:42", date: "Vendredi 9 octobre", mediaMode: "metadata" }) + controller: preview.dummyController + hostSurface: preview.dummySurface + player: preview.dummyPlayer + notificationData: preview.dummyNotifications + battery: preview.dummyBattery + } + Rectangle { + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + width: warning.implicitWidth + 24; height: 30 + color: "#1e1e2e"; border.color: "#f38ba8"; radius: 4 + Text { + id: warning + anchors.centerIn: parent + text: "APERÇU — NON VERROUILLÉ" + color: "#f38ba8"; font.pixelSize: 14; font.bold: true + } + } + } +} diff --git a/quickshell/.config/quickshell/labfy-lock/README.md b/quickshell/.config/quickshell/labfy-lock/README.md new file mode 100644 index 000000000..8b4b9cba9 --- /dev/null +++ b/quickshell/.config/quickshell/labfy-lock/README.md @@ -0,0 +1,222 @@ +# labfy-lock V1 — verrouillage de production + +`labfy-lock` lance une configuration QuickShell distincte de `labfy-sway`. +Elle n'importe ni `shell.qml` ni `Bar.qml` et ne crée ni serveur de +notifications, ni agent Polkit, ni contrôleur d'apparence. `WlSessionLock` +demande au compositeur une surface opaque par sortie. Une seule conversation +`PamContext` est partagée par toutes les sorties. Le wrapper sérialise les +demandes avec `flock`, associe le PID à son heure de création `/proc` et à +une génération aléatoire, puis interroge **ce PID** jusqu'à `secure`. Son +retour ne signifie jamais que PAM a réussi. + +## État du lot + +Le processus et le wrapper sont installés par les liens Stow. Le raccourci +Sway `$mod+Alt+o`, le bouton Verrouiller du Control Center et les commandes +`timeout 300` et `before-sleep` de `swayidle.service` appellent tous +`/home/fy59/.local/bin/labfy-lock`. Les délais 300/360/420 s, l'alimentation +des sorties, la pause média et `idlehint 420` restent identiques. Le wrapper +rend la main après la confirmation `secure`, sans attendre le déverrouillage. +Le service PAM dédié `labfy-lock` est installé sous `/etc/pam.d/labfy-lock`. +Le paquet `swaylock` a été retiré après deux déverrouillages réels réussis, +dont un après sa suppression. Le fichier PAM qu'il fournissait a disparu ; +la politique dédiée reste installée. +Aucune action de veille/reprise physique n'a été testée. + +La source suivie de la politique est +[`system/pam.d/labfy-lock`](../../../../system/pam.d/labfy-lock), avec sa +[procédure de réinstallation](../../../../system/pam.d/README.md). Un +déverrouillage réel avec `config: "labfy-lock"` a obtenu `PamResult.Success`, +libéré `WlSessionLock` et rendu le bureau avant et après suppression du paquet. +Le paquet `swaylock` n'est plus requis pour cette authentification. + +## PAM et saisie + +`PamContext` nomme explicitement le service `/etc/pam.d/labfy-lock`, qui +contient `auth include login`, comme l'ancienne politique `swaylock`. +La chaîne `login` inclut `system-local-login`, `system-login`, puis +`system-auth` pour la phase `auth`, notamment `pam_faillock` et `pam_unix`. +QuickShell 0.3.1 ne prend en charge que `auth` : cela ne démontre pas une +équivalence des phases `account` et `session` du binaire `swaylock`. +Les piles PAM globales ne sont pas modifiées. + +L'utilisateur provient de l'UID effectif via `pwd.getpwuid`, sans identifiant +codé en dur. La conversation PAM démarre dès que le verrou est `secure`. +Entrée répond uniquement lorsque `responseRequired` est vrai ; si la demande +n'est pas encore prête, la saisie reste dans le champ. Le libellé et la +visibilité proviennent de PAM. +Échap vide la saisie et annule la conversation. Seul `PamResult.Success` de +la conversation non annulée, avec verrou `secure`, appelle `unlock()`. +`Failed`, `Error`, `MaxTries` et les réponses tardives ne libèrent rien. +La zone de saisie est vidée après envoi et fin de conversation. Cela ne +garantit pas l'effacement physique de toute la mémoire Qt/QML. + +La disposition active vient de `swaymsg get_inputs` quand disponible. +L'état initial de Verr. maj. n'est pas exposé par cette source ; l'indication +après une pression sur la touche est indicative, pas un contrôle matériel. + +## Apparence et confidentialité + +Le fond provient de `labfy-appearance/effective.json`, pas d'une capture. +La palette Catppuccin effective est lue sans lancer de second contrôleur. +Une couleur opaque reste présente si le fond ou la palette manque. L'avatar +vient exclusivement d'une image PNG AccountsService validée, sinon un +pictogramme neutre. Le média MPRIS exclut `playerctld`, respecte les +capacités des boutons et ne charge que des pochettes `file://` bornées. +La progression n'est montrée que si le lecteur expose une position fiable. +Aucune action d'ouverture d'application, navigateur ou URI n'est présente. + +Depuis le bureau déverrouillé : + +```sh +labfy-lock-settings media hidden # masque la carte +labfy-lock-settings media generic # commandes, sans métadonnées +labfy-lock-settings media metadata # titre, artiste et pochette locale +``` + +Le serveur de notifications de la barre publie une projection bornée dans +`$XDG_RUNTIME_DIR` : icône, nom public et compteurs, au plus quatre +lignes. Le lockscreen ne lit jamais l'historique privé. Si la barre n'a pas +encore publié ce fichier, la carte est absente. Sa présentation ne marque +aucune notification comme lue. + +Le wrapper pose un garde de capture **avant** de démarrer QuickShell. +L'Overview et son backend refusent alors les captures automatiques. Le garde +reste actif après un crash ; il passe à `released` seulement après le +déverrouillage PAM. Ce fichier est une protection des captures, pas une preuve +du verrouillage Wayland. `Quickshell.watchFiles = false` désactive le +rechargement du code dans le processus verrouilleur ; les FileView de données +peuvent toujours se mettre à jour. + +## Essai visuel et essai réel + +La fixture autonome ne demande jamais de mot de passe ni PAM : + +```sh +quickshell --path ~/.config/quickshell/labfy-lock/Preview.qml +``` + +Elle affiche « APERÇU — NON VERROUILLÉ » et des données synthétiques. +Elle vérifie les proportions et la suppression des cartes sur petit écran, +mais ne remplace pas un essai de la vraie surface de verrou. + +Avant un essai réel, arrêter les captures et diagnostics pouvant +observer la saisie, vérifier l'accès à un TTY distinct et conserver une +session disponible pour la récupération. L'utilisateur lance localement +`labfy-lock`, saisit lui-même son secret, puis confirme le retour au bureau. +Ne jamais transmettre le secret dans un chat, un script ou un fichier. +Le déclenchement par l'IPC Sway de la commande du raccourci a été confirmé +jusqu'à l'état `secure`. Le timeout réel de 300 s, l'extinction d'écran et +la séquence d'inactivité ont ensuite été validés en usage réel. + +## Récupération et retour arrière + +En SwayFX headless isolé, après un `SIGKILL` du verrouilleur confirmé, une +nouvelle instance `labfy-lock` a obtenu `secure`. Cela teste une voie de +récupération pour **ce build** ; cela ne garantit pas tous les états d'une +session physique ni la conservation de l'état après une panne du compositeur. +Un crash laisse l'écran protégé par la politique du compositeur. Depuis un +TTY distinct et avec le même utilisateur, on peut tenter une nouvelle +instance en visant le socket Wayland de la session : + +```sh +XDG_RUNTIME_DIR=/run/user/$(id -u) WAYLAND_DISPLAY=wayland-1 \ + DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus \ + ~/.local/bin/labfy-lock +``` + +Vérifier le vrai nom du socket avant cette commande. Si un autre verrou +`WlSessionLock` est déjà présent, ne pas le tuer : le second verrou est +refusé. Aucun repli automatique vers un autre verrouilleur n'a lieu après +acquisition. Un échec du wrapper ne retarde pas indéfiniment logind ; le +délai observé est de cinq secondes. + +Pour un retour arrière PAM, réinstaller d'abord le paquet avec +`sudo pacman -S swaylock`, confirmer l'existence de `/etc/pam.d/swaylock`, +remettre temporairement `config: "swaylock"` dans le seul `PamContext`, +recharger uniquement le lockscreen si nécessaire, puis refaire un essai +manuel. Ne pas exécuter ce retour arrière tant que `labfy-lock` fonctionne. + +Pour revenir aussi au verrouilleur historique, restaurer dans la source suivie +`systemd/.config/systemd/user/swayidle.service` exactement +`timeout 300 'swaylock -f -c 000000'` et +`before-sleep 'playerctl -a pause 2>/dev/null || true; swaylock -f -c 000000'`. +Puis exécuter `systemctl --user daemon-reload` et redémarrer +`swayidle.service` uniquement s'il était actif avant le rollback. Restaurer +également `exec swaylock` dans `sway/.config/sway/bind` et `["swaylock"]` +pour l'action Verrouiller du Control Center, puis recharger Sway. Ne jamais +lancer `swaylock` par-dessus un `WlSessionLock` encore détenu. + +## Validation isolée + +QuickShell 0.3.1, SwayFX 0.6. Tests : `qmllint`, garde PAM, projection privée, +wrapper concurrent, refus d'un second `WlSessionLock`, réacquisition après crash, ajout et +retrait d'une sortie avec échelle 1,5 et redimensionnement, aperçu sur +1280×720 et 800×600. Démarrage jusqu'à `secure` : 0,209 à 0,216 s sur trois +sessions headless fraîches ; RSS 443 à 448 Mo, PSS environ 341 Mo avec +`WLR_RENDERER=gles2` logiciel. Un verrou QuickShell minimal mesure déjà +environ 308 Mo RSS et 219 Mo PSS dans cette configuration ; ces valeurs ne +prédisent pas la consommation sur le GPU physique. Le 9 octobre 2026, un +déverrouillage PAM sur la session physique `tty1` a été confirmé par l'arrêt +du verrouilleur et le passage du garde de capture à `released`. La +veille/reprise et un ensemble de sorties physiques n'ont pas été validés. + +## Composition visuelle V2 (historique, remplacée par V3) + +`LockContent.qml` sert à la surface verrouillée et à la fixture non verrouillée. +L'heure et la date en français sont centrées en haut ; la batterie, quand elle +existe, reste sous la date. L'avatar, l'identité, la demande PAM, la saisie, +la disposition clavier et une ligne de retour réservée forment un bloc central +stable. Les cartes média et notifications publiques sont réunies en bas, +centrées, avec au plus deux cartes de 420 px chacune. Chaque carte absente +libère sa place. Sous 900 px de largeur, elles s'empilent ; sous 850 px de +hauteur dans ce mode, elles disparaissent pour préserver le formulaire. Sous +680 px de hauteur, les cartes disparaissent également. + +La fixture emploie uniquement des données synthétiques, sans fond personnel. +Les variables suivantes permettent de vérifier les états, sans PAM ni verrou : + +```sh +LABFY_LOCK_PREVIEW_SCENARIO=both quickshell --path ~/.config/quickshell/labfy-lock/Preview.qml +LABFY_LOCK_PREVIEW_SCENARIO=media quickshell --path ~/.config/quickshell/labfy-lock/Preview.qml +LABFY_LOCK_PREVIEW_SCENARIO=notifications quickshell --path ~/.config/quickshell/labfy-lock/Preview.qml +LABFY_LOCK_PREVIEW_SCENARIO=none LABFY_LOCK_PREVIEW_BATTERY=absent \ + LABFY_LOCK_PREVIEW_FEEDBACK=error LABFY_LOCK_PREVIEW_CAPS=on \ + quickshell --path ~/.config/quickshell/labfy-lock/Preview.qml +LABFY_LOCK_PREVIEW_SCENARIO=long quickshell --path ~/.config/quickshell/labfy-lock/Preview.qml +LABFY_LOCK_PREVIEW_FEEDBACK=pam quickshell --path ~/.config/quickshell/labfy-lock/Preview.qml +``` + +Capture synthétique : `docs/assets/screenshots/quickshell-lock-style-v2-synthetic.png`. + +## Composition visuelle V3 + +La surface et la fixture partagent désormais une seule colonne horizontale +centrée : heure, date, avatar, identité, batterie discrète, demande et champ de +saisie, zone stable pour disposition clavier/Caps Lock et retour PAM, player, +puis notifications. Les deux cartes secondaires sont immédiatement sous la +zone d'authentification et disparaissent séparément si leurs données manquent. +Elles restent sous le champ à 1280×720 et 800×600, avec une pochette plus petite +sur écran compact. Aucune carte n'est placée dans un coin. + +Le mode média par défaut est désormais `metadata` pour afficher la pochette +lorsqu'elle existe. Dans ce mode, une pochette locale `file://` de 4 096 caractères +au plus est affichée à 72 px, y compris en format compact, avec titre et artiste +tronqués. Si l'URI manque ou si l'image ne se charge pas, le repli est un +pictogramme de 32 px. Les URI distantes restent exclues par la politique locale +existante. Le mode `generic` configuré par l'utilisateur +conserve sa politique de masquage des métadonnées. Seules les commandes +supportées par le lecteur sont visibles. Les notifications n'affichent que deux +applications publiques au plus, avec icône, nom et compteur ; ni résumé ni +corps privé ne sont lus ou affichés. + +La fixture utilise une pochette générée pour les scénarios `both`, `media` et +`long`. `LABFY_LOCK_PREVIEW_SCENARIO=noart` montre le repli sans pochette. +`none`, `notifications`, `LABFY_LOCK_PREVIEW_FEEDBACK=error|pam|prompt` et +`LABFY_LOCK_PREVIEW_CAPS=on` couvrent les autres états synthétiques. +Le prompt PAM normal reste au-dessus du champ, sans doublon rouge sous la saisie. +Seules les erreurs PAM et d'authentification sont rouges ; une information PAM +éventuelle conserve la couleur neutre. La fixture accepte aussi +`LABFY_LOCK_PREVIEW_ART_URI` pour une copie locale synthétique à URI longue. +Capture V3 sans données personnelles : +`docs/assets/screenshots/quickshell-lock-style-v3-synthetic.png`. diff --git a/quickshell/.config/quickshell/labfy-lock/preview-cover.png b/quickshell/.config/quickshell/labfy-lock/preview-cover.png new file mode 100644 index 0000000000000000000000000000000000000000..a1135e6a2e34d933f886720db568a83b7b0ea206 GIT binary patch literal 2167 zcmaKuc{J1w7stmm!x)(ek*Kl8pvV$>Cu1ANGJX&}dSQ=BP zM5DrxvXhaKHI+46vd!C&r&sU$$Mc@^E`Qv6&pr3td(OS*e9z}514Ij9q%;x&fd~^U z%}(varr(Bu?)ZX#KMysW5M!sX_(FOD9V6%@td=c9i%o?YMVJG?bZ$^Kp z$LDp0R12Y*ML07!Tppt$Vs8v4A(3XM0dz45Dk>qK#Gw7F6#az9CfgZB4jMyvTThIL z(TRd)r%x*Zv{s9WxJ_O1PWB%?|7YBPD-Epx4I$_1e^o84d7LwvJ4Mz_Q)8d==`@OV zE=*IRoXYJCIZ3r4i!ch0Pxr{aR_ui?tOg}yvvpN5Y1!+^}i8Ju|4XxCe`b7su z+XMVK3{zf0rLW$v^$mM?=MadTUtgowzjVBt7dT~%mI-&N{pO}H3JrRwJ$OIG1Z}`> z|6ZBm7|HT*HyOVB%yIN{`R<@94Er9oWML>>o4lyub!{NIf{VvzY5NG))Ck<@!Bo4x ze5vwGk^KlOHXm>+oZO9=dS4J5NZ!4UOB+a5uaj5P5RHJ!zAi{=Q6cU73UFcF2Jvca zz9qq-FvJ;JO5Bi`W=%B>>erT6#1~}F?0PB}uOhM}m>r5BqDStMmPPL2YWxLXm^wor zunQ2Y0K`2Rug@IEZbhtWhcsdj0R&KTIp*`(BD`q8#Dt?l&xcuZ$NKjCvARCK36Xf5 z!{YFQEzg=iJAQ5_Q{A1K{YnjooxH`vUEjS=xD<@wQv%%RL)Fh;67A!gbih}wc7|S(AqRz- z{^4~!vQ`2?6XsE5LF%w}RYnCQ%RtA17HzSciUqY$J;)-SAZIQQ`nCX9<}E^On-Str z{)jRi7E4kxaBEjaa30>&)6o*P&L6irK|nWs@K5ea97)EBI!8i3Pv^f503#|q@aZ#7 ze!cE3XO$?)lcQ#LHnJrVyzYxacK*IIaQQ3yF~Ays`)koLUrA-AF>!A@ds9O5%=djn zn0&RSI;Dkc&|;w$4OnQO%b8zH~O3#KkRC0TNwQ@o0YYvYyeChP^!4NYkg%? z@45emNA72u@G}fxge+bUZ8sGEiaIP&WV-*KdTM0YNG1Zvf79Fg?auOqdPcU%Z}D9v z-<@mz(Luu*z|8}C_m)Fj&gfOLv>iLTy2v?dT`_?s>d~8BUA>9dCy#rLb2N{D?+2e6 zH~4b|<|t`VllcQmDY2f;w8uegh>Q--+Z9cU6$85yC@)F1Qafv&9J%^KAa}930_8fU zD3X(K15{_eKRGfRdjmXWqwibX9)TC)OokQrO}=m(7CV5xd=CeWiHgg*&QF9{rJo-n zSP5z8=34KZeU6@8q#EcvKek~#rU*cIDc$`!XF8)m!;d-SFouH`VvlaOD8pk2s%Prs zTIljC@XJrw1r*iv$bngEAT<0YOZ`f~E&TAc`=izC%h%@e0?*e>KFfRF0Eg`>CL8 znR2^-43p~gK&JXqPwJtZBLF@0VB|&U7(~ zjwLBjQi2khnwMMlv>gOrp{RT>#(SYRRGz{*(=?{Q1N@5<5>0z8zp~w%mPB!2JZgIn z3rkX^u;moEhzguFYMMQKB65p2!(FrOcO}NEu~)NCB$;-7tVk(jyt5>S!ZS<_OKbf% zo@~eNvqa4RYLRABUkW8PFoweH%&!{F=<~-7-wys7i)0%18{5&6CRI+2Xlq5krQjww za{b&S4bbTdE?A0#TJ8*AtGM>Y5h`3Q=;$Oenqb9xI_Rurzm zGlIyTx+9Nz9%H>P3tGFlzf&~m6y5in#P-iCHyu{^07qU5RIH9HVa_88uoI@VCPJz& zl`yTlzNi)-%S}2ALgA@X-#E<7hxC;TUioT<1$%~_aHQe# zn7JO@ub02k8w=sV>DeMGyNl1aU&|>i>$nL-g+uva$gyZvU%I>D^B}(qk!xeOu%x4a zA$3#5s)HWi6}t;!e;Qe)8sca`l?wy%MG;Ta(~oONUV6jJejjo;gxhf zH*Qi9>=v~|1tMXYX`(=l;OD}SoB%}!+edp$O6Fk|NO1;7Wy_{faI`KW*XgjQ+s;af zoxF>yo~iWyu+O${<8XI2ajwVc8{AT2Qzi~<0CgZS<7P5QaY9*8y-nH!!iqZ3DV2kg zx9zltS-B9`Zdl0zSxOe-0fLVD2YhOeA*BCe?+z@hCe;_;lof8zqw!ulT>cI3zt!=3 iODX?vV*iEMp8`i#e@O=~v>WU^6cB item !== surface); + if (activeSurface === surface) { + activeSurface = null; + if (surfaces.length) chooseSurface(surfaces[0]); + } + } + function chooseSurface(surface) { + if (activeSurface && activeSurface.field) activeSurface.field.text = ""; + activeSurface = surface; + if (surface && surface.field) Qt.callLater(() => surface.field.forceActiveFocus()); + } + function cancel() { + if (activeSurface && activeSurface.field) activeSurface.field.text = ""; + if (attempt && pam.active) { + aborting = true; + pam.abort(); + } + feedback = "Saisie effacée."; + feedbackIsError = false; + } + function startAuthentication() { + if (!lock.secure || attempt || authorized || aborting) return; + attempt = true; + feedback = ""; + feedbackIsError = false; + if (!pam.start()) { + attempt = false; + feedback = "Authentification indisponible."; + feedbackIsError = true; + } + } + function submit(value) { + if (!lock.secure || authorized || aborting || responding) return; + if (!attempt) startAuthentication(); + // INVARIANT: an early Enter cannot discard a secret before PAM asks for it. + if (!attempt || !pam.responseRequired) return; + // CONTRACT: an empty submission leaves the current PAM prompt pending. + if (value.length === 0) return; + responding = true; + pam.respond(value); + if (activeSurface && activeSurface.field) activeSurface.field.text = ""; + responding = false; + } + + // INVARIANT: no IPC method mutates the lock. A caller only observes this + // exact process generation and compositor confirmation. + IpcHandler { + target: "lock" + function state(): string { + return root.generation + ":" + (lock.secure && lock.locked ? "secure" : "pending"); + } + } + Process { + // WHY: Sway exposes the active layout, but not a reliable initial + // CapsLock state in get_inputs. We report the latter only after a key event. + command: ["swaymsg", "-t", "get_inputs", "-r"] + running: true + stdout: StdioCollector { id: inputData; waitForEnd: true } + onExited: (code, status) => { + if (code !== 0 || inputData.text.length > 65536) return; + try { + const inputs = JSON.parse(inputData.text); + const keyboard = inputs.find(item => item.type === "keyboard" + && item.identifier && item.identifier.includes("AT_Translated")) + || inputs.find(item => item.type === "keyboard" && item.xkb_active_layout_name); + if (keyboard && typeof keyboard.xkb_active_layout_name === "string") + root.keyboardLayout = keyboard.xkb_active_layout_name.slice(0, 48); + } catch (_) {} + } + } + + PamContext { + id: pam + // CONTRACT: la politique auth dédiée reprend « auth include login » ; + // le fichier appartient à root sous /etc/pam.d/labfy-lock. + config: "labfy-lock" + user: root.loginUser + onPamMessage: { + // CONTRACT: a PAM response prompt is already shown above the field; + // only informational messages and actual errors use the lower line. + root.feedback = pam.responseRequired && !pam.messageIsError ? "" : pam.message; + root.feedbackIsError = pam.messageIsError; + root.responding = false; + if (root.activeSurface && root.activeSurface.field) + Qt.callLater(() => root.activeSurface.field.forceActiveFocus()); + } + onError: error => { + root.feedback = "Erreur d’authentification."; + root.feedbackIsError = true; + } + onCompleted: result => { + const valid = AuthGate.mayUnlock(root.attempt, root.aborting, + lock.secure, result, PamResult.Success); + root.attempt = false; + root.responding = false; + root.aborting = false; + if (root.activeSurface && root.activeSurface.field) root.activeSurface.field.text = ""; + if (valid) { + root.authorized = true; + // CONTRACT: QuickShell releases this Wayland session lock by + // changing its locked property after PAM succeeds. + lock.locked = false; + } else { + root.feedback = result === PamResult.MaxTries + ? "Nombre maximal de tentatives atteint." + : "Authentification refusée. Appuyez sur Entrée pour réessayer."; + root.feedbackIsError = true; + } + } + } + + // WHY: the surface component is instantiated by QuickShell for every + // compositor output, including outputs plugged in after acquisition. + WlSessionLock { + id: lock + locked: true + onLockStateChanged: { + // CONTRACT: an acquisition refusal cannot leave an inert second + // locker occupying the wrapper's singleton record. + if (!locked && !root.authorized) Qt.quit(); + } + onSecureStateChanged: { + if (!secure && root.authorized) { + // CONTRACT: exit only after the authenticated unlock has + // removed compositor protection, so the next request is fresh. + root.captureGuard.setText("released\n"); + Qt.quit(); + return; + } + if (secure) { + // WHY: PAM is ready before the user types the first password. + root.startAuthentication(); + if (root.activeSurface && root.activeSurface.field) + Qt.callLater(() => root.activeSurface.field.forceActiveFocus()); + } + } + LockSurface { controller: root; secure: lock.secure } + } +} diff --git a/quickshell/.config/quickshell/labfy-sway/Bar.qml b/quickshell/.config/quickshell/labfy-sway/Bar.qml index b8f543dcd..cc2dc750d 100644 --- a/quickshell/.config/quickshell/labfy-sway/Bar.qml +++ b/quickshell/.config/quickshell/labfy-sway/Bar.qml @@ -30,6 +30,19 @@ PanelWindow { required property var drawerService required property var osdService screen: modelData + FileView { + id: lockCaptureGuardFile + path: (Quickshell.env("XDG_RUNTIME_DIR") || "") + "/labfy-lock.capture-guard" + printErrors: false + } + readonly property bool lockCaptureGuard: lockCaptureGuardFile.text().trim() === "active" + onLockCaptureGuardChanged: { + if (lockCaptureGuard) { + overviewCapture.running = false; + overviewCaptureRefresh.stop(); + closeOverview(); + } + } function closeDrawer() { drawerIndicator.close(false) } function toggleDrawer() { drawerIndicator.toggle() } @@ -215,7 +228,7 @@ PanelWindow { return true; } function openOverview() { - if (authenticationActive) return false; + if (authenticationActive || lockCaptureGuard) return false; // INVARIANT: deux ouvertures ne créent ni une seconde capture ni une fermeture. if (overviewRequested || overviewLoader.active) return true; closeApplications(); @@ -230,7 +243,7 @@ PanelWindow { return true; } function toggleOverview() { - if (authenticationActive) return false; + if (authenticationActive || lockCaptureGuard) return false; if (overviewRequested || overviewLoader.active) return closeOverview(); return openOverview(); } @@ -265,7 +278,7 @@ PanelWindow { id: overviewCapture command: ["python3", "-B", bar.overviewBackend, "capture", "--output", bar.screen.name] onExited: (code, status) => { - if (bar.overviewRequested && !bar.authenticationActive) overviewLoader.active = true; + if (bar.overviewRequested && !bar.authenticationActive && !bar.lockCaptureGuard) overviewLoader.active = true; else if (code !== 0) console.warn("Capture du workspace impossible"); } } @@ -281,7 +294,7 @@ PanelWindow { id: overviewCaptureRefresh interval: 350; repeat: false onTriggered: { - if (!bar.authenticationActive && !overviewCapture.running && !overviewLoader.active && !clipboardLoader.active + if (!bar.authenticationActive && !bar.lockCaptureGuard && !overviewCapture.running && !overviewLoader.active && !clipboardLoader.active && !applicationsLoader.active && !controlCenter.visible && !dateCenter.visible) overviewCapture.running = true; @@ -292,7 +305,7 @@ PanelWindow { Timer { interval: 15000; repeat: true; running: true; triggeredOnStart: true onTriggered: { - if (!bar.authenticationActive && !overviewCapture.running && !overviewLoader.active && !clipboardLoader.active + if (!bar.authenticationActive && !bar.lockCaptureGuard && !overviewCapture.running && !overviewLoader.active && !clipboardLoader.active && !applicationsLoader.active && !controlCenter.visible && !dateCenter.visible) overviewCapture.running = true; diff --git a/quickshell/.config/quickshell/labfy-sway/README.md b/quickshell/.config/quickshell/labfy-sway/README.md index 2e8b573fc..15d402f69 100644 --- a/quickshell/.config/quickshell/labfy-sway/README.md +++ b/quickshell/.config/quickshell/labfy-sway/README.md @@ -93,7 +93,8 @@ est 60 % ; le helper root est documenté dans `bin/root/README.md`. Dépendances essentielles : `quickshell`, SwayFX 0.6, `scenefx`, une Nerd Font, NetworkManager, BlueZ, PipeWire, TLP/`tlpctl`, `checkupdates`, `yay`, `df`, -`python3`, `swaylock` et le helper batterie pour le réglage du seuil. Le +`python3`, la politique `/etc/pam.d/labfy-lock` et le helper batterie pour le +réglage du seuil. Le backlight utilise le noeud machine `amdgpu_bl1` dans `BrightnessSlider.qml`. Les scans Wi-Fi et Bluetooth ne sont possédés que par les pages ouvertes. diff --git a/quickshell/.config/quickshell/labfy-sway/controlcenter/ControlCenter.qml b/quickshell/.config/quickshell/labfy-sway/controlcenter/ControlCenter.qml index 3fe33624a..1b10bb3c4 100644 --- a/quickshell/.config/quickshell/labfy-sway/controlcenter/ControlCenter.qml +++ b/quickshell/.config/quickshell/labfy-sway/controlcenter/ControlCenter.qml @@ -43,7 +43,7 @@ PopupWindow { + "/quickshell/labfy-sway/session-v2.py" // Une seule table associe les libellés, confirmations et commandes de session. readonly property var sessionActions: [ - { id: "lock", label: "Verrouiller", icon: "", accent: Theme.accent, command: ["swaylock"] }, + { id: "lock", label: "Verrouiller", icon: "", accent: Theme.accent, command: ["/home/fy59/.local/bin/labfy-lock"] }, { id: "suspend", label: "Veille", icon: "", accent: Theme.accent, confirmTitle: "Mettre l'ordinateur en veille ?", description: "La session sera verrouillée avant la veille.", diff --git a/quickshell/.config/quickshell/labfy-sway/notifications/LockProjection.js b/quickshell/.config/quickshell/labfy-sway/notifications/LockProjection.js new file mode 100644 index 000000000..cc938dfff --- /dev/null +++ b/quickshell/.config/quickshell/labfy-sway/notifications/LockProjection.js @@ -0,0 +1,19 @@ +// CONTRACT: only public application identity and counts cross into the lock +// process. The private record never enters the serialized result. +function project(records) { + const groups = {}; + let total = 0; + for (const record of records.slice(0, 200)) { + if (!record || record.read !== false) continue; + total++; + const name = typeof record.appName === "string" + && /^[^\r\n\t]{1,48}$/.test(record.appName) ? record.appName : "Application"; + const icon = typeof record.appIcon === "string" + && /^[A-Za-z0-9_.-]{1,64}$/.test(record.appIcon) ? record.appIcon : ""; + const key = name + "\n" + icon; + if (!groups[key]) groups[key] = { name: name, icon: icon, count: 0 }; + groups[key].count++; + } + return { version: 1, generatedAt: Date.now(), total: total, + apps: Object.values(groups).slice(0, 4) }; +} diff --git a/quickshell/.config/quickshell/labfy-sway/notifications/NotificationStore.qml b/quickshell/.config/quickshell/labfy-sway/notifications/NotificationStore.qml index 3c83843b9..8512052d1 100644 --- a/quickshell/.config/quickshell/labfy-sway/notifications/NotificationStore.qml +++ b/quickshell/.config/quickshell/labfy-sway/notifications/NotificationStore.qml @@ -2,6 +2,7 @@ import QtQuick import Quickshell import Quickshell.Io import Quickshell.Services.Notifications +import "LockProjection.js" as LockProjection QtObject { id: store @@ -24,6 +25,17 @@ QtObject { onLoadFailed: store.load() onSaveFailed: error => console.warn("Historique notifications : écriture échouée", error) } + // CONTRACT: the lock process receives only a bounded public projection. + // Summary, body, image, sender, actions and record identifiers never cross. + property FileView lockFile: FileView { + path: (Quickshell.env("XDG_RUNTIME_DIR") || "") + "/labfy-lock-notifications.json" + blockWrites: true + atomicWrites: true + printErrors: false + } + function publishLockProjection() { + lockFile.setText(JSON.stringify(LockProjection.project(records))); + } function load() { if (ready) return; @@ -50,6 +62,7 @@ QtObject { return; } ready = true; + publishLockProjection(); // INVARIANT: la migration conserve chaque champ utilisateur et persiste le schéma v2. if (migrationNeeded) save(); } @@ -58,6 +71,7 @@ QtObject { function save() { if (!ready) return; file.setText(JSON.stringify({ version: 2, nextId: nextId, records: records })); + publishLockProjection(); } function upsert(record) { diff --git a/quickshell/.config/quickshell/labfy-sway/overview/backend.py b/quickshell/.config/quickshell/labfy-sway/overview/backend.py index 5b2fcdd80..66c292699 100644 --- a/quickshell/.config/quickshell/labfy-sway/overview/backend.py +++ b/quickshell/.config/quickshell/labfy-sway/overview/backend.py @@ -13,6 +13,18 @@ import tempfile import time +def lock_capture_guard_active(): + # CONTRACT: capture is denied while the dedicated lock may render an auth + # surface. A crash leaves the guard active until authenticated recovery. + runtime = os.environ.get("XDG_RUNTIME_DIR") + if not runtime: + return False + try: + return (Path(runtime) / "labfy-lock.capture-guard").read_text() == "active\n" + except OSError: + return False + + def sway(kind): result = subprocess.run(["swaymsg", "-t", kind, "-r"], capture_output=True, text=True, timeout=5, check=True) @@ -224,6 +236,8 @@ def state(): def capture(output, expected_number=None, expected_workspace_id=None, expected_revision=None, settle_ms=0): + if lock_capture_guard_active(): + return {"captured": False} if settle_ms < 0 or settle_ms > 1000: raise ValueError("Délai de capture invalide") if settle_ms: @@ -242,6 +256,8 @@ def capture(output, expected_number=None, expected_workspace_id=None, with tempfile.NamedTemporaryFile(dir=path.parent, suffix=".jpg", delete=False) as temp: temporary = Path(temp.name) try: + if lock_capture_guard_active(): + return {"captured": False} subprocess.run(["grim", "-o", output, "-s", "0.25", "-t", "jpeg", "-q", "72", str(temporary)], check=True, timeout=8, capture_output=True) if not temporary.is_file() or temporary.stat().st_size == 0: @@ -250,7 +266,7 @@ def capture(output, expected_number=None, expected_workspace_id=None, updated = next((card for card in after["cards"] if card["number"] == current["number"]), None) # INVARIANT: un changement pendant grim ou la présence de l'Overview # interdit de publier une image associée au mauvais arbre. - if (output in after["overlayOutputs"] or not updated or not updated["visible"] + if (lock_capture_guard_active() or output in after["overlayOutputs"] or not updated or not updated["visible"] or updated["output"] != output or updated["workspaceId"] != current["workspaceId"] or updated["revision"] != current["revision"]): return {"captured": False} @@ -275,6 +291,8 @@ def capture(output, expected_number=None, expected_workspace_id=None, def capture_windows(numbers, cursor=0): + if lock_capture_guard_active(): + return {"captured": [], "nextCursor": 0} if cursor < 0: raise ValueError("Curseur de capture invalide") before = state() @@ -287,6 +305,8 @@ def capture_windows(numbers, cursor=0): captures = [] try: for window in batch: + if lock_capture_guard_active(): + break with tempfile.NamedTemporaryFile(dir=cache_dir(), suffix=".jpg", delete=False) as temp: temporary = Path(temp.name) captures.append((window, temporary)) @@ -302,6 +322,8 @@ def capture_windows(numbers, cursor=0): current = {window["id"]: window for card in after["cards"] for window in card["windows"]} published = [] for window, temporary in captures: + if lock_capture_guard_active(): + break if not temporary.is_file() or current.get(window["id"], {}).get("foreignId") != window["foreignId"]: continue path = window_preview_path(window["id"]) diff --git a/sway/.config/sway/bind b/sway/.config/sway/bind index eb3bf5fe4..ab6a6003a 100644 --- a/sway/.config/sway/bind +++ b/sway/.config/sway/bind @@ -215,7 +215,7 @@ bindsym $mod+Alt+a exec AppFlowy bindsym $mod+Alt+g exec gimp bindsym $mod+Alt+m exec limusic-app bindsym $mod+Alt+b exec blueman-manager -bindsym $mod+Alt+o exec swaylock +bindsym $mod+Alt+o exec /home/fy59/.local/bin/labfy-lock bindsym $mod+Alt+l exec ~/.config/sway/scripts/applications-quickshell bindsym $mod+Alt+p exec proton-pass bindsym $mod+Alt+t exec ~/.local/bin/thunderbird-proton diff --git a/system/pam.d/README.md b/system/pam.d/README.md new file mode 100644 index 000000000..84899d4e7 --- /dev/null +++ b/system/pam.d/README.md @@ -0,0 +1,41 @@ +# Politique PAM de labfy-lock + +`labfy-lock` emploie la phase `auth` de PAM. Le fichier source +[`labfy-lock`](labfy-lock) reprend exactement la règle active du paquet +`swaylock` : `auth include login`. La chaîne d'authentification sur cette +installation est `login` → `system-local-login` → `system-login` → +`system-auth`. Elle conserve notamment `pam_nologin`, `pam_shells`, +`pam_faillock`, `pam_unix` et les modules standard de cette pile. Les phases +`account`, `password` et `session` ne sont pas ajoutées : `PamContext` de +QuickShell 0.3.1 utilise seulement `auth`. + +Après une reconstruction du système, installer depuis la racine du dépôt : + +```sh +sudo install -o root -g root -m 0644 system/pam.d/labfy-lock /etc/pam.d/labfy-lock +``` + +Le mot de passe d'administration se saisit uniquement dans le dialogue +normal de `sudo`. Ne pas employer Stow pour `/etc/pam.d`. + +Vérifier le fichier installé avant de changer `PamContext` : + +```sh +stat -c '%U:%G %a %n' /etc/pam.d/labfy-lock +cmp system/pam.d/labfy-lock /etc/pam.d/labfy-lock +test -f /etc/pam.d/login +test -f /etc/pam.d/system-local-login +test -f /etc/pam.d/system-login +test -f /etc/pam.d/system-auth +``` + +Après installation, régler `config: "labfy-lock"` dans le seul `PamContext` +du lockscreen, vérifier le QML et les tests, puis faire un essai réel avec +saisie du secret par l'utilisateur. Conserver le paquet `swaylock` jusqu'à +confirmation du retour au bureau. Un test synthétique ne valide pas PAM. + +En cas d'échec après désinstallation du paquet, exécuter d'abord +`sudo pacman -S swaylock`, puis confirmer que `/etc/pam.d/swaylock` existe. +Remettre `config: "swaylock"`, recharger uniquement le lockscreen si +nécessaire et refaire un essai manuel. Ne pas lancer `swaylock` +par-dessus une session `WlSessionLock` déjà verrouillée. diff --git a/system/pam.d/labfy-lock b/system/pam.d/labfy-lock new file mode 100644 index 000000000..8b77ee7af --- /dev/null +++ b/system/pam.d/labfy-lock @@ -0,0 +1,6 @@ +#%PAM-1.0 +# WHY: conserver la même politique auth que le verrou validé sans dépendre +# du fichier PAM fourni par le paquet swaylock. +# CONTRACT: PamContext utilise uniquement la phase auth ; login inclut +# system-local-login, system-login et system-auth pour cette phase. +auth include login diff --git a/systemd/.config/systemd/user/swayidle.service b/systemd/.config/systemd/user/swayidle.service index 582038980..a11605273 100644 --- a/systemd/.config/systemd/user/swayidle.service +++ b/systemd/.config/systemd/user/swayidle.service @@ -4,7 +4,8 @@ PartOf=graphical-session.target After=graphical-session.target [Service] -ExecStart=/usr/bin/swayidle -w timeout 300 'swaylock -f -c 000000' timeout 360 'swaymsg "output * power off"' resume 'swaymsg "output * power on"' timeout 420 'playerctl -a pause 2>/dev/null || true' idlehint 420 before-sleep 'playerctl -a pause 2>/dev/null || true; swaylock -f -c 000000' +# CONTRACT: -w attend la fin de la commande before-sleep ; labfy-lock rend la main uniquement après confirmation secure. +ExecStart=/usr/bin/swayidle -w timeout 300 '/home/fy59/.local/bin/labfy-lock' timeout 360 'swaymsg "output * power off"' resume 'swaymsg "output * power on"' timeout 420 'playerctl -a pause 2>/dev/null || true' idlehint 420 before-sleep 'playerctl -a pause 2>/dev/null || true; /home/fy59/.local/bin/labfy-lock' Restart=on-failure [Install] diff --git a/tests/overview/test_backend.py b/tests/overview/test_backend.py index af97fecf2..ee0dac741 100644 --- a/tests/overview/test_backend.py +++ b/tests/overview/test_backend.py @@ -14,6 +14,15 @@ spec.loader.exec_module(backend) class OverviewBackendTests(unittest.TestCase): + def test_lock_guard_blocks_all_capture_entrypoints(self): + # CONTRACT: no compositor read or grim call follows an active lock + # guard, including the window-preview path. + with tempfile.TemporaryDirectory() as directory, patch.dict("os.environ", {"XDG_RUNTIME_DIR": directory}): + (Path(directory) / "labfy-lock.capture-guard").write_text("active\n") + with patch.object(backend, "state", side_effect=AssertionError("capture reached state")): + self.assertEqual(backend.capture("HEADLESS-1"), {"captured": False}) + self.assertEqual(backend.capture_windows({1}), {"captured": [], "nextCursor": 0}) + def test_state_keeps_floating_and_excludes_layer_shell(self): rect = {"x": 0, "y": 0, "width": 1000, "height": 700} tiled = {"type": "con", "id": 10, "app_id": "native-app", "name": "Native", diff --git a/tests/qml/tst_session_exit_gate.qml b/tests/qml/tst_session_exit_gate.qml index 913785573..5e68f798c 100644 --- a/tests/qml/tst_session_exit_gate.qml +++ b/tests/qml/tst_session_exit_gate.qml @@ -9,7 +9,7 @@ TestCase { id: gateComponent SessionExitGate { actions: [ - { id: "lock", label: "Verrouiller", command: ["swaylock"] }, + { id: "lock", label: "Verrouiller", command: ["/home/fy59/.local/bin/labfy-lock"] }, { id: "suspend", label: "Veille", command: ["systemctl", "suspend"] }, { id: "logout", label: "Déconnexion", confirmTitle: "Se déconnecter ?", description: "La session SwayFX en cours sera fermée.", checkpointReason: "logout", @@ -128,7 +128,7 @@ TestCase { const commandSpy = createSpy(gate, "commandRequested"); gate.requestAction("lock"); compare(commandSpy.count, 1); - compare(commandSpy.signalArguments[0][0], ["swaylock"]); + compare(commandSpy.signalArguments[0][0], ["/home/fy59/.local/bin/labfy-lock"]); compare(checkpointSpy.count, 0); gate.requestAction("suspend"); compare(gate.pendingAction.id, "suspend"); diff --git a/tests/quickshell/test_labfy_lock.py b/tests/quickshell/test_labfy_lock.py new file mode 100644 index 000000000..890d34ec2 --- /dev/null +++ b/tests/quickshell/test_labfy_lock.py @@ -0,0 +1,101 @@ +"""Contrôles TEST_ONLY sur un compositeur Sway headless enfant.""" + +import json +import os +from pathlib import Path +import signal +import subprocess +import sys +import time +import unittest + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "session_v2/fixtures")) +from headless import Lab + +ROOT = Path(__file__).resolve().parents[2] +WRAPPER = ROOT / "bin/.local/bin/labfy-lock" + + +class LockLifecycleTests(unittest.TestCase): + def test_refuses_second_lock(self): + with Lab() as lab: + # CONTRACT: un autre processus détient déjà WlSessionLock ; le + # wrapper ne doit pas déclarer sa propre acquisition secure. + lab.env["LABFY_LOCK_GENERATION"] = "foreign" + holder = lab.start(["quickshell", "--path", str(ROOT / "quickshell/.config/quickshell/labfy-lock")]) + deadline = time.monotonic() + 8 + while time.monotonic() < deadline: + observed = subprocess.run( + ["quickshell", "ipc", "--pid", str(holder.pid), "call", "lock", "state"], + env=lab.env, capture_output=True, text=True, timeout=2) + if observed.stdout.strip() == "foreign:secure": + break + time.sleep(.08) + self.assertEqual(observed.stdout.strip(), "foreign:secure") + attempt = subprocess.run([str(WRAPPER)], env=lab.env, + capture_output=True, text=True, timeout=12) + self.assertNotEqual(attempt.returncode, 0) + self.assertNotIn("secure", attempt.stdout) + + def test_concurrent_secure_and_crash_reacquisition(self): + with Lab() as lab: + first = subprocess.Popen([str(WRAPPER)], env=lab.env, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + second = subprocess.Popen([str(WRAPPER)], env=lab.env, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + self.assertEqual(first.communicate(timeout=12)[1], "") + self.assertEqual(second.communicate(timeout=12)[1], "") + self.assertEqual((first.returncode, second.returncode), (0, 0)) + record = json.loads((lab.directory / "labfy-lock.launch").read_text()) + pid = record["pid"] + try: + observed = subprocess.run( + ["quickshell", "ipc", "--pid", str(pid), "call", "lock", "state"], + env=lab.env, capture_output=True, text=True, timeout=2) + self.assertEqual(observed.stdout.strip(), record["generation"] + ":secure") + # No callable IPC path may authorize unlock. + forbidden = subprocess.run( + ["quickshell", "ipc", "--pid", str(pid), "call", "lock", "unlock"], + env=lab.env, capture_output=True, text=True, timeout=2) + # Some QuickShell versions return 0 even for an unknown method; + # the compositor state is the authoritative observation. + self.assertNotIn(record["generation"] + ":secure", forbidden.stdout) + still_locked = subprocess.run( + ["quickshell", "ipc", "--pid", str(pid), "call", "lock", "state"], + env=lab.env, capture_output=True, text=True, timeout=2) + self.assertEqual(still_locked.stdout.strip(), record["generation"] + ":secure") + # Dynamic outputs remain owned by the same lock conversation. + for command in ("create_output", "output HEADLESS-2 mode 800x600", + "output HEADLESS-2 scale 1.5", + "output HEADLESS-2 mode 1024x768", + "output HEADLESS-2 disable"): + lab.command(command) + time.sleep(.12) + deadline = time.monotonic() + 2 + while time.monotonic() < deadline: + state = subprocess.run( + ["quickshell", "ipc", "--pid", str(pid), "call", "lock", "state"], + env=lab.env, capture_output=True, text=True, timeout=2) + if state.stdout.strip() == record["generation"] + ":secure": + break + time.sleep(.05) + self.assertEqual(state.stdout.strip(), record["generation"] + ":secure", command) + self.assertTrue(Path(f"/proc/{pid}").exists()) + finally: + try: + os.kill(pid, signal.SIGKILL) + except ProcessLookupError: + pass + deadline = time.monotonic() + 2 + while time.monotonic() < deadline and Path(f"/proc/{pid}").exists(): + time.sleep(.05) + retry = subprocess.run([str(WRAPPER)], env=lab.env, + capture_output=True, text=True, timeout=12) + self.assertEqual(retry.returncode, 0, retry.stderr) + replacement = json.loads((lab.directory / "labfy-lock.launch").read_text()) + self.assertNotEqual(replacement["generation"], record["generation"]) + os.kill(replacement["pid"], signal.SIGKILL) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/quickshell/test_lock_auth_gate.js b/tests/quickshell/test_lock_auth_gate.js new file mode 100644 index 000000000..37f1ac82b --- /dev/null +++ b/tests/quickshell/test_lock_auth_gate.js @@ -0,0 +1,14 @@ +const fs = require('fs'); +const vm = require('vm'); +const assert = require('assert'); +const source = fs.readFileSync('quickshell/.config/quickshell/labfy-lock/AuthGate.js', 'utf8'); +const context = vm.createContext({}); +vm.runInContext(source, context); +const allowed = context.mayUnlock; +const Success = 0, Failed = 1, Error = 2, MaxTries = 3; +assert.equal(allowed(true, false, true, Success, Success), true); +for (const result of [Failed, Error, MaxTries]) + assert.equal(allowed(true, false, true, result, Success), false); +assert.equal(allowed(true, true, true, Success, Success), false); // late result after abort +assert.equal(allowed(false, false, true, Success, Success), false); // inactive generation +assert.equal(allowed(true, false, false, Success, Success), false); // not secure diff --git a/tests/quickshell/test_lock_projection.js b/tests/quickshell/test_lock_projection.js new file mode 100644 index 000000000..fca71a088 --- /dev/null +++ b/tests/quickshell/test_lock_projection.js @@ -0,0 +1,21 @@ +const fs = require('fs'); +const vm = require('vm'); +const assert = require('assert'); +const source = fs.readFileSync('quickshell/.config/quickshell/labfy-sway/notifications/LockProjection.js', 'utf8'); +const context = vm.createContext({}); +vm.runInContext(source, context); +const result = JSON.parse(JSON.stringify(context.project([ + { read: false, appName: 'Messagerie', appIcon: 'mail-client', + summary: 'Code 123456', body: 'Contact privé', image: '/private.png' }, + { read: false, appName: 'Messagerie', appIcon: 'mail-client', summary: 'Autre secret' }, + { read: true, appName: 'Navigateur', summary: 'Ne pas montrer' }, + { read: false, appName: 'Nom\nSecret', appIcon: 'file:///private' } +]))) +assert.equal(result.total, 3); +assert.deepEqual(result.apps, [ + { name: 'Messagerie', icon: 'mail-client', count: 2 }, + { name: 'Application', icon: '', count: 1 } +]); +const serialized = JSON.stringify(result); +for (const forbidden of ['123456', 'Contact privé', '/private.png', 'Autre secret', 'Nom\nSecret']) + assert(!serialized.includes(forbidden), forbidden);