add crypto

This commit is contained in:
zack 2025-07-22 20:21:21 -04:00
parent 90cbe489f6
commit af6a3bce3e
Signed by: zoey
GPG key ID: 81FB9FECDD6A33E2
120 changed files with 24616 additions and 462 deletions

View file

@ -0,0 +1,262 @@
import QtQuick
import QtQuick.Controls
import Qt5Compat.GraphicalEffects
import "root:/Data" as Data
// Authentication area
Column {
id: authColumn
anchors.centerIn: parent
anchors.verticalCenterOffset: 80
spacing: 20
width: 300
required property bool isVisible
required property string errorMessage
required property bool isAuthenticating
required property bool authSuccess
required property string usernameText
signal passwordEntered(string password)
// Expose password field
readonly property alias passwordField: passwordField
// Subtle slide up animation (after main slide)
transform: Translate {
id: authTransform
y: isVisible ? 0 : 50
Behavior on y {
SequentialAnimation {
PauseAnimation { duration: 600 } // Wait for main slide and time
NumberAnimation {
duration: 600
easing.type: Easing.OutBack
}
}
}
}
opacity: isVisible ? 1.0 : 0.0
Behavior on opacity {
SequentialAnimation {
PauseAnimation { duration: 700 } // Wait for time to appear
NumberAnimation {
duration: 600
easing.type: Easing.OutCubic
}
}
}
// User avatar with circular masking
Rectangle {
id: avatarContainer
width: 100
height: 100
radius: 50
anchors.horizontalCenter: parent.horizontalCenter
color: "transparent"
border.color: Data.ThemeManager.accentColor
border.width: 4
clip: true
// Scale animation for avatar
scale: isVisible ? 1.0 : 0.0
Behavior on scale {
SequentialAnimation {
PauseAnimation { duration: 1000 } // Wait for auth area to appear
NumberAnimation {
duration: 400
easing.type: Easing.OutBack
}
}
}
Image {
id: avatarImage
anchors.fill: parent
anchors.margins: 4
source: Data.Settings.avatarSource
fillMode: Image.PreserveAspectCrop
cache: false
visible: false // Hidden for masking
asynchronous: true
sourceSize.width: 92
sourceSize.height: 92
}
// Apply circular mask to avatar
OpacityMask {
anchors.fill: avatarImage
source: avatarImage
maskSource: Rectangle {
width: avatarImage.width
height: avatarImage.height
radius: 46
visible: false
}
}
// Fallback icon if avatar fails to load
Text {
anchors.centerIn: parent
text: "person"
font.family: "Material Symbols Outlined"
font.pixelSize: 48
color: Data.ThemeManager.accentColor
visible: avatarImage.status !== Image.Ready
}
}
// Username display
Text {
id: usernameDisplay
anchors.horizontalCenter: parent.horizontalCenter
font.family: "FiraCode Nerd Font"
font.pixelSize: 18
color: Data.ThemeManager.primaryText
text: usernameText
}
// Password input field
Rectangle {
width: parent.width
height: 50
radius: 25
color: Data.ThemeManager.withOpacity(Data.ThemeManager.bgLighter, 0.4)
border.color: passwordField.activeFocus ? Data.ThemeManager.accentColor : Data.ThemeManager.withOpacity(Data.ThemeManager.border, 0.6)
border.width: 2
TextInput {
id: passwordField
anchors.fill: parent
anchors.margins: 15
echoMode: TextInput.Normal
font.family: "FiraCode Nerd Font"
font.pixelSize: 16
color: "transparent" // Hide the actual text
selectionColor: Data.ThemeManager.accentColor
selectByMouse: true
focus: isVisible
onAccepted: {
if (text.length > 0) {
passwordEntered(text)
}
}
// Password mask with better spaced dots
Row {
id: passwordDotsRow
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
spacing: 8
visible: passwordField.text.length > 0
property int previousLength: 0
Repeater {
id: passwordRepeater
model: passwordField.text.length
delegate: Rectangle {
id: passwordDot
width: 8
height: 8
radius: 4
color: Data.ThemeManager.primaryText
property bool isNewDot: index >= passwordDotsRow.previousLength
// Only animate new dots, existing ones stay visible
scale: isNewDot ? 0 : 1.0
opacity: isNewDot ? 0 : 1.0
ParallelAnimation {
running: passwordDot.isNewDot
NumberAnimation {
target: passwordDot
property: "scale"
from: 0
to: 1.0
duration: 200
easing.type: Easing.OutCubic
}
NumberAnimation {
target: passwordDot
property: "opacity"
from: 0
to: 1.0
duration: 150
easing.type: Easing.OutQuad
}
}
}
}
// Track length changes to identify new dots
Connections {
target: passwordField
function onTextChanged() {
// Update previous length after a short delay to allow new dots to be marked as new
Qt.callLater(function() {
passwordDotsRow.previousLength = passwordField.text.length
})
}
}
}
// Placeholder text
Text {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
text: "Password"
font.family: passwordField.font.family
font.pixelSize: passwordField.font.pixelSize
color: Data.ThemeManager.withOpacity(Data.ThemeManager.secondaryText, 0.7)
visible: passwordField.text.length === 0 && !passwordField.activeFocus
}
}
}
// Error message
Text {
id: errorText
anchors.horizontalCenter: parent.horizontalCenter
font.family: "FiraCode Nerd Font"
font.pixelSize: 14
color: Data.ThemeManager.errorColor
text: errorMessage
visible: errorMessage !== ""
wrapMode: Text.WordWrap
width: parent.width
horizontalAlignment: Text.AlignHCenter
}
// Authentication status
Text {
id: statusText
anchors.horizontalCenter: parent.horizontalCenter
font.family: "FiraCode Nerd Font"
font.pixelSize: 14
color: authSuccess ? Data.ThemeManager.success : Data.ThemeManager.accentColorBright
text: {
if (authSuccess) return "Authentication successful!"
if (isAuthenticating) return "Authenticating..."
return ""
}
visible: isAuthenticating || authSuccess
}
// Public function to clear password
function clearPassword() {
passwordField.text = ""
passwordField.focus = true
}
// Public function to focus password field
function focusPassword() {
passwordField.focus = true
}
}

View file

@ -0,0 +1,299 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Wayland
import Quickshell.Io
import "root:/Data" as Data
import "root:/Core" as Core
// Custom lockscreen
PanelWindow {
id: lockScreen
required property var shell
property bool isLocked: false
property bool isAuthenticated: false
property string errorMessage: ""
property int failedAttempts: 0
property bool isAuthenticating: false
property bool authSuccess: false
property string usernameText: "Enter Password"
// Animation state - controlled by timer for proper timing
property bool animateIn: false
// Full screen coverage
screen: Quickshell.primaryScreen || Quickshell.screens[0]
anchors.top: true
anchors.left: true
anchors.right: true
anchors.bottom: true
color: "transparent"
// Top layer to block everything
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
WlrLayershell.namespace: "quickshell-lockscreen"
visible: isLocked
// Timer for slide-in animation - more reliable than Qt.callLater
Timer {
id: slideInTimer
interval: 100 // Short delay to ensure window is fully rendered
running: false
onTriggered: {
console.log("slideInTimer triggered, setting animateIn = true")
animateIn = true
}
}
// Timer for slide-out animation before hiding window
Timer {
id: slideOutTimer
interval: 1000 // Wait for slide animation to complete
running: false
onTriggered: {
isLocked = false
authArea.clearPassword()
errorMessage = ""
failedAttempts = 0
authSuccess = false
}
}
// Timer to show success message before unlocking
Timer {
id: successTimer
interval: 1200 // Show success message for 1.2 seconds
running: false
onTriggered: {
unlock()
}
}
// Reset animation state when window becomes invisible
onVisibleChanged: {
if (!visible) {
animateIn = false
authSuccess = false
slideInTimer.stop()
slideOutTimer.stop()
successTimer.stop()
}
}
// Background component
LockscreenBackground {
id: background
isVisible: lockScreen.visible
}
// Main lockscreen content with slide-from-top animation
Item {
id: mainContent
anchors.fill: parent
focus: true // Enable focus for keyboard handling
// Dramatic slide animation - starts off-screen, slides down when animateIn is true
transform: Translate {
id: mainTransform
y: lockScreen.animateIn ? 0 : -lockScreen.height
Behavior on y {
NumberAnimation {
duration: 800
easing.type: Easing.OutCubic
}
}
}
// Scale animation for extra drama
scale: lockScreen.animateIn ? 1.0 : 0.98
Behavior on scale {
NumberAnimation {
duration: 800
easing.type: Easing.OutCubic
}
}
// Keyboard shortcuts
Keys.onPressed: function(event) {
if (event.key === Qt.Key_Escape) {
authArea.clearPassword()
errorMessage = ""
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
if (authArea.passwordField.text.length > 0) {
authenticate(authArea.passwordField.text)
}
}
}
// Authentication area component
AuthenticationArea {
id: authArea
isVisible: lockScreen.animateIn
errorMessage: lockScreen.errorMessage
isAuthenticating: lockScreen.isAuthenticating
authSuccess: lockScreen.authSuccess
usernameText: lockScreen.usernameText
onPasswordEntered: function(password) {
authenticate(password)
}
}
// Power buttons component
PowerButtons {
id: powerButtons
isVisible: lockScreen.animateIn
onRebootRequested: rebootProcess.running = true
onShutdownRequested: shutdownProcess.running = true
}
}
// Authentication process using proper PAM authentication
Process {
id: authProcess
property string password: ""
command: ["sh", "-c", "echo '" + password.replace(/'/g, "'\"'\"'") + "' | sudo -S -k true"]
running: false
onExited: function(exitCode) {
isAuthenticating = false
if (exitCode === 0) {
// Authentication successful
isAuthenticated = true
errorMessage = ""
authSuccess = true
// Show success message for a brief moment before unlocking
successTimer.start()
} else {
// Authentication failed
failedAttempts++
errorMessage = failedAttempts === 1 ? "Incorrect password" : `Incorrect password (${failedAttempts} attempts)`
authArea.clearPassword()
// Add delay for failed attempts
if (failedAttempts >= 3) {
lockoutTimer.start()
}
}
}
}
// Lockout timer for failed attempts
Timer {
id: lockoutTimer
interval: 30000 // 30 second lockout
onTriggered: {
errorMessage = ""
authArea.passwordField.enabled = true
authArea.focusPassword()
}
}
// Reboot process
Process {
id: rebootProcess
command: ["systemctl", "reboot"]
running: false
onExited: function(exitCode) { console.log("Reboot process completed with exit code:", exitCode) }
}
// Shutdown process
Process {
id: shutdownProcess
command: ["systemctl", "poweroff"]
running: false
onExited: function(exitCode) { console.log("Shutdown process completed with exit code:", exitCode) }
}
// Get current username
Process {
id: usernameProcess
command: ["whoami"]
running: lockScreen.isLocked
stdout: SplitParser {
onRead: function(data) {
const username = data.trim()
if (username) {
usernameText = username.charAt(0).toUpperCase() + username.slice(1)
}
}
}
}
// Public functions
function lock() {
console.log("Lockscreen.lock() called")
// Reset animation state FIRST, before making window visible
animateIn = false
isLocked = true
isAuthenticated = false
authSuccess = false
errorMessage = ""
failedAttempts = 0
authArea.clearPassword()
usernameProcess.running = true
// Trigger slide animation after a short delay
console.log("Starting slideInTimer")
slideInTimer.start()
}
function unlock() {
console.log("Lockscreen.unlock() called")
// Start slide-out animation first
animateIn = false
// Use timer for reliable timing before completely hiding
slideOutTimer.start()
}
function authenticate(password) {
if (isAuthenticating || password.length === 0) return
console.log("Authenticating...")
isAuthenticating = true
authSuccess = false
errorMessage = ""
// Use sudo authentication
authProcess.password = password
authProcess.running = true
}
// Focus management when locked state changes
onIsLockedChanged: {
console.log("isLocked changed to:", isLocked)
if (isLocked) {
mainContent.focus = true
authArea.focusPassword()
}
}
// Clean up processes on destruction
Component.onDestruction: {
if (authProcess.running) authProcess.running = false
if (rebootProcess.running) rebootProcess.running = false
if (shutdownProcess.running) shutdownProcess.running = false
if (usernameProcess.running) usernameProcess.running = false
if (lockoutTimer.running) lockoutTimer.running = false
if (slideInTimer.running) slideInTimer.running = false
if (slideOutTimer.running) slideOutTimer.running = false
if (successTimer.running) successTimer.running = false
}
}

View file

@ -0,0 +1,59 @@
import QtQuick
import Qt5Compat.GraphicalEffects
import "root:/Data" as Data
// Background with wallpaper
Rectangle {
id: backgroundContainer
anchors.fill: parent
color: Data.ThemeManager.bgColor
required property bool isVisible
// Fade-in animation for the whole background
opacity: isVisible ? 1.0 : 0.0
Behavior on opacity {
NumberAnimation {
duration: 500
easing.type: Easing.OutCubic
}
}
// Wallpaper background
Image {
id: wallpaperImage
anchors.fill: parent
source: Data.WallpaperManager.currentWallpaper ? "file://" + Data.WallpaperManager.currentWallpaper : ""
fillMode: Image.PreserveAspectCrop
asynchronous: true
cache: false
}
// Dark overlay
Rectangle {
anchors.fill: parent
color: Data.ThemeManager.withOpacity(Data.ThemeManager.bgColor, 0.8)
}
// Blur effect overlay
GaussianBlur {
anchors.fill: wallpaperImage
source: wallpaperImage
radius: 32
samples: 65
// Blur animation - starts less blurred and increases
Behavior on radius {
NumberAnimation {
duration: 1200
easing.type: Easing.OutCubic
}
}
Component.onCompleted: {
if (isVisible) {
radius = 32
}
}
}
}

View file

@ -0,0 +1,87 @@
import QtQuick
import "root:/Data" as Data
// Reboot and shutdown buttons positioned at bottom right
Row {
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: 40
spacing: 16
required property bool isVisible
signal rebootRequested()
signal shutdownRequested()
// Fade in with delay
opacity: isVisible ? 1.0 : 0.0
Behavior on opacity {
SequentialAnimation {
PauseAnimation { duration: 900 } // Wait for most elements to appear
NumberAnimation {
duration: 500
easing.type: Easing.OutCubic
}
}
}
// Reboot button
Rectangle {
width: 45
height: 45
radius: 22
color: rebootMouseArea.containsMouse ? Data.ThemeManager.withOpacity(Data.ThemeManager.secondaryText, 0.3) : Data.ThemeManager.withOpacity(Data.ThemeManager.secondaryText, 0.2)
border.color: Data.ThemeManager.withOpacity(Data.ThemeManager.secondaryText, 0.6)
border.width: 1
Text {
anchors.centerIn: parent
text: "restart_alt"
font.family: "Material Symbols Outlined"
font.pixelSize: 20
color: Data.ThemeManager.secondaryText
}
MouseArea {
id: rebootMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: rebootRequested()
}
Behavior on color {
ColorAnimation { duration: 200 }
}
}
// Shutdown button
Rectangle {
width: 45
height: 45
radius: 22
color: shutdownMouseArea.containsMouse ? Data.ThemeManager.withOpacity(Data.ThemeManager.secondaryText, 0.3) : Data.ThemeManager.withOpacity(Data.ThemeManager.secondaryText, 0.2)
border.color: Data.ThemeManager.withOpacity(Data.ThemeManager.secondaryText, 0.6)
border.width: 1
Text {
anchors.centerIn: parent
text: "power_settings_new"
font.family: "Material Symbols Outlined"
font.pixelSize: 18
color: Data.ThemeManager.secondaryText
}
MouseArea {
id: shutdownMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: shutdownRequested()
}
Behavior on color {
ColorAnimation { duration: 200 }
}
}
}

View file

@ -0,0 +1,72 @@
import QtQuick
import "root:/Data" as Data
// Time and date display
Column {
id: timeColumn
anchors.top: parent.top
anchors.left: parent.left
anchors.topMargin: 60
anchors.leftMargin: 60
spacing: 16
required property bool isVisible
// Subtle slide-left animation (after main slide)
transform: Translate {
id: timeTransform
x: isVisible ? 0 : -100
Behavior on x {
SequentialAnimation {
PauseAnimation { duration: 400 } // Wait for main slide to be visible
NumberAnimation {
duration: 500
easing.type: Easing.OutQuart
}
}
}
}
opacity: isVisible ? 1.0 : 0.0
Behavior on opacity {
SequentialAnimation {
PauseAnimation { duration: 500 } // Wait for main slide
NumberAnimation {
duration: 500
easing.type: Easing.OutCubic
}
}
}
// Current time
Text {
id: timeText
font.family: "Roboto"
font.pixelSize: 84
font.weight: Font.ExtraLight
color: Data.ThemeManager.brightText
text: Qt.formatTime(new Date(), "hh:mm")
}
// Current date
Text {
id: dateText
font.family: "Roboto"
font.pixelSize: 28
font.weight: Font.Light
color: Data.ThemeManager.secondaryText
text: Qt.formatDate(new Date(), "dddd, MMMM d, yyyy")
}
// Time update timer
Timer {
id: timeTimer
interval: 1000
running: isVisible
repeat: true
onTriggered: {
timeText.text = Qt.formatTime(new Date(), "hh:mm")
dateText.text = Qt.formatDate(new Date(), "dddd, MMMM d, yyyy")
}
}
}