summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/lock/Backgrounds.qml216
-rw-r--r--modules/lock/Clock.qml56
-rw-r--r--modules/lock/Input.qml254
-rw-r--r--modules/lock/Lock.qml36
-rw-r--r--modules/lock/LockSurface.qml97
5 files changed, 659 insertions, 0 deletions
diff --git a/modules/lock/Backgrounds.qml b/modules/lock/Backgrounds.qml
new file mode 100644
index 0000000..78a4799
--- /dev/null
+++ b/modules/lock/Backgrounds.qml
@@ -0,0 +1,216 @@
+import "root:/widgets"
+import "root:/services"
+import "root:/config"
+import QtQuick
+import QtQuick.Shapes
+import QtQuick.Effects
+
+Item {
+ id: root
+
+ required property bool locked
+
+ readonly property real clockBottom: innerMask.anchors.margins + clockPath.height
+ readonly property real inputTop: innerMask.anchors.margins + inputPath.height
+
+ anchors.fill: parent
+
+ StyledRect {
+ id: base
+
+ anchors.fill: parent
+ color: Colours.alpha(Config.border.colour, false)
+ visible: false
+ }
+
+ Item {
+ id: mask
+
+ anchors.fill: parent
+ layer.enabled: true
+ visible: false
+
+ Rectangle {
+ id: innerMask
+
+ anchors.fill: parent
+ anchors.margins: root.locked ? root.height * Config.lock.sizes.border : 0
+ anchors.leftMargin: root.locked ? root.width * Config.lock.sizes.border : 0
+ anchors.rightMargin: root.locked ? root.width * Config.lock.sizes.border : 0
+
+ radius: Appearance.rounding.large * 2
+
+ Behavior on anchors.margins {
+ Anim {}
+ }
+
+ Behavior on anchors.leftMargin {
+ Anim {}
+ }
+
+ Behavior on anchors.rightMargin {
+ Anim {}
+ }
+ }
+ }
+
+ MultiEffect {
+ anchors.fill: parent
+ source: base
+ maskEnabled: true
+ maskInverted: true
+ maskSource: mask
+ maskThresholdMin: 0.5
+ maskSpreadAtMin: 1
+ }
+
+ Shape {
+ anchors.fill: parent
+ anchors.margins: Math.floor(innerMask.anchors.margins)
+ anchors.leftMargin: innerMask.anchors.leftMargin
+ anchors.rightMargin: innerMask.anchors.rightMargin
+
+ preferredRendererType: Shape.CurveRenderer
+
+ ShapePath {
+ id: clockPath
+
+ readonly property int width: Config.lock.sizes.clockWidth
+ property real height: root.locked ? Config.lock.sizes.clockHeight : 0
+
+ readonly property real rounding: Appearance.rounding.large * 4
+ readonly property bool flatten: height < rounding * 2
+ readonly property real roundingY: flatten ? height / 2 : rounding
+
+ strokeWidth: -1
+ fillColor: Config.border.colour
+
+ startX: (innerMask.width - width) / 2 - rounding
+
+ PathArc {
+ relativeX: clockPath.rounding
+ relativeY: clockPath.roundingY
+ radiusX: clockPath.rounding
+ radiusY: Math.min(clockPath.rounding, clockPath.height)
+ }
+ PathLine {
+ relativeX: 0
+ relativeY: clockPath.height - clockPath.roundingY * 2
+ }
+ PathArc {
+ relativeX: clockPath.rounding
+ relativeY: clockPath.roundingY
+ radiusX: clockPath.rounding
+ radiusY: Math.min(clockPath.rounding, clockPath.height)
+ direction: PathArc.Counterclockwise
+ }
+ PathLine {
+ relativeX: clockPath.width - clockPath.rounding * 2
+ relativeY: 0
+ }
+ PathArc {
+ relativeX: clockPath.rounding
+ relativeY: -clockPath.roundingY
+ radiusX: clockPath.rounding
+ radiusY: Math.min(clockPath.rounding, clockPath.height)
+ direction: PathArc.Counterclockwise
+ }
+ PathLine {
+ relativeX: 0
+ relativeY: -(clockPath.height - clockPath.roundingY * 2)
+ }
+ PathArc {
+ relativeX: clockPath.rounding
+ relativeY: -clockPath.roundingY
+ radiusX: clockPath.rounding
+ radiusY: Math.min(clockPath.rounding, clockPath.height)
+ }
+
+ Behavior on height {
+ Anim {}
+ }
+
+ Behavior on fillColor {
+ ColorAnimation {
+ duration: Appearance.anim.durations.normal
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Appearance.anim.curves.standard
+ }
+ }
+ }
+
+ ShapePath {
+ id: inputPath
+
+ readonly property int width: Config.lock.sizes.inputWidth
+ property real height: root.locked ? Config.lock.sizes.inputHeight : 0
+
+ readonly property real rounding: Appearance.rounding.large * 2
+ readonly property bool flatten: height < rounding * 2
+ readonly property real roundingY: flatten ? height / 2 : rounding
+
+ strokeWidth: -1
+ fillColor: Config.border.colour
+
+ startX: (innerMask.width - width) / 2 - rounding
+ startY: Math.ceil(innerMask.height)
+
+ PathArc {
+ relativeX: inputPath.rounding
+ relativeY: -inputPath.roundingY
+ radiusX: inputPath.rounding
+ radiusY: Math.min(inputPath.rounding, inputPath.height)
+ direction: PathArc.Counterclockwise
+ }
+ PathLine {
+ relativeX: 0
+ relativeY: -(inputPath.height - inputPath.roundingY * 2)
+ }
+ PathArc {
+ relativeX: inputPath.rounding
+ relativeY: -inputPath.roundingY
+ radiusX: inputPath.rounding
+ radiusY: Math.min(inputPath.rounding, inputPath.height)
+ }
+ PathLine {
+ relativeX: inputPath.width - inputPath.rounding * 2
+ relativeY: 0
+ }
+ PathArc {
+ relativeX: inputPath.rounding
+ relativeY: inputPath.roundingY
+ radiusX: inputPath.rounding
+ radiusY: Math.min(inputPath.rounding, inputPath.height)
+ }
+ PathLine {
+ relativeX: 0
+ relativeY: inputPath.height - inputPath.roundingY * 2
+ }
+ PathArc {
+ relativeX: inputPath.rounding
+ relativeY: inputPath.roundingY
+ radiusX: inputPath.rounding
+ radiusY: Math.min(inputPath.rounding, inputPath.height)
+ direction: PathArc.Counterclockwise
+ }
+
+ Behavior on height {
+ Anim {}
+ }
+
+ Behavior on fillColor {
+ ColorAnimation {
+ duration: Appearance.anim.durations.normal
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Appearance.anim.curves.standard
+ }
+ }
+ }
+ }
+
+ component Anim: NumberAnimation {
+ duration: Appearance.anim.durations.large
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Appearance.anim.curves.emphasized
+ }
+}
diff --git a/modules/lock/Clock.qml b/modules/lock/Clock.qml
new file mode 100644
index 0000000..3d4da9e
--- /dev/null
+++ b/modules/lock/Clock.qml
@@ -0,0 +1,56 @@
+import "root:/widgets"
+import "root:/services"
+import "root:/config"
+import QtQuick
+import QtQuick.Layouts
+
+ColumnLayout {
+ id: root
+
+ required property bool locked
+
+ spacing: 0
+
+ RowLayout {
+ Layout.alignment: Qt.AlignHCenter
+ spacing: Appearance.spacing.small
+
+ StyledText {
+ Layout.alignment: Qt.AlignVCenter
+ text: Time.format("HH")
+ color: Colours.palette.m3secondary
+ font.pointSize: Appearance.font.size.extraLarge * 4
+ font.family: Appearance.font.family.mono
+ font.weight: 800
+ }
+
+ StyledText {
+ Layout.alignment: Qt.AlignVCenter
+ text: ":"
+ color: Colours.palette.m3primary
+ font.pointSize: Appearance.font.size.extraLarge * 4
+ font.family: Appearance.font.family.mono
+ font.weight: 800
+ }
+
+ StyledText {
+ Layout.alignment: Qt.AlignVCenter
+ text: Time.format("mm")
+ color: Colours.palette.m3secondary
+ font.pointSize: Appearance.font.size.extraLarge * 4
+ font.family: Appearance.font.family.mono
+ font.weight: 800
+ }
+ }
+
+ StyledText {
+ Layout.alignment: Qt.AlignHCenter
+ Layout.bottomMargin: Appearance.padding.large * 3
+
+ text: Time.format("dddd, d MMMM yyyy")
+ color: Colours.palette.m3tertiary
+ font.pointSize: Appearance.font.size.extraLarge
+ font.family: Appearance.font.family.mono
+ font.bold: true
+ }
+}
diff --git a/modules/lock/Input.qml b/modules/lock/Input.qml
new file mode 100644
index 0000000..922dce8
--- /dev/null
+++ b/modules/lock/Input.qml
@@ -0,0 +1,254 @@
+import "root:/widgets"
+import "root:/services"
+import "root:/config"
+import "root:/utils"
+import Quickshell
+import Quickshell.Wayland
+import Quickshell.Services.Pam
+import QtQuick
+import QtQuick.Layouts
+
+ColumnLayout {
+ id: root
+
+ required property WlSessionLockSurface lock
+
+ property string passwordBuffer
+
+ spacing: Appearance.spacing.large * 2
+
+ RowLayout {
+ Layout.alignment: Qt.AlignHCenter
+ Layout.topMargin: Appearance.padding.large * 3
+ Layout.maximumWidth: Config.lock.sizes.inputWidth - Appearance.rounding.large * 2
+
+ spacing: Appearance.spacing.large
+
+ StyledClippingRect {
+ Layout.alignment: Qt.AlignVCenter
+ implicitWidth: Config.lock.sizes.faceSize
+ implicitHeight: Config.lock.sizes.faceSize
+
+ radius: Appearance.rounding.large
+ color: Colours.palette.m3surfaceContainer
+
+ MaterialIcon {
+ anchors.centerIn: parent
+
+ text: "person"
+ fill: 1
+ font.pointSize: Config.lock.sizes.faceSize / 2
+ }
+
+ CachingImage {
+ anchors.fill: parent
+ path: `${Paths.home}/.face`
+ }
+ }
+
+ ColumnLayout {
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignVCenter
+ spacing: Appearance.spacing.small
+
+ StyledText {
+ Layout.fillWidth: true
+ text: qsTr("Welcome back, %1").arg(Quickshell.env("USER"))
+ font.pointSize: Appearance.font.size.extraLarge
+ font.weight: 500
+ elide: Text.ElideRight
+ }
+
+ StyledText {
+ Layout.fillWidth: true
+ text: qsTr("Logging in to %1").arg(Quickshell.env("XDG_CURRENT_DESKTOP") || Quickshell.env("XDG_SESSION_DESKTOP"))
+ color: Colours.palette.m3tertiary
+ font.pointSize: Appearance.font.size.large
+ elide: Text.ElideRight
+ }
+ }
+ }
+
+ StyledRect {
+ Layout.fillWidth: true
+ Layout.preferredWidth: charList.implicitWidth + Appearance.padding.large * 2
+ Layout.preferredHeight: Appearance.font.size.normal + Appearance.padding.large * 2
+
+ focus: true
+ color: Colours.palette.m3surfaceContainer
+ radius: Appearance.rounding.small
+ clip: true
+
+ Keys.onPressed: event => {
+ if (pam.active)
+ return;
+
+ if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
+ placeholder.animate = false;
+ pam.start();
+ } else if (event.key === Qt.Key_Backspace) {
+ if (event.modifiers & Qt.ControlModifier) {
+ charList.implicitWidth = charList.implicitWidth; // Break binding
+ root.passwordBuffer = "";
+ } else {
+ root.passwordBuffer = root.passwordBuffer.slice(0, -1);
+ }
+ } else if ("abcdefghijklmnopqrstuvwxyz1234567890`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?".includes(event.text.toLowerCase())) {
+ charList.bindImWidth();
+ root.passwordBuffer += event.text;
+ }
+ }
+
+ PamContext {
+ id: pam
+
+ onResponseRequiredChanged: {
+ if (!responseRequired)
+ return;
+
+ respond(root.passwordBuffer);
+ charList.implicitWidth = charList.implicitWidth; // Break binding
+ root.passwordBuffer = "";
+ placeholder.animate = true;
+ }
+
+ onCompleted: res => {
+ if (res === PamResult.Success)
+ return root.lock.unlock();
+
+ if (res === PamResult.Error)
+ placeholder.pamState = "error";
+ else if (res === PamResult.MaxTries)
+ placeholder.pamState = "max";
+ else if (res === PamResult.Failed)
+ placeholder.pamState = "fail";
+
+ placeholderDelay.restart();
+ }
+ }
+
+ Timer {
+ id: placeholderDelay
+
+ interval: 3000
+ onTriggered: placeholder.pamState = ""
+ }
+
+ StyledText {
+ id: placeholder
+
+ property string pamState
+
+ anchors.centerIn: parent
+
+ text: {
+ if (pam.active)
+ return qsTr("Loading...");
+ if (pamState === "error")
+ return qsTr("An error occured");
+ if (pamState === "max")
+ return qsTr("You have reached the maximum number of tries");
+ if (pamState === "fail")
+ return qsTr("Incorrect password");
+ return qsTr("Enter your password");
+ }
+
+ animate: true
+ color: pam.active ? Colours.palette.m3secondary : pamState ? Colours.palette.m3error : Colours.palette.m3outline
+ font.pointSize: Appearance.font.size.larger
+
+ opacity: root.passwordBuffer ? 0 : 1
+
+ Behavior on opacity {
+ Anim {}
+ }
+ }
+
+ ListView {
+ id: charList
+
+ function bindImWidth(): void {
+ imWidthBehavior.enabled = false;
+ implicitWidth = Qt.binding(() => Math.min(count * (Appearance.font.size.normal + spacing) - spacing, Config.lock.sizes.inputWidth - Appearance.rounding.large * 2 - Appearance.padding.large * 5));
+ imWidthBehavior.enabled = true;
+ }
+
+ anchors.centerIn: parent
+
+ implicitWidth: Math.min(count * (Appearance.font.size.normal + spacing) - spacing, Config.lock.sizes.inputWidth - Appearance.rounding.large * 2 - Appearance.padding.large * 5)
+ implicitHeight: Appearance.font.size.normal
+
+ orientation: Qt.Horizontal
+ spacing: Appearance.spacing.small / 2
+
+ model: ScriptModel {
+ values: root.passwordBuffer.split("")
+ }
+
+ delegate: StyledRect {
+ id: ch
+
+ implicitWidth: Appearance.font.size.normal
+ implicitHeight: Appearance.font.size.normal
+
+ color: Colours.palette.m3onSurface
+ radius: Appearance.rounding.full
+
+ opacity: 0
+ scale: 0.5
+ Component.onCompleted: {
+ opacity = 1;
+ scale = 1;
+ }
+ ListView.onRemove: removeAnim.start()
+
+ SequentialAnimation {
+ id: removeAnim
+
+ PropertyAction {
+ target: ch
+ property: "ListView.delayRemove"
+ value: true
+ }
+ ParallelAnimation {
+ Anim {
+ target: ch
+ property: "opacity"
+ to: 0
+ }
+ Anim {
+ target: ch
+ property: "scale"
+ to: 0.5
+ }
+ }
+ PropertyAction {
+ target: ch
+ property: "ListView.delayRemove"
+ value: false
+ }
+ }
+
+ Behavior on opacity {
+ Anim {}
+ }
+
+ Behavior on scale {
+ Anim {}
+ }
+ }
+
+ Behavior on implicitWidth {
+ id: imWidthBehavior
+
+ Anim {}
+ }
+ }
+ }
+
+ component Anim: NumberAnimation {
+ duration: Appearance.anim.durations.normal
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Appearance.anim.curves.standard
+ }
+}
diff --git a/modules/lock/Lock.qml b/modules/lock/Lock.qml
new file mode 100644
index 0000000..1eca886
--- /dev/null
+++ b/modules/lock/Lock.qml
@@ -0,0 +1,36 @@
+pragma ComponentBehavior: Bound
+
+import "root:/widgets"
+import Quickshell
+import Quickshell.Io
+import Quickshell.Wayland
+
+Scope {
+ WlSessionLock {
+ id: lock
+
+ LockSurface {
+ lock: lock
+ }
+ }
+
+ CustomShortcut {
+ name: "lock"
+ description: "Lock the current session"
+ onPressed: lock.locked = true
+ }
+
+ CustomShortcut {
+ name: "unlock"
+ description: "Unlock the current session"
+ onPressed: lock.locked = false
+ }
+
+ IpcHandler {
+ target: "lock"
+
+ function lock(): void {
+ lock.locked = true;
+ }
+ }
+}
diff --git a/modules/lock/LockSurface.qml b/modules/lock/LockSurface.qml
new file mode 100644
index 0000000..1b98c2c
--- /dev/null
+++ b/modules/lock/LockSurface.qml
@@ -0,0 +1,97 @@
+import "root:/widgets"
+import "root:/services"
+import "root:/config"
+import Quickshell.Wayland
+import QtQuick
+import QtQuick.Effects
+
+WlSessionLockSurface {
+ id: root
+
+ required property WlSessionLock lock
+
+ property bool locked
+
+ function unlock(): void {
+ locked = false;
+ background.opacity = 0;
+ animDelay.start();
+ }
+
+ Component.onCompleted: locked = true
+
+ color: "transparent"
+
+ Timer {
+ id: animDelay
+
+ interval: Appearance.anim.durations.large
+ onTriggered: root.lock.locked = false
+ }
+
+ ScreencopyView {
+ id: screencopy
+
+ anchors.fill: parent
+ captureSource: root.screen
+ visible: false
+ }
+
+ MultiEffect {
+ id: background
+
+ anchors.fill: parent
+
+ source: screencopy
+ autoPaddingEnabled: false
+ blurEnabled: true
+ blur: root.locked ? 1 : 0
+ blurMax: 64
+ blurMultiplier: 1
+
+ Behavior on opacity {
+ Anim {}
+ }
+
+ Behavior on blur {
+ Anim {}
+ }
+ }
+
+ Backgrounds {
+ id: backgrounds
+
+ locked: root.locked
+ visible: false
+ }
+
+ MultiEffect {
+ anchors.fill: source
+ source: backgrounds
+ shadowEnabled: true
+ blurMax: 15
+ shadowColor: Qt.alpha(Colours.palette.m3shadow, 0.7)
+ }
+
+ Clock {
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.bottom: parent.top
+ anchors.bottomMargin: -backgrounds.clockBottom
+
+ locked: root.locked
+ }
+
+ Input {
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: parent.bottom
+ anchors.topMargin: -backgrounds.inputTop
+
+ lock: root
+ }
+
+ component Anim: NumberAnimation {
+ duration: Appearance.anim.durations.large
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Appearance.anim.curves.standard
+ }
+}