Merge branch 'main' into main

This commit is contained in:
end-4
2025-10-30 10:00:39 +01:00
committed by GitHub
81 changed files with 1909 additions and 420 deletions
@@ -28,7 +28,10 @@ Scope {
Connections {
target: GlobalStates
function onScreenLockedChanged() {
if (GlobalStates.screenLocked) lockContext.reset();
if (GlobalStates.screenLocked) {
lockContext.reset();
lockContext.tryFingerUnlock();
}
}
}
@@ -113,7 +116,7 @@ Scope {
onPressed: {
if (Config.options.lock.useHyprlock) {
Quickshell.execDetached(["hyprlock"])
Quickshell.execDetached(["bash", "-c", "pidof hyprlock || hyprlock"]);
return;
}
GlobalStates.screenLocked = true;
@@ -2,6 +2,7 @@ import qs
import qs.modules.common
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Services.Pam
Scope {
@@ -18,6 +19,7 @@ Scope {
property string currentText: ""
property bool unlockInProgress: false
property bool showFailure: false
property bool fingerprintsConfigured: false
property var targetAction: LockContext.ActionEnum.Unlock
function resetTargetAction() {
@@ -60,6 +62,34 @@ Scope {
pam.start();
}
function tryFingerUnlock() {
if (root.fingerprintsConfigured) {
fingerPam.start();
}
}
function stopFingerPam() {
fingerPam.abort();
}
Process {
id: fingerprintCheckProc
running: true
command: ["bash", "-c", "fprintd-list $(whoami)"]
stdout: StdioCollector {
id: fingerprintOutputCollector
onStreamFinished: {
root.fingerprintsConfigured = fingerprintOutputCollector.text.includes("Fingerprints for user");
}
}
onExited: (exitCode, exitStatus) => {
if (exitCode !== 0) {
console.warn("fprintd-list command exited with error:", exitCode, exitStatus);
root.fingerprintsConfigured = false;
}
}
}
PamContext {
id: pam
@@ -74,6 +104,7 @@ Scope {
onCompleted: result => {
if (result == PamResult.Success) {
root.unlocked(root.targetAction);
stopFingerPam();
} else {
root.clearText();
root.unlockInProgress = false;
@@ -83,4 +114,19 @@ Scope {
}
}
PamContext {
id: fingerPam
configDirectory: "pam"
config: "fprintd.conf"
onCompleted: result => {
if (result == PamResult.Success) {
root.unlocked(root.targetAction);
stopFingerPam();
} else if (result == PamResult.Error){ // if timeout or etc..
tryFingerUnlock()
}
}
}
}
@@ -1,5 +1,6 @@
import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell.Services.UPower
import qs
import qs.services
@@ -7,6 +8,7 @@ import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import qs.modules.bar as Bar
import Quickshell
import Quickshell.Services.SystemTray
MouseArea {
@@ -98,6 +100,23 @@ MouseArea {
scale: root.toolbarScale
opacity: root.toolbarOpacity
// Fingerprint
Loader {
Layout.leftMargin: 10
Layout.rightMargin: 6
Layout.alignment: Qt.AlignVCenter
active: root.context.fingerprintsConfigured
visible: active
sourceComponent: MaterialSymbol {
id: fingerprintIcon
fill: 1
text: "fingerprint"
iconSize: Appearance.font.pixelSize.hugeass
color: Appearance.colors.colOnSurfaceVariant
}
}
ToolbarTextField {
id: passwordBox
placeholderText: GlobalStates.screenUnlockFailed ? Translation.tr("Incorrect password") : Translation.tr("Enter password")
@@ -124,7 +143,17 @@ MouseArea {
Keys.onPressed: event => {
root.context.resetClearTimer();
}
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: passwordBox.width - 8
height: passwordBox.height
radius: height / 2
}
}
// Shake when wrong password
SequentialAnimation {
id: wrongPasswordShakeAnim
NumberAnimation { target: passwordBox; property: "x"; to: -30; duration: 50 }
@@ -139,6 +168,17 @@ MouseArea {
if (GlobalStates.screenUnlockFailed) wrongPasswordShakeAnim.restart();
}
}
// We're drawing dots manually
color: ColorUtils.transparentize(Appearance.colors.colOnLayer1)
PasswordChars {
anchors {
fill: parent
leftMargin: passwordBox.padding
rightMargin: passwordBox.padding
}
length: root.context.currentText.length
}
}
ToolbarButton {
@@ -0,0 +1,96 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import Quickshell
StyledFlickable {
id: root
required property int length
contentWidth: dotsRow.implicitWidth
contentX: (Math.max(contentWidth - width, 0))
Behavior on contentX {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
rightMargin: 14
Row {
id: dotsRow
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
leftMargin: 4
}
spacing: 10
Repeater {
model: ScriptModel {
values: Array(root.length)
}
delegate: Item {
id: charItem
required property int index
implicitWidth: 10
implicitHeight: 10
MaterialShape {
id: materialShape
anchors.centerIn: parent
property list<var> charShapes: [
MaterialShape.Shape.Clover4Leaf,
MaterialShape.Shape.Arrow,
MaterialShape.Shape.Pill,
MaterialShape.Shape.SoftBurst,
MaterialShape.Shape.Diamond,
MaterialShape.Shape.ClamShell,
MaterialShape.Shape.Pentagon,
]
shape: charShapes[charItem.index % charShapes.length]
// Animate on appearance
color: Appearance.colors.colPrimary
implicitSize: 0
opacity: 0
scale: 0.5
Component.onCompleted: {
appearAnim.start();
}
ParallelAnimation {
id: appearAnim
NumberAnimation {
target: materialShape
properties: "opacity"
to: 1
duration: 50
easing.type: Appearance.animation.elementMoveFast.type
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
}
NumberAnimation {
target: materialShape
properties: "scale"
to: 1
duration: 200
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
}
NumberAnimation {
target: materialShape
properties: "implicitSize"
to: 18
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
}
ColorAnimation {
target: materialShape
properties: "color"
from: Appearance.colors.colPrimary
to: Appearance.colors.colOnLayer1
duration: 1000
easing.type: Appearance.animation.elementMoveFast.type
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
}
}
}
}
}
}
}
@@ -0,0 +1 @@
auth sufficient pam_fprintd.so