lockscreen: replace hyprlock with quickshell WlSessionLock

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-07-08 11:04:27 +01:00
parent 24123270b2
commit bedb11930f
2 changed files with 162 additions and 54 deletions

View file

@ -137,6 +137,7 @@ in
singleton Theme 1.0 Theme.qml
singleton Commands 1.0 Commands.qml
Bar 1.0 Bar.qml
Lock 1.0 Lock.qml
'';
};
@ -191,6 +192,150 @@ in
'';
};
"quickshell/Lock.qml" = {
onChange = qsRestart;
text = ''
import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Pam
import QtQuick
import Qt5Compat.GraphicalEffects
// ext-session-lock screen, replacing hyprlock. Lives in the shell
// process: set `locked: true` and the compositor hands every
// screen to a LockSurface below until PAM says otherwise.
WlSessionLock {
id: lock
property string password: ""
property bool failed: false
function submit(pw) {
if (pam.active || pw === "") return;
failed = false;
password = pw;
pam.start();
}
PamContext {
id: pam
// NixOS ships a "login" pam service; pam_unix verifies the
// user's own password via the unix_chkpwd setuid wrapper.
config: "login"
onPamMessage: {
if (this.responseRequired) this.respond(lock.password);
}
onCompleted: result => {
lock.password = "";
if (result === PamResult.Success) lock.locked = false;
else lock.failed = true;
}
}
WlSessionLockSurface {
id: surface
color: "black"
Image {
id: wall
anchors.fill: parent
source: "file://" + Quickshell.env("HOME") + "/.local/share/backgrounds/wallpaper.png"
fillMode: Image.PreserveAspectCrop
visible: false
}
FastBlur {
anchors.fill: wall
source: wall
radius: 48
}
Rectangle { anchors.fill: parent; color: "black"; opacity: 0.4 }
SystemClock {
id: lockClock
precision: SystemClock.Minutes
}
Column {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: -60
spacing: 8
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: lockClock.date.toLocaleTimeString(Qt.locale(), "HH:mm")
color: Theme.base05
font.family: Theme.fontFamily
font.pixelSize: 72
font.weight: Font.Medium
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: lockClock.date.toLocaleDateString(Qt.locale(), "dddd, d MMMM")
color: Theme.base04
font.family: Theme.fontFamily
font.pixelSize: 16
}
Item { width: 1; height: 32 }
// Password pill: dots for typed chars; border tracks
// state (accent while checking, red on failure).
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
width: 280
height: 44
radius: Theme.radius
color: Theme.cardBg
border.width: Theme.borderWidth
border.color: pam.active ? Theme.base0D
: lock.failed ? Theme.base08
: Theme.base03
Behavior on border.color { ColorAnimation { duration: Theme.animFade } }
Row {
anchors.centerIn: parent
spacing: 8
Repeater {
model: Math.min(pwInput.text.length, 24)
Rectangle {
width: 8; height: 8; radius: 4
color: Theme.base05
}
}
}
Text {
anchors.centerIn: parent
visible: pwInput.text === "" && lock.failed
text: "wrong password"
color: Theme.base08
font.family: Theme.fontFamily
font.pixelSize: 13
}
TextInput {
id: pwInput
anchors.fill: parent
opacity: 0
enabled: !pam.active
echoMode: TextInput.Password
focus: true
onAccepted: {
lock.submit(text);
text = "";
}
Keys.onEscapePressed: text = ""
}
}
}
Component.onCompleted: pwInput.forceActiveFocus()
}
}
'';
};
"quickshell/Commands.qml" = {
onChange = qsRestart;
text = ''
@ -201,7 +346,6 @@ in
readonly property string nmcli: "${nmcli}"
readonly property string wifiConnect: "${wifiConnectScript}"
readonly property string notifSound: "${pkgs.libcanberra-gtk3}/bin/canberra-gtk-play"
readonly property string hyprlock: "${pkgs.hyprlock}/bin/hyprlock"
readonly property string systemctl: "${pkgs.systemd}/bin/systemctl"
readonly property string weatherFetch: "${weatherFetchScript}"
}
@ -239,6 +383,16 @@ in
function reload(): void { Quickshell.reload(false); }
}
// Session lock (replaces hyprlock). `qs ipc call lock lock`
// from hypridle / loginctl lock-session; the session menu
// calls root.lock() directly. Idempotent while locked.
Lock { id: lockScreen }
function lock() { lockScreen.locked = true; }
IpcHandler {
target: "lock"
function lock(): void { root.lock(); }
}
// Screenshot pin: the Shift+Super+S keybind brackets hyprshot
// with pin/unpin so the focus grab is suspended while slurp
// grabs input otherwise the open menu would close like any
@ -639,7 +793,7 @@ in
function activate(act) {
open = false;
if (act === "lock") Quickshell.execDetached([Commands.hyprlock]);
if (act === "lock") bar.shellRoot.lock();
else if (act === "logout") Hyprland.dispatch("hl.dsp.exit()");
else if (act === "reboot") Quickshell.execDetached([Commands.systemctl, "reboot"]);
else Quickshell.execDetached([Commands.systemctl, "poweroff"]);