last 7 months of qs changes

This commit is contained in:
outfoxxed 2025-01-06 00:13:19 -08:00
parent 2c64563ade
commit 4b90113a54
Signed by: outfoxxed
GPG key ID: 4C88A185FB89301E
103 changed files with 3467 additions and 1415 deletions

View file

@ -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;
}

View file

@ -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();
}
}
}

View file

@ -44,6 +44,8 @@ Item {
anchors.margins: 15
source: root.icon
sourceSize.width: width
sourceSize.height: height
}
}
}

View file

@ -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
}
}

View file

@ -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;
}
}

View 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;
}
});
}
}

View file

@ -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();
}
}
}

View file

@ -0,0 +1 @@
auth required /run/current-system/sw/lib/security/pam_fprintd.so

View file

@ -0,0 +1 @@
auth required pam_unix.so