summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/dashboard/dash/Weather.qml6
-rw-r--r--modules/lock/Center.qml186
-rw-r--r--modules/lock/Content.qml80
-rw-r--r--modules/lock/Input.qml60
-rw-r--r--modules/lock/InputField.qml159
-rw-r--r--modules/lock/Lock.qml35
-rw-r--r--modules/lock/LockSurface.qml292
-rw-r--r--modules/lock/Pam.qml66
-rw-r--r--modules/lock/WeatherInfo.qml192
9 files changed, 850 insertions, 226 deletions
diff --git a/modules/dashboard/dash/Weather.qml b/modules/dashboard/dash/Weather.qml
index 0160229..c90ccf0a 100644
--- a/modules/dashboard/dash/Weather.qml
+++ b/modules/dashboard/dash/Weather.qml
@@ -20,7 +20,7 @@ Item {
anchors.left: parent.left
animate: true
- text: Weather.icon || "cloud_alert"
+ text: Weather.icon
color: Colours.palette.m3secondary
font.pointSize: Appearance.font.size.extraLarge * 2
}
@@ -38,7 +38,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
animate: true
- text: Config.services.useFahrenheit ? Weather.tempF : Weather.tempC
+ text: Weather.temp
color: Colours.palette.m3primary
font.pointSize: Appearance.font.size.extraLarge
font.weight: 500
@@ -48,7 +48,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
animate: true
- text: Weather.description || qsTr("No weather")
+ text: Weather.description
elide: Text.ElideRight
width: Math.min(implicitWidth, root.parent.width - icon.implicitWidth - info.anchors.leftMargin - Appearance.padding.large * 2)
diff --git a/modules/lock/Center.qml b/modules/lock/Center.qml
new file mode 100644
index 0000000..b288284
--- /dev/null
+++ b/modules/lock/Center.qml
@@ -0,0 +1,186 @@
+pragma ComponentBehavior: Bound
+
+import qs.components
+import qs.components.images
+import qs.services
+import qs.config
+import qs.utils
+import QtQuick
+import QtQuick.Layouts
+
+ColumnLayout {
+ id: root
+
+ required property var lock
+ readonly property list<string> timeComponents: Time.format(Config.services.useTwelveHourClock ? "hh:mm:A" : "hh:mm").split(":")
+
+ Layout.preferredWidth: Config.lock.sizes.centerWidth
+ Layout.fillHeight: true
+
+ spacing: Appearance.spacing.large * 2
+
+ RowLayout {
+ Layout.alignment: Qt.AlignHCenter
+ spacing: Appearance.spacing.small
+
+ StyledText {
+ Layout.alignment: Qt.AlignVCenter
+ text: root.timeComponents[0]
+ color: Colours.palette.m3secondary
+ font.pointSize: Appearance.font.size.extraLarge * 3
+ font.bold: true
+ }
+
+ StyledText {
+ Layout.alignment: Qt.AlignVCenter
+ text: ":"
+ color: Colours.palette.m3primary
+ font.pointSize: Appearance.font.size.extraLarge * 3
+ font.bold: true
+ }
+
+ StyledText {
+ Layout.alignment: Qt.AlignVCenter
+ text: root.timeComponents[1]
+ color: Colours.palette.m3secondary
+ font.pointSize: Appearance.font.size.extraLarge * 3
+ font.bold: true
+ }
+
+ Loader {
+ Layout.leftMargin: Appearance.spacing.normal
+ Layout.alignment: Qt.AlignVCenter
+
+ asynchronous: true
+ active: Config.services.useTwelveHourClock
+ visible: active
+
+ sourceComponent: StyledText {
+ text: root.timeComponents[2] ?? ""
+ color: Colours.palette.m3primary
+ font.pointSize: Appearance.font.size.extraLarge * 2
+ font.bold: true
+ }
+ }
+ }
+
+ StyledText {
+ Layout.alignment: Qt.AlignHCenter
+ Layout.topMargin: -Appearance.padding.large * 2
+
+ 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
+ }
+
+ StyledClippingRect {
+ Layout.topMargin: Appearance.spacing.large * 2
+ Layout.alignment: Qt.AlignHCenter
+
+ implicitWidth: Config.lock.sizes.centerWidth / 2
+ implicitHeight: Config.lock.sizes.centerWidth / 2
+
+ color: Colours.tPalette.m3surfaceContainer
+ radius: Appearance.rounding.full
+
+ MaterialIcon {
+ anchors.centerIn: parent
+
+ text: "person"
+ fill: 1
+ grade: 200
+ font.pointSize: Math.floor(Config.lock.sizes.centerWidth / 4)
+ }
+
+ CachingImage {
+ id: pfp
+
+ anchors.fill: parent
+ path: `${Paths.stringify(Paths.home)}/.face`
+ }
+ }
+
+ StyledRect {
+ Layout.alignment: Qt.AlignHCenter
+
+ implicitWidth: Config.lock.sizes.centerWidth * 0.8
+ implicitHeight: input.implicitHeight + Appearance.padding.small * 2
+
+ color: Colours.palette.m3surfaceContainer
+ radius: Appearance.rounding.full
+
+ focus: true
+ onActiveFocusChanged: {
+ if (!activeFocus)
+ forceActiveFocus();
+ }
+
+ Keys.onPressed: event => {
+ if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return)
+ inputField.placeholder.animate = false;
+
+ root.lock.pam.handleKey(event);
+ }
+
+ StateLayer {
+ hoverEnabled: false
+ cursorShape: Qt.IBeamCursor
+
+ function onClicked(): void {
+ parent.forceActiveFocus();
+ }
+ }
+
+ RowLayout {
+ id: input
+
+ anchors.fill: parent
+ anchors.margins: Appearance.padding.small
+ spacing: Appearance.spacing.normal
+
+ MaterialIcon {
+ Layout.leftMargin: Appearance.padding.smaller
+ text: "lock"
+ }
+
+ InputField {
+ id: inputField
+
+ pam: root.lock.pam
+ }
+
+ StyledRect {
+ implicitWidth: implicitHeight
+ implicitHeight: enterIcon.implicitHeight + Appearance.padding.small * 2
+
+ color: root.lock.pam.buffer ? Colours.palette.m3primary : Colours.layer(Colours.palette.m3surfaceContainerHigh, 2)
+ radius: Appearance.rounding.full
+
+ StateLayer {
+ color: root.lock.pam.buffer ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
+
+ function onClicked(): void {
+ root.lock.pam.passwd.start();
+ }
+ }
+
+ MaterialIcon {
+ id: enterIcon
+
+ anchors.centerIn: parent
+ text: "arrow_forward"
+ color: root.lock.pam.buffer ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
+ font.weight: 500
+ }
+ }
+ }
+ }
+
+ component Anim: NumberAnimation {
+ duration: Appearance.anim.durations.normal
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Appearance.anim.curves.standard
+ }
+}
diff --git a/modules/lock/Content.qml b/modules/lock/Content.qml
new file mode 100644
index 0000000..b7d3ddb
--- /dev/null
+++ b/modules/lock/Content.qml
@@ -0,0 +1,80 @@
+import qs.components
+import qs.services
+import qs.config
+import QtQuick
+import QtQuick.Layouts
+
+RowLayout {
+ id: root
+
+ required property var lock
+ property real centerScale
+
+ anchors.fill: parent
+ anchors.margins: Appearance.padding.large
+
+ spacing: Appearance.spacing.large * 2
+
+ ColumnLayout {
+ Layout.fillWidth: true
+ spacing: Appearance.spacing.normal
+
+ StyledRect {
+ Layout.fillWidth: true
+ implicitHeight: weather.implicitHeight
+
+ topLeftRadius: Appearance.rounding.large
+ radius: Appearance.rounding.small
+ color: Colours.tPalette.m3surfaceContainer
+
+ WeatherInfo {
+ id: weather
+ }
+ }
+
+ StyledRect {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ bottomLeftRadius: Appearance.rounding.large
+ radius: Appearance.rounding.small
+ color: Colours.tPalette.m3surfaceContainer
+ }
+ }
+
+ Center {
+ Layout.leftMargin: -(1 - scale) * implicitWidth / 2
+ Layout.rightMargin: -(1 - scale) * implicitWidth / 2
+ scale: root.centerScale
+ lock: root.lock
+ }
+
+ ColumnLayout {
+ Layout.fillWidth: true
+ spacing: Appearance.spacing.normal
+
+ StyledRect {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ topRightRadius: Appearance.rounding.large
+ radius: Appearance.rounding.small
+ color: Colours.tPalette.m3surfaceContainer
+ }
+
+ StyledRect {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ bottomRightRadius: Appearance.rounding.large
+ radius: Appearance.rounding.small
+ color: Colours.tPalette.m3surfaceContainer
+ }
+ }
+
+ component Anim: NumberAnimation {
+ duration: Appearance.anim.durations.normal
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Appearance.anim.curves.standard
+ }
+}
diff --git a/modules/lock/Input.qml b/modules/lock/Input.qml
index b989bb9..1e164b8 100644
--- a/modules/lock/Input.qml
+++ b/modules/lock/Input.qml
@@ -12,60 +12,40 @@ import QtQuick.Layouts
ColumnLayout {
id: root
- required property WlSessionLockSurface lock
+ required property var lock
property string passwordBuffer
+ Layout.preferredWidth: Config.lock.sizes.faceSize * 2
+ Layout.fillWidth: false
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.tPalette.m3surfaceContainer
+ StyledRect {
+ Layout.fillWidth: true
+ implicitHeight: user.implicitHeight + Appearance.padding.small * 2
- MaterialIcon {
- anchors.centerIn: parent
+ color: Colours.tPalette.m3surfaceContainer
+ radius: Appearance.rounding.small
- text: "person"
- fill: 1
- grade: 200
- font.pointSize: Config.lock.sizes.faceSize / 2
- }
+ RowLayout {
+ id: user
- CachingImage {
- anchors.fill: parent
- path: `${Paths.stringify(Paths.home)}/.face`
- }
- }
+ anchors.centerIn: parent
- ColumnLayout {
- Layout.fillWidth: true
- Layout.alignment: Qt.AlignVCenter
- spacing: Appearance.spacing.small
+ spacing: Appearance.spacing.normal
- StyledText {
- Layout.fillWidth: true
- text: qsTr("Welcome back, %1").arg(Quickshell.env("USER"))
- font.pointSize: Appearance.font.size.extraLarge
+ MaterialIcon {
+ text: "account_circle"
+ font.pointSize: Appearance.font.size.large * 1.4
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
+ // Layout.fillWidth: true
+ text: Quickshell.env("USER")
font.pointSize: Appearance.font.size.large
+ // font.capitalization: Font.Capitalize
+ font.weight: 500
elide: Text.ElideRight
}
}
@@ -121,7 +101,7 @@ ColumnLayout {
onCompleted: res => {
if (res === PamResult.Success)
- return root.lock.unlock();
+ return root.lock.lock.unlock();
if (res === PamResult.Error)
placeholder.pamState = "error";
diff --git a/modules/lock/InputField.qml b/modules/lock/InputField.qml
new file mode 100644
index 0000000..9472d41
--- /dev/null
+++ b/modules/lock/InputField.qml
@@ -0,0 +1,159 @@
+pragma ComponentBehavior: Bound
+
+import qs.components
+import qs.services
+import qs.config
+import Quickshell
+import QtQuick
+import QtQuick.Layouts
+
+Item {
+ id: root
+
+ required property Pam pam
+ readonly property alias placeholder: placeholder
+ property string buffer
+
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ clip: true
+
+ Connections {
+ target: root.pam
+
+ function onBufferChanged(): void {
+ if (root.pam.buffer.length > root.buffer.length) {
+ charList.bindImWidth();
+ } else if (root.pam.buffer.length === 0) {
+ charList.implicitWidth = charList.implicitWidth;
+ placeholder.animate = true;
+ }
+
+ root.buffer = root.pam.buffer;
+ }
+ }
+
+ StyledText {
+ id: placeholder
+
+ anchors.centerIn: parent
+
+ text: {
+ if (root.pam.active)
+ return qsTr("Loading...");
+ if (root.pam.state === "error")
+ return qsTr("An error occured");
+ if (root.pam.state === "max")
+ return qsTr("You have reached the maximum number of tries");
+ if (root.pam.state === "fail")
+ return qsTr("Incorrect password");
+ return qsTr("Enter your password");
+ }
+
+ animate: true
+ color: root.pam.active ? Colours.palette.m3secondary : root.pam.state ? Colours.palette.m3error : Colours.palette.m3outline
+ font.pointSize: Appearance.font.size.normal
+ font.family: Appearance.font.family.mono
+
+ opacity: root.buffer ? 0 : 1
+
+ Behavior on opacity {
+ Anim {}
+ }
+ }
+
+ ListView {
+ id: charList
+
+ readonly property int fullWidth: count * (implicitHeight + spacing) - spacing
+
+ function bindImWidth(): void {
+ imWidthBehavior.enabled = false;
+ implicitWidth = Qt.binding(() => fullWidth);
+ imWidthBehavior.enabled = true;
+ }
+
+ anchors.centerIn: parent
+ anchors.horizontalCenterOffset: implicitWidth > root.width ? -(implicitWidth - root.width) / 2 : 0
+
+ implicitWidth: fullWidth
+ implicitHeight: Appearance.font.size.normal
+
+ orientation: Qt.Horizontal
+ spacing: Appearance.spacing.small / 2
+ interactive: false
+
+ model: ScriptModel {
+ values: root.buffer.split("")
+ }
+
+ delegate: StyledRect {
+ id: ch
+
+ implicitWidth: implicitHeight
+ implicitHeight: charList.implicitHeight
+
+ color: Colours.palette.m3onSurface
+ radius: Appearance.rounding.small / 2
+
+ opacity: 0
+ scale: 0
+ 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 {
+ duration: Appearance.anim.durations.expressiveFastSpatial
+ easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
+ }
+ }
+ }
+
+ 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
index d0f27ec..fc0bff8 100644
--- a/modules/lock/Lock.qml
+++ b/modules/lock/Lock.qml
@@ -6,52 +6,49 @@ import Quickshell.Io
import Quickshell.Wayland
Scope {
- LazyLoader {
- id: loader
- WlSessionLock {
- id: lock
+ WlSessionLock {
+ id: lock
- property bool unlocked
+ signal unlock
- locked: true
+ LockSurface {
+ lock: lock
+ pam: pam
+ }
+ }
- onLockedChanged: {
- if (!locked)
- loader.active = false;
- }
+ Pam {
+ id: pam
- LockSurface {
- lock: lock
- }
- }
+ lock: lock
}
CustomShortcut {
name: "lock"
description: "Lock the current session"
- onPressed: loader.activeAsync = true
+ onPressed: lock.locked = true
}
CustomShortcut {
name: "unlock"
description: "Unlock the current session"
- onPressed: loader.item.locked = false
+ onPressed: lock.unlock()
}
IpcHandler {
target: "lock"
function lock(): void {
- loader.activeAsync = true;
+ lock.locked = true;
}
function unlock(): void {
- loader.item.locked = false;
+ lock.unlock();
}
function isLocked(): bool {
- return loader.active;
+ return lock.locked;
}
}
}
diff --git a/modules/lock/LockSurface.qml b/modules/lock/LockSurface.qml
index ed6895b..88bd1af 100644
--- a/modules/lock/LockSurface.qml
+++ b/modules/lock/LockSurface.qml
@@ -1,5 +1,7 @@
pragma ComponentBehavior: Bound
+import qs.components
+import qs.components.effects
import qs.services
import qs.config
import Quickshell.Wayland
@@ -10,31 +12,144 @@ WlSessionLockSurface {
id: root
required property WlSessionLock lock
+ required property Pam pam
- property bool thisLocked
- readonly property bool locked: thisLocked && !lock.unlocked
+ property bool locked
- function unlock(): void {
- lock.unlocked = true;
- animDelay.start();
- }
-
- Component.onCompleted: thisLocked = true
+ Component.onCompleted: locked = true
color: "transparent"
- Timer {
- id: animDelay
+ Connections {
+ target: root.lock
- interval: Appearance.anim.durations.large
- onTriggered: root.lock.locked = false
+ function onUnlock(): void {
+ root.locked = false;
+ unlockAnim.start();
+ }
}
- Connections {
- target: root.lock
+ SequentialAnimation {
+ id: unlockAnim
+
+ ParallelAnimation {
+ Anim {
+ target: lockBg
+ properties: "implicitWidth,implicitHeight"
+ to: lockBg.size
+ duration: Appearance.anim.durations.expressiveDefaultSpatial
+ easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
+ }
+ Anim {
+ target: lockBg
+ property: "radius"
+ to: lockBg.size / 4
+ }
+ Anim {
+ target: content
+ property: "opacity"
+ to: 0
+ duration: Appearance.anim.durations.small
+ }
+ Anim {
+ target: lockIcon
+ property: "opacity"
+ to: 1
+ }
+ Anim {
+ target: background
+ property: "opacity"
+ to: 0
+ duration: Appearance.anim.durations.large
+ }
+ SequentialAnimation {
+ PauseAnimation {
+ duration: Appearance.anim.durations.small
+ }
+ Anim {
+ target: lockBg
+ property: "opacity"
+ to: 0
+ }
+ }
+ }
+ PropertyAction {
+ target: root.lock
+ property: "locked"
+ value: false
+ }
+ }
+
+ ParallelAnimation {
+ running: true
- function onUnlockedChanged(): void {
- background.opacity = 0;
+ Anim {
+ target: background
+ property: "opacity"
+ to: 1
+ duration: Appearance.anim.durations.large
+ }
+ SequentialAnimation {
+ ParallelAnimation {
+ Anim {
+ target: lockBg
+ property: "scale"
+ to: 1
+ duration: Appearance.anim.durations.expressiveFastSpatial
+ easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
+ }
+ Anim {
+ target: lockBg
+ property: "rotation"
+ to: 360
+ duration: Appearance.anim.durations.expressiveFastSpatial
+ easing.bezierCurve: Appearance.anim.curves.standardAccel
+ }
+ }
+ ParallelAnimation {
+ Anim {
+ target: lockIcon
+ property: "rotation"
+ to: 360
+ easing.bezierCurve: Appearance.anim.curves.standardDecel
+ }
+ Anim {
+ target: lockIcon
+ property: "opacity"
+ to: 0
+ }
+ Anim {
+ target: content
+ property: "opacity"
+ to: 1
+ }
+ Anim {
+ target: content
+ property: "centerScale"
+ to: 1
+ duration: Appearance.anim.durations.expressiveDefaultSpatial
+ easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
+ }
+ Anim {
+ target: lockBg
+ property: "radius"
+ to: Appearance.rounding.large * 1.5
+ }
+ Anim {
+ target: lockBg
+ property: "implicitWidth"
+ to: root.screen.height * Config.lock.sizes.heightMult * Config.lock.sizes.ratio
+ duration: Appearance.anim.durations.expressiveDefaultSpatial
+ easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
+ }
+ Anim {
+ target: lockBg
+ property: "implicitHeight"
+ to: root.screen.height * Config.lock.sizes.heightMult
+ duration: Appearance.anim.durations.expressiveDefaultSpatial
+ easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
+ }
+ }
}
}
@@ -43,138 +158,63 @@ WlSessionLockSurface {
anchors.fill: parent
captureSource: root.screen
+ opacity: 0
layer.enabled: true
layer.effect: MultiEffect {
+ id: backgroundBlur
+
autoPaddingEnabled: false
blurEnabled: true
- blur: root.locked ? 1 : 0
+ blur: 1
blurMax: 64
blurMultiplier: 1
-
- Behavior on blur {
- Anim {}
- }
- }
-
- Behavior on opacity {
- Anim {}
}
}
- Backgrounds {
- id: backgrounds
-
- locked: root.locked
- weatherWidth: weather.implicitWidth
- buttonsWidth: buttons.item?.nonAnimWidth ?? 0
- buttonsHeight: buttons.item?.nonAnimHeight ?? 0
- statusWidth: status.nonAnimWidth ?? 0
- statusHeight: status.nonAnimHeight ?? 0
- isNormal: root.screen.width > Config.lock.sizes.smallScreenWidth
- isLarge: root.screen.width > Config.lock.sizes.largeScreenWidth
-
- layer.enabled: true
- layer.effect: MultiEffect {
- 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
- }
-
- Input {
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.top: parent.bottom
- anchors.topMargin: -backgrounds.inputTop
-
- lock: root
- }
-
- WeatherInfo {
- id: weather
-
- anchors.top: parent.bottom
- anchors.right: parent.left
- anchors.topMargin: -backgrounds.weatherTop
- anchors.rightMargin: -backgrounds.weatherRight
- }
+ StyledRect {
+ id: lockBg
- Loader {
- id: media
+ readonly property int size: lockIcon.implicitHeight + Appearance.padding.large * 4
- active: root.screen.width > Config.lock.sizes.smallScreenWidth
- asynchronous: true
+ anchors.centerIn: parent
+ implicitWidth: size
+ implicitHeight: size
- state: root.screen.width > Config.lock.sizes.largeScreenWidth ? "tl" : "br"
- states: [
- State {
- name: "tl"
+ clip: true
+ color: Colours.tPalette.m3surface
+ radius: size / 4
+ rotation: 180
+ scale: 0
- AnchorChanges {
- target: media
- anchors.bottom: media.parent.top
- anchors.right: media.parent.left
- }
-
- PropertyChanges {
- media.anchors.bottomMargin: -backgrounds.mediaY
- media.anchors.rightMargin: -backgrounds.mediaX
- }
- },
- State {
- name: "br"
-
- AnchorChanges {
- target: media
- anchors.top: media.parent.bottom
- anchors.left: media.parent.right
- }
-
- PropertyChanges {
- media.anchors.topMargin: -backgrounds.mediaY
- media.anchors.leftMargin: -backgrounds.mediaX
- }
- }
- ]
-
- sourceComponent: MediaPlaying {
- isLarge: root.screen.width > Config.lock.sizes.largeScreenWidth
+ Elevation {
+ anchors.fill: parent
+ radius: parent.radius
+ z: -1
+ level: 3
+ offset.y: 0
}
- }
-
- Loader {
- id: buttons
-
- active: root.screen.width > Config.lock.sizes.largeScreenWidth
- asynchronous: true
- anchors.top: parent.bottom
- anchors.left: parent.right
- anchors.topMargin: -backgrounds.buttonsTop
- anchors.leftMargin: -backgrounds.buttonsLeft
+ MaterialIcon {
+ id: lockIcon
- sourceComponent: Buttons {}
- }
-
- Status {
- id: status
+ anchors.centerIn: parent
+ text: "lock"
+ font.pointSize: Appearance.font.size.extraLarge * 4
+ font.bold: true
+ rotation: 180
+ }
- anchors.bottom: parent.top
- anchors.left: parent.right
- anchors.bottomMargin: -backgrounds.statusBottom
- anchors.leftMargin: -backgrounds.statusLeft
+ Content {
+ id: content
- showNotifs: root.screen.width > Config.lock.sizes.largeScreenWidth
+ lock: root
+ opacity: 0
+ }
}
component Anim: NumberAnimation {
- duration: Appearance.anim.durations.large
+ duration: Appearance.anim.durations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.anim.curves.standard
}
diff --git a/modules/lock/Pam.qml b/modules/lock/Pam.qml
new file mode 100644
index 0000000..fa6e3d7
--- /dev/null
+++ b/modules/lock/Pam.qml
@@ -0,0 +1,66 @@
+import Quickshell
+import Quickshell.Wayland
+import Quickshell.Services.Pam
+import QtQuick
+
+Scope {
+ id: root
+
+ required property WlSessionLock lock
+
+ readonly property alias passwd: passwd
+ readonly property bool active: passwd.active
+ property string state
+ property string buffer
+
+ function handleKey(event: KeyEvent): void {
+ if (passwd.active)
+ return;
+
+ if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
+ passwd.start();
+ } else if (event.key === Qt.Key_Backspace) {
+ if (event.modifiers & Qt.ControlModifier) {
+ buffer = "";
+ } else {
+ buffer = buffer.slice(0, -1);
+ }
+ } else if (" abcdefghijklmnopqrstuvwxyz1234567890`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?".includes(event.text.toLowerCase())) {
+ // No illegal characters (you are insane if you use unicode in your password)
+ buffer += event.text;
+ }
+ }
+
+ PamContext {
+ id: passwd
+
+ onResponseRequiredChanged: {
+ if (!responseRequired)
+ return;
+
+ respond(root.buffer);
+ root.buffer = "";
+ }
+
+ onCompleted: res => {
+ if (res === PamResult.Success)
+ return root.lock.unlock();
+
+ if (res === PamResult.Error)
+ root.state = "error";
+ else if (res === PamResult.MaxTries)
+ root.state = "max";
+ else if (res === PamResult.Failed)
+ root.state = "fail";
+
+ stateReset.restart();
+ }
+ }
+
+ Timer {
+ id: stateReset
+
+ interval: 4000
+ onTriggered: root.state = ""
+ }
+}
diff --git a/modules/lock/WeatherInfo.qml b/modules/lock/WeatherInfo.qml
index b944d2f..5d10e7b 100644
--- a/modules/lock/WeatherInfo.qml
+++ b/modules/lock/WeatherInfo.qml
@@ -1,61 +1,177 @@
import qs.components
import qs.services
import qs.config
+import qs.utils
import QtQuick
import QtQuick.Layouts
-RowLayout {
+ColumnLayout {
id: root
- Timer {
- running: true
- triggeredOnStart: true
- repeat: true
- interval: 900000 // 15 minutes
- onTriggered: Weather.reload()
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.margins: Appearance.padding.large * 2
+
+ spacing: Appearance.spacing.small
+
+ StyledText {
+ Layout.topMargin: Appearance.padding.large * 2
+ Layout.alignment: Qt.AlignHCenter
+ text: qsTr("Weather")
+ color: Colours.palette.m3primary
+ font.pointSize: Appearance.font.size.extraLarge
+ font.weight: 500
}
- spacing: Appearance.spacing.large
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: Appearance.spacing.large
+
+ MaterialIcon {
+ animate: true
+ text: Weather.icon
+ color: Colours.palette.m3secondary
+ font.pointSize: Appearance.font.size.extraLarge * 2.5
+ }
+
+ ColumnLayout {
+ spacing: Appearance.spacing.small
- MaterialIcon {
- id: icon
+ StyledText {
+ Layout.fillWidth: true
- Layout.alignment: Qt.AlignVCenter
- Layout.topMargin: Config.lock.sizes.border / 4
+ animate: true
+ text: Weather.description
+ color: Colours.palette.m3secondary
+ font.pointSize: Appearance.font.size.large
+ font.weight: 500
+ elide: Text.ElideRight
+ }
- animate: true
- text: Weather.icon || "cloud_alert"
- color: Colours.palette.m3secondary
- font.pointSize: Appearance.font.size.extraLarge * 2.5
- }
+ StyledText {
+ Layout.fillWidth: true
- ColumnLayout {
- Layout.alignment: Qt.AlignVCenter
- Layout.topMargin: Config.lock.sizes.border / 4
- Layout.rightMargin: Config.lock.sizes.border / 2
+ animate: true
+ text: qsTr("Humidity: %1%").arg(Weather.humidity)
+ color: Colours.palette.m3onSurfaceVariant
+ font.pointSize: Appearance.font.size.normal
+ elide: Text.ElideRight
+ }
+ }
- spacing: Appearance.spacing.small
+ Loader {
+ Layout.rightMargin: Appearance.padding.smaller
+ asynchronous: true
+ active: root.width > 400
+ visible: active
- StyledText {
- Layout.fillWidth: true
+ sourceComponent: ColumnLayout {
+ spacing: Appearance.spacing.small
- animate: true
- text: Config.services.useFahrenheit ? Weather.tempF : Weather.tempC
- color: Colours.palette.m3primary
- horizontalAlignment: Text.AlignHCenter
- font.pointSize: Appearance.font.size.extraLarge
- font.weight: 500
+ StyledText {
+ Layout.fillWidth: true
+
+ animate: true
+ text: Weather.temp
+ color: Colours.palette.m3primary
+ horizontalAlignment: Text.AlignRight
+ font.pointSize: Appearance.font.size.extraLarge
+ font.weight: 500
+ elide: Text.ElideLeft
+ }
+
+ StyledText {
+ Layout.fillWidth: true
+
+ animate: true
+ text: qsTr("Feels like: %1").arg(Weather.feelsLike)
+ color: Colours.palette.m3outline
+ horizontalAlignment: Text.AlignRight
+ font.pointSize: Appearance.font.size.smaller
+ elide: Text.ElideLeft
+ }
+ }
}
+ }
- StyledText {
- Layout.fillWidth: true
- Layout.maximumWidth: Config.lock.sizes.weatherWidth - icon.implicitWidth
+ RowLayout {
+ Layout.topMargin: Appearance.spacing.smaller
+ Layout.bottomMargin: Appearance.padding.large * 2
+ Layout.fillWidth: true
+ spacing: Appearance.spacing.large
- animate: true
- text: Weather.description || qsTr("No weather")
- horizontalAlignment: Text.AlignHCenter
- font.pointSize: Appearance.font.size.large
- elide: Text.ElideRight
+ Repeater {
+ model: {
+ const forecast = Weather.forecast;
+ let count = root.width < 320 ? 3 : root.width < 400 ? 4 : 5;
+ if (!forecast)
+ return Array.from({
+ length: count
+ }, () => null);
+
+ const hours = [];
+ const hour = new Date().getHours();
+
+ const today = forecast[0].hourly;
+ const arr = [...today, ...forecast[1].hourly];
+ for (let i = 0; i < arr.length; i++) {
+ const time = parseInt(arr[i].time, 10) / 100;
+
+ if (i > today.length ? time < hour : time > hour) {
+ hours.push(arr[i]);
+ count--;
+ }
+
+ if (count === 0)
+ break;
+ }
+
+ return hours;
+ }
+
+ ColumnLayout {
+ id: forecastHour
+
+ required property var modelData
+
+ Layout.fillWidth: true
+ spacing: Appearance.spacing.small
+
+ StyledText {
+ Layout.fillWidth: true
+ text: {
+ if (!forecastHour.modelData)
+ return "00 AM";
+ const hour = parseInt(forecastHour.modelData.time, 10) / 100;
+ return hour > 12 ? `${(hour - 12).toString().padStart(2, "0")} PM` : `${hour.toString().padStart(2, "0")} AM`;
+ }
+ color: Colours.palette.m3outline
+ horizontalAlignment: Text.AlignHCenter
+ font.pointSize: Appearance.font.size.larger
+ }
+
+ MaterialIcon {
+ Layout.alignment: Qt.AlignHCenter
+ text: forecastHour.modelData ? Icons.getWeatherIcon(forecastHour.modelData.weatherCode) : "cloud_alert"
+ font.pointSize: Appearance.font.size.extraLarge * 1.5
+ font.weight: 500
+ }
+
+ StyledText {
+ Layout.alignment: Qt.AlignHCenter
+ text: Config.services.useFahrenheit ? `${forecastHour.modelData?.tempF ?? 0}°F` : `${forecastHour.modelData?.tempC ?? 0}°C`
+ color: Colours.palette.m3secondary
+ font.pointSize: Appearance.font.size.larger
+ }
+ }
}
}
+
+ Timer {
+ running: true
+ triggeredOnStart: true
+ repeat: true
+ interval: 900000 // 15 minutes
+ onTriggered: Weather.reload()
+ }
}