last 7 months of qs changes
This commit is contained in:
parent
2c64563ade
commit
4b90113a54
103 changed files with 3467 additions and 1415 deletions
|
|
@ -3,8 +3,10 @@ pragma Singleton
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Services.Pam
|
||||
import ".."
|
||||
import "../.."
|
||||
|
||||
|
|
@ -61,18 +63,15 @@ Singleton {
|
|||
root.oldWorkspaces = ({});
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
name: "lock"
|
||||
onPressed: {
|
||||
if (root.locked) root.locked = false;
|
||||
else root.locked = true;
|
||||
}
|
||||
IpcHandler {
|
||||
target: "lockscreen"
|
||||
function lock(): void { root.locked = true; }
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: lockContextLoader
|
||||
|
||||
LockContext {
|
||||
SessionLockContext {
|
||||
onUnlocked: root.locked = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -82,7 +81,7 @@ Singleton {
|
|||
|
||||
onSecureChanged: {
|
||||
if (secure) {
|
||||
Qt.callLater(() => root.workspaceLockAnimation());
|
||||
root.workspaceLockAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -108,7 +107,7 @@ Singleton {
|
|||
|
||||
LockContent {
|
||||
id: lockContent
|
||||
context: lockContextLoader.item;
|
||||
state: lockContextLoader.item.state;
|
||||
|
||||
visible: false
|
||||
width: lockSurface.width
|
||||
|
|
@ -128,7 +127,6 @@ Singleton {
|
|||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
lockContent.y = -lockSurface.height
|
||||
console.log(`y ${lockContent.y}`)
|
||||
lockContent.visible = true;
|
||||
lockAnim.running = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Services.Greetd
|
||||
|
||||
Scope {
|
||||
id: root
|
||||
signal launch();
|
||||
|
||||
property LockState state: LockState {
|
||||
onTryPasswordUnlock: {
|
||||
this.isUnlocking = true;
|
||||
Greetd.createSession("admin");
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Greetd
|
||||
|
||||
function onAuthMessage(message: string, error: bool, responseRequired: bool, echoResponse: bool) {
|
||||
if (responseRequired) {
|
||||
Greetd.respond(root.state.currentText);
|
||||
} // else ignore - only supporting passwords
|
||||
}
|
||||
|
||||
function onAuthFailure() {
|
||||
root.state.currentText = "";
|
||||
root.state.error = "Invalid password";
|
||||
root.state.failed = true;
|
||||
root.state.isUnlocking = false;
|
||||
}
|
||||
|
||||
function onReadyToLaunch() {
|
||||
root.state.isUnlocking = false;
|
||||
root.launch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -44,6 +44,8 @@ Item {
|
|||
anchors.margins: 15
|
||||
|
||||
source: root.icon
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,113 +1,202 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import ".."
|
||||
|
||||
Item {
|
||||
id: root
|
||||
required property LockContext context;
|
||||
required property LockState state;
|
||||
|
||||
property real focusAnim: focusAnimInternal * 0.001
|
||||
property int focusAnimInternal: Window.active ? 1000 : 0
|
||||
Behavior on focusAnimInternal { SmoothedAnimation { velocity: 5000 } }
|
||||
|
||||
Rectangle {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
y: parent.height / 2 + textBox.height
|
||||
id: sep
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
|
||||
implicitHeight: 6
|
||||
implicitWidth: 800
|
||||
radius: height / 2
|
||||
color: ShellGlobals.colors.widget
|
||||
}
|
||||
property real startMoveX: 0
|
||||
property real startMoveY: 0
|
||||
|
||||
ColumnLayout {
|
||||
implicitWidth: sep.implicitWidth
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: sep.top
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
id: timeText
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
font {
|
||||
pointSize: 120
|
||||
hintingPreference: Font.PreferFullHinting
|
||||
family: "Noto Sans"
|
||||
}
|
||||
|
||||
color: "white"
|
||||
renderType: Text.NativeRendering
|
||||
|
||||
text: {
|
||||
const hours = ShellGlobals.time.getHours().toString().padStart(2, '0');
|
||||
const minutes = ShellGlobals.time.getMinutes().toString().padStart(2, '0');
|
||||
return `${hours}:${minutes}`;
|
||||
// prevents wakeups from bumping the mouse
|
||||
onPositionChanged: event => {
|
||||
if (root.state.fadedOut) {
|
||||
if (root.state.mouseMoved()) {
|
||||
const xOffset = Math.abs(event.x - startMoveX);
|
||||
const yOffset = Math.abs(event.y - startMoveY);
|
||||
const distanceSq = (xOffset * xOffset) + (yOffset * yOffset);
|
||||
if (distanceSq > (100 * 100)) root.state.fadeIn();
|
||||
} else {
|
||||
startMoveX = event.x;
|
||||
startMoveY = event.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
implicitHeight: childrenRect.height * focusAnim
|
||||
implicitWidth: sep.implicitWidth
|
||||
clip: true
|
||||
id: content
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
y: root.state.fadeOutMul * (height / 2 + childrenRect.height)
|
||||
|
||||
TextInput {
|
||||
id: textBox
|
||||
focus: true
|
||||
width: parent.width
|
||||
Rectangle {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
y: parent.height / 2 + textBox.height
|
||||
id: sep
|
||||
|
||||
color: enabled ?
|
||||
root.context.failed ? "#ffa0a0" : "white"
|
||||
: "#80ffffff";
|
||||
implicitHeight: 6
|
||||
implicitWidth: 800
|
||||
radius: height / 2
|
||||
color: ShellGlobals.colors.widget
|
||||
}
|
||||
|
||||
font.pointSize: 24
|
||||
horizontalAlignment: TextInput.AlignHCenter
|
||||
echoMode: TextInput.Password
|
||||
inputMethodHints: Qt.ImhSensitiveData
|
||||
ColumnLayout {
|
||||
implicitWidth: sep.implicitWidth
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: sep.top
|
||||
spacing: 0
|
||||
|
||||
onTextChanged: root.context.currentText = text;
|
||||
SystemClock {
|
||||
id: clock
|
||||
precision: SystemClock.Minutes
|
||||
}
|
||||
|
||||
Window.onActiveChanged: {
|
||||
if (Window.active) {
|
||||
text = root.context.currentText;
|
||||
Text {
|
||||
id: timeText
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
font {
|
||||
pointSize: 120
|
||||
hintingPreference: Font.PreferFullHinting
|
||||
family: "Noto Sans"
|
||||
}
|
||||
|
||||
color: "white"
|
||||
renderType: Text.NativeRendering
|
||||
|
||||
text: {
|
||||
const hours = clock.hours.toString().padStart(2, '0');
|
||||
const minutes = clock.minutes.toString().padStart(2, '0');
|
||||
return `${hours}:${minutes}`;
|
||||
}
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
if (text != "") root.context.tryUnlock();
|
||||
}
|
||||
Item {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
implicitHeight: textBox.height * focusAnim
|
||||
implicitWidth: sep.implicitWidth
|
||||
clip: true
|
||||
|
||||
enabled: !root.context.isUnlocking;
|
||||
TextInput {
|
||||
id: textBox
|
||||
focus: true
|
||||
width: parent.width
|
||||
|
||||
color: enabled ?
|
||||
root.state.failed ? "#ffa0a0" : "white"
|
||||
: "#80ffffff";
|
||||
|
||||
font.pointSize: 24
|
||||
horizontalAlignment: TextInput.AlignHCenter
|
||||
echoMode: TextInput.Password
|
||||
inputMethodHints: Qt.ImhSensitiveData
|
||||
|
||||
cursorVisible: text != ""
|
||||
onCursorVisibleChanged: cursorVisible = text != ""
|
||||
|
||||
onTextChanged: {
|
||||
root.state.currentText = text;
|
||||
cursorVisible = text != ""
|
||||
}
|
||||
|
||||
Window.onActiveChanged: {
|
||||
if (Window.active) {
|
||||
text = root.state.currentText;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.state
|
||||
|
||||
function onCurrentTextChanged() {
|
||||
textBox.text = root.state.currentText;
|
||||
}
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
if (text != "") root.state.tryPasswordUnlock();
|
||||
}
|
||||
|
||||
enabled: !root.state.isUnlocking;
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.fill: textBox
|
||||
font: textBox.font
|
||||
color: root.state.failed ? "#ffa0a0" : "#80ffffff";
|
||||
horizontalAlignment: TextInput.AlignHCenter
|
||||
visible: !textBox.cursorVisible
|
||||
text: root.state.failed ? root.state.error
|
||||
: root.state.fprintAvailable ? "Touch sensor or enter password" : "Enter password";
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillHeight: true
|
||||
implicitWidth: height
|
||||
color: "transparent"
|
||||
visible: root.state.fprintAvailable
|
||||
|
||||
anchors {
|
||||
right: textBox.right
|
||||
top: textBox.top
|
||||
bottom: textBox.bottom
|
||||
}
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 5
|
||||
source: "root:icons/fingerprint.svg"
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: sep.bottom
|
||||
implicitHeight: (75 + 30) * focusAnim
|
||||
implicitWidth: sep.implicitWidth
|
||||
clip: true
|
||||
|
||||
RowLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.topMargin: 50
|
||||
spacing: 0
|
||||
|
||||
LockButton {
|
||||
icon: "root:icons/monitor.svg"
|
||||
onClicked: root.state.fadeOut();
|
||||
}
|
||||
|
||||
LockButton {
|
||||
icon: "root:icons/pause.svg"
|
||||
show: root.state.mediaPlaying;
|
||||
onClicked: root.state.pauseMedia();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: sep.bottom
|
||||
implicitHeight: (childrenRect.height + 30) * focusAnim
|
||||
implicitWidth: sep.implicitWidth
|
||||
clip: true
|
||||
|
||||
RowLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.topMargin: 50
|
||||
spacing: 0
|
||||
|
||||
LockButton {
|
||||
icon: "root:icons/monitor.svg"
|
||||
onClicked: root.context.dpms();
|
||||
}
|
||||
|
||||
LockButton {
|
||||
icon: "root:icons/pause.svg"
|
||||
show: context.mediaPlaying;
|
||||
onClicked: root.context.pauseMedia();
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
id: darkenOverlay
|
||||
anchors.fill: parent
|
||||
color: "black"
|
||||
opacity: root.state.fadeOutMul
|
||||
visible: opacity != 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,57 +0,0 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Services.Mpris
|
||||
|
||||
Scope {
|
||||
id: root
|
||||
signal unlocked();
|
||||
property string currentText: "";
|
||||
readonly property alias isUnlocking: pamtester.running;
|
||||
property bool failed: false;
|
||||
|
||||
onCurrentTextChanged: failed = false;
|
||||
|
||||
readonly property bool mediaPlaying: Mpris.players.values.some(player => {
|
||||
return player.playbackState === MprisPlaybackState.Playing && player.canPause;
|
||||
});
|
||||
|
||||
function pauseMedia() {
|
||||
Mpris.players.values.forEach(player => {
|
||||
if (player.playbackState === MprisPlaybackState.Playing && player.canPause) {
|
||||
player.playbackState = MprisPlaybackState.Paused;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function dpms() {
|
||||
Hyprland.dispatch(`dpms`);
|
||||
}
|
||||
|
||||
Process {
|
||||
id: pamtester
|
||||
property bool failed: true
|
||||
|
||||
command: ["pamtester", "login", Quickshell.env("USER"), "authenticate"]
|
||||
|
||||
onStarted: this.write(`${currentText}\n`)
|
||||
|
||||
stdout: SplitParser {
|
||||
// fails go to stderr
|
||||
onRead: pamtester.failed = false
|
||||
}
|
||||
|
||||
onExited: {
|
||||
if (failed) {
|
||||
root.failed = true;
|
||||
} else {
|
||||
root.unlocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function tryUnlock() {
|
||||
pamtester.running = true;
|
||||
}
|
||||
}
|
||||
70
modules/user/modules/quickshell/shell/lock/LockState.qml
Normal file
70
modules/user/modules/quickshell/shell/lock/LockState.qml
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Services.Mpris
|
||||
|
||||
Scope {
|
||||
signal tryPasswordUnlock();
|
||||
property string currentText: "";
|
||||
property string error: "";
|
||||
property bool isUnlocking: false;
|
||||
property bool failed: false;
|
||||
property bool fprintAvailable: false;
|
||||
|
||||
property bool fadedOut: false
|
||||
property real fadeOutMul: 0
|
||||
|
||||
NumberAnimation on fadeOutMul {
|
||||
id: fadeAnim
|
||||
duration: 600
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: [0.0, 0.75, 0.15, 1.0, 1.0, 1.0]
|
||||
|
||||
onStopped: {
|
||||
if (fadedOut) Hyprland.dispatch("dpms off");
|
||||
}
|
||||
}
|
||||
|
||||
onCurrentTextChanged: {
|
||||
failed = false;
|
||||
error = "";
|
||||
|
||||
if (fadedOut) {
|
||||
fadeIn();
|
||||
}
|
||||
}
|
||||
|
||||
function fadeOut() {
|
||||
if (fadedOut) return;
|
||||
fadedOut = true;
|
||||
fadeAnim.to = 1;
|
||||
fadeAnim.restart();
|
||||
}
|
||||
|
||||
function fadeIn() {
|
||||
if (!fadedOut) return;
|
||||
Hyprland.dispatch("dpms on");
|
||||
fadedOut = false;
|
||||
fadeAnim.to = 0;
|
||||
fadeAnim.restart();
|
||||
}
|
||||
|
||||
ElapsedTimer { id: mouseTimer }
|
||||
|
||||
// returns if mouse move should be continued, false should restart
|
||||
function mouseMoved(): bool {
|
||||
return mouseTimer.restart() < 0.2;
|
||||
}
|
||||
|
||||
readonly property bool mediaPlaying: Mpris.players.values.some(player => {
|
||||
return player.playbackState === MprisPlaybackState.Playing && player.canPause;
|
||||
});
|
||||
|
||||
function pauseMedia() {
|
||||
Mpris.players.values.forEach(player => {
|
||||
if (player.playbackState === MprisPlaybackState.Playing && player.canPause) {
|
||||
player.playbackState = MprisPlaybackState.Paused;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import Quickshell
|
||||
import Quickshell.Services.Pam
|
||||
|
||||
Scope {
|
||||
id: root
|
||||
signal unlocked();
|
||||
|
||||
property LockState state: LockState {
|
||||
onTryPasswordUnlock: {
|
||||
root.state.isUnlocking = true;
|
||||
pam.start();
|
||||
}
|
||||
}
|
||||
|
||||
PamContext {
|
||||
id: pam
|
||||
configDirectory: "pam"
|
||||
config: "password.conf"
|
||||
|
||||
onPamMessage: {
|
||||
if (this.responseRequired) {
|
||||
this.respond(root.state.currentText);
|
||||
} else if (this.messageIsError) {
|
||||
root.state.currentText = "";
|
||||
root.state.failed = true;
|
||||
root.state.error = this.message;
|
||||
} // else ignore
|
||||
}
|
||||
|
||||
onCompleted: status => {
|
||||
const success = status == PamResult.Success;
|
||||
|
||||
if (!success) {
|
||||
root.state.currentText = "";
|
||||
root.state.error = "Invalid password";
|
||||
}
|
||||
|
||||
root.state.failed = !success;
|
||||
root.state.isUnlocking = false;
|
||||
|
||||
if (success) root.unlocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
auth required /run/current-system/sw/lib/security/pam_fprintd.so
|
||||
|
|
@ -0,0 +1 @@
|
|||
auth required pam_unix.so
|
||||
Loading…
Add table
Add a link
Reference in a new issue