Sort applauncher entries alphabetical Use global fontFamily and font size (Theme.qml) Tons of other small fixes
454 lines
No EOL
16 KiB
QML
454 lines
No EOL
16 KiB
QML
import QtQuick
|
|
import QtQuick.Layouts
|
|
import QtQuick.Controls
|
|
import QtQuick.Effects
|
|
import Qt5Compat.GraphicalEffects
|
|
import Quickshell.Wayland
|
|
import Quickshell
|
|
import Quickshell.Services.Pam
|
|
import Quickshell.Io
|
|
import qs.Settings
|
|
import qs.Helpers
|
|
import "../Helpers/Weather.js" as WeatherHelper
|
|
|
|
WlSessionLock {
|
|
id: lock
|
|
|
|
property string errorMessage: ""
|
|
property bool authenticating: false
|
|
property string password: ""
|
|
property bool pamAvailable: typeof PamContext !== "undefined"
|
|
property string weatherCity: Settings.weatherCity
|
|
property var weatherData: null
|
|
property string weatherError: ""
|
|
property string weatherInfo: ""
|
|
property string weatherIcon: ""
|
|
property double currentTemp: 0
|
|
locked: false
|
|
|
|
// On component completed, fetch weather data
|
|
Component.onCompleted: {
|
|
fetchWeatherData()
|
|
}
|
|
|
|
// Weather fetching function
|
|
function fetchWeatherData() {
|
|
WeatherHelper.fetchCityWeather(weatherCity,
|
|
function(result) {
|
|
weatherData = result.weather;
|
|
weatherError = "";
|
|
},
|
|
function(err) {
|
|
weatherError = err;
|
|
}
|
|
);
|
|
}
|
|
|
|
function materialSymbolForCode(code) {
|
|
if (code === 0) return "sunny";
|
|
if (code === 1 || code === 2) return "partly_cloudy_day";
|
|
if (code === 3) return "cloud";
|
|
if (code >= 45 && code <= 48) return "foggy";
|
|
if (code >= 51 && code <= 67) return "rainy";
|
|
if (code >= 71 && code <= 77) return "weather_snowy";
|
|
if (code >= 80 && code <= 82) return "rainy";
|
|
if (code >= 95 && code <= 99) return "thunderstorm";
|
|
return "cloud";
|
|
}
|
|
|
|
// Authentication function
|
|
function unlockAttempt() {
|
|
console.log("Unlock attempt started");
|
|
if (!pamAvailable) {
|
|
lock.errorMessage = "PAM authentication not available.";
|
|
console.log("PAM not available");
|
|
return;
|
|
}
|
|
if (!lock.password) {
|
|
lock.errorMessage = "Password required.";
|
|
console.log("No password entered");
|
|
return;
|
|
}
|
|
console.log("Starting PAM authentication...");
|
|
lock.authenticating = true;
|
|
lock.errorMessage = "";
|
|
|
|
console.log("[LockScreen] About to create PAM context with userName:", Quickshell.env("USER"))
|
|
var pam = Qt.createQmlObject('import Quickshell.Services.Pam; PamContext { config: "login"; user: "' + Quickshell.env("USER") + '" }', lock);
|
|
console.log("PamContext created", pam);
|
|
|
|
pam.onCompleted.connect(function(result) {
|
|
console.log("PAM completed with result:", result);
|
|
lock.authenticating = false;
|
|
if (result === PamResult.Success) {
|
|
console.log("Authentication successful, unlocking...");
|
|
lock.locked = false;
|
|
lock.password = "";
|
|
lock.errorMessage = "";
|
|
} else {
|
|
console.log("Authentication failed");
|
|
lock.errorMessage = "Authentication failed.";
|
|
lock.password = "";
|
|
}
|
|
pam.destroy();
|
|
});
|
|
|
|
pam.onError.connect(function(error) {
|
|
console.log("PAM error:", error);
|
|
lock.authenticating = false;
|
|
lock.errorMessage = pam.message || "Authentication error.";
|
|
lock.password = "";
|
|
pam.destroy();
|
|
});
|
|
|
|
pam.onPamMessage.connect(function() {
|
|
console.log("PAM message:", pam.message, "isError:", pam.messageIsError);
|
|
if (pam.messageIsError) {
|
|
lock.errorMessage = pam.message;
|
|
}
|
|
});
|
|
|
|
pam.onResponseRequiredChanged.connect(function() {
|
|
console.log("PAM response required:", pam.responseRequired);
|
|
if (pam.responseRequired && lock.authenticating) {
|
|
console.log("Responding to PAM with password");
|
|
pam.respond(lock.password);
|
|
}
|
|
});
|
|
|
|
var started = pam.start();
|
|
console.log("PAM start result:", started);
|
|
}
|
|
|
|
// Lock surface
|
|
WlSessionLockSurface {
|
|
// Blurred wallpaper background
|
|
Image {
|
|
id: lockBgImage
|
|
anchors.fill: parent
|
|
fillMode: Image.PreserveAspectCrop
|
|
source: Settings.currentWallpaper !== "" ? Settings.currentWallpaper : "/home/lysec/nixos/assets/wallpapers/lantern.png"
|
|
cache: true
|
|
smooth: true
|
|
sourceSize.width: 2560
|
|
sourceSize.height: 1440
|
|
visible: true // Show the original for FastBlur input
|
|
}
|
|
FastBlur {
|
|
anchors.fill: parent
|
|
source: lockBgImage
|
|
radius: 48 // Adjust blur strength as needed
|
|
transparentBorder: true
|
|
}
|
|
// Main content container (moved up, Rectangle removed)
|
|
ColumnLayout {
|
|
anchors.centerIn: parent
|
|
spacing: 30
|
|
width: Math.min(parent.width * 0.8, 400)
|
|
|
|
// User avatar/icon
|
|
Rectangle {
|
|
Layout.alignment: Qt.AlignHCenter
|
|
width: 80
|
|
height: 80
|
|
radius: 40
|
|
color: Theme.accentPrimary
|
|
|
|
Image {
|
|
id: avatarImage
|
|
anchors.fill: parent
|
|
anchors.margins: 4
|
|
source: Settings.profileImage
|
|
fillMode: Image.PreserveAspectCrop
|
|
visible: false // Only show the masked version
|
|
asynchronous: true
|
|
}
|
|
OpacityMask {
|
|
anchors.fill: avatarImage
|
|
source: avatarImage
|
|
maskSource: Rectangle {
|
|
width: avatarImage.width
|
|
height: avatarImage.height
|
|
radius: avatarImage.width / 2
|
|
visible: false
|
|
}
|
|
visible: Settings.profileImage !== ""
|
|
}
|
|
// Fallback icon
|
|
Text {
|
|
anchors.centerIn: parent
|
|
text: "person"
|
|
font.family: "Material Symbols Outlined"
|
|
font.pixelSize: 32
|
|
color: Theme.onAccent
|
|
visible: Settings.profileImage === ""
|
|
}
|
|
// Glow effect
|
|
layer.enabled: true
|
|
layer.effect: Glow {
|
|
color: Theme.accentPrimary
|
|
radius: 8
|
|
samples: 16
|
|
}
|
|
}
|
|
|
|
// Username
|
|
Text {
|
|
Layout.alignment: Qt.AlignHCenter
|
|
text: Quickshell.env("USER")
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: 24
|
|
font.weight: Font.Medium
|
|
color: Theme.textPrimary
|
|
}
|
|
|
|
// Password input container
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
height: 50
|
|
radius: 25
|
|
color: Theme.surface
|
|
opacity: 0.3
|
|
border.color: passwordInput.activeFocus ? Theme.accentPrimary : Theme.outline
|
|
border.width: 2
|
|
|
|
TextInput {
|
|
id: passwordInput
|
|
anchors.fill: parent
|
|
anchors.margins: 15
|
|
verticalAlignment: TextInput.AlignVCenter
|
|
horizontalAlignment: TextInput.AlignHCenter
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: 16
|
|
color: Theme.textPrimary
|
|
echoMode: TextInput.Password
|
|
passwordCharacter: "●"
|
|
passwordMaskDelay: 0
|
|
|
|
text: lock.password
|
|
onTextChanged: lock.password = text
|
|
|
|
// Placeholder text
|
|
Text {
|
|
anchors.centerIn: parent
|
|
text: "Enter password..."
|
|
color: Theme.textSecondary
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: 16
|
|
visible: !passwordInput.text && !passwordInput.activeFocus
|
|
}
|
|
|
|
// Handle Enter key
|
|
Keys.onPressed: function(event) {
|
|
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
|
lock.unlockAttempt()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Error message
|
|
Text {
|
|
Layout.alignment: Qt.AlignHCenter
|
|
text: lock.errorMessage
|
|
color: Theme.error
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: 14
|
|
visible: lock.errorMessage !== ""
|
|
opacity: lock.errorMessage !== "" ? 1 : 0
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation { duration: 200 }
|
|
}
|
|
}
|
|
|
|
// Unlock button
|
|
Rectangle {
|
|
Layout.alignment: Qt.AlignHCenter
|
|
width: 120
|
|
height: 44
|
|
radius: 22
|
|
color: unlockButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
|
border.color: Theme.accentPrimary
|
|
border.width: 2
|
|
opacity: lock.authenticating ? 0.5 : 0.8
|
|
enabled: !lock.authenticating
|
|
|
|
Text {
|
|
anchors.centerIn: parent
|
|
text: lock.authenticating ? "Authenticating..." : "Unlock"
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: 16
|
|
font.bold: true
|
|
color: unlockButtonArea.containsMouse ? Theme.onAccent : Theme.accentPrimary
|
|
}
|
|
|
|
MouseArea {
|
|
id: unlockButtonArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
onClicked: {
|
|
if (!lock.authenticating) {
|
|
lock.unlockAttempt()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Top-center info panel (clock + weather)
|
|
ColumnLayout {
|
|
anchors.top: parent.top
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.topMargin: 40
|
|
spacing: 8
|
|
// Clock
|
|
Text {
|
|
id: timeText
|
|
text: Qt.formatDateTime(new Date(), "HH:mm")
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: 48
|
|
font.bold: true
|
|
color: Theme.textPrimary
|
|
horizontalAlignment: Text.AlignHCenter
|
|
Layout.alignment: Qt.AlignHCenter
|
|
}
|
|
Text {
|
|
id: dateText
|
|
text: Qt.formatDateTime(new Date(), "dddd, MMMM d")
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: 16
|
|
color: Theme.textSecondary
|
|
opacity: 0.8
|
|
horizontalAlignment: Text.AlignHCenter
|
|
Layout.alignment: Qt.AlignHCenter
|
|
}
|
|
// Weather info (centered, no city)
|
|
RowLayout {
|
|
spacing: 6
|
|
Layout.alignment: Qt.AlignHCenter
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
visible: weatherData && weatherData.current_weather
|
|
Text {
|
|
text: weatherData && weatherData.current_weather ? materialSymbolForCode(weatherData.current_weather.weathercode) : "cloud"
|
|
font.family: "Material Symbols Outlined"
|
|
font.pixelSize: 28
|
|
color: Theme.accentPrimary
|
|
verticalAlignment: Text.AlignVCenter
|
|
}
|
|
Text {
|
|
text: weatherData && weatherData.current_weather ? (Settings.useFahrenheit ? `${Math.round(weatherData.current_weather.temperature * 9/5 + 32)}°F` : `${Math.round(weatherData.current_weather.temperature)}°C`) : (Settings.useFahrenheit ? "--°F" : "--°C")
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: 18
|
|
color: Theme.textSecondary
|
|
verticalAlignment: Text.AlignVCenter
|
|
}
|
|
}
|
|
// Weather error
|
|
Text {
|
|
text: weatherError
|
|
color: Theme.error
|
|
visible: weatherError !== ""
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: 10
|
|
horizontalAlignment: Text.AlignHCenter
|
|
Layout.alignment: Qt.AlignHCenter
|
|
}
|
|
}
|
|
|
|
// Update clock every second
|
|
Timer {
|
|
interval: 1000
|
|
running: true
|
|
repeat: true
|
|
onTriggered: {
|
|
timeText.text = Qt.formatDateTime(new Date(), "HH:mm")
|
|
dateText.text = Qt.formatDateTime(new Date(), "dddd, MMMM d")
|
|
}
|
|
}
|
|
|
|
// Update weather every 10 minutes
|
|
Timer {
|
|
interval: 600000 // 10 minutes
|
|
running: true
|
|
repeat: true
|
|
onTriggered: {
|
|
fetchWeatherData()
|
|
}
|
|
}
|
|
|
|
// System control buttons (bottom right)
|
|
ColumnLayout {
|
|
anchors.right: parent.right
|
|
anchors.bottom: parent.bottom
|
|
anchors.margins: 32
|
|
spacing: 12
|
|
// Shutdown
|
|
Rectangle {
|
|
width: 48; height: 48; radius: 24
|
|
color: shutdownArea.containsMouse ? Theme.error : "transparent"
|
|
border.color: Theme.error
|
|
border.width: 1
|
|
MouseArea {
|
|
id: shutdownArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
onClicked: {
|
|
Qt.createQmlObject('import Quickshell.Io; Process { command: ["shutdown", "-h", "now"]; running: true }', lock)
|
|
}
|
|
}
|
|
Text {
|
|
anchors.centerIn: parent
|
|
text: "power_settings_new"
|
|
font.family: "Material Symbols Outlined"
|
|
font.pixelSize: 24
|
|
color: shutdownArea.containsMouse ? Theme.onAccent : Theme.error
|
|
}
|
|
}
|
|
// Reboot
|
|
Rectangle {
|
|
width: 48; height: 48; radius: 24
|
|
color: rebootArea.containsMouse ? Theme.accentPrimary : "transparent"
|
|
border.color: Theme.accentPrimary
|
|
border.width: 1
|
|
MouseArea {
|
|
id: rebootArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
onClicked: {
|
|
Qt.createQmlObject('import Quickshell.Io; Process { command: ["reboot"]; running: true }', lock)
|
|
}
|
|
}
|
|
Text {
|
|
anchors.centerIn: parent
|
|
text: "refresh"
|
|
font.family: "Material Symbols Outlined"
|
|
font.pixelSize: 24
|
|
color: rebootArea.containsMouse ? Theme.onAccent : Theme.accentPrimary
|
|
}
|
|
}
|
|
// Logout
|
|
Rectangle {
|
|
width: 48; height: 48; radius: 24
|
|
color: logoutArea.containsMouse ? Theme.accentSecondary : "transparent"
|
|
border.color: Theme.accentSecondary
|
|
border.width: 1
|
|
MouseArea {
|
|
id: logoutArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
onClicked: {
|
|
Qt.createQmlObject('import Quickshell.Io; Process { command: ["loginctl", "terminate-user", "' + Quickshell.env("USER") + '"]; running: true }', lock)
|
|
}
|
|
}
|
|
Text {
|
|
anchors.centerIn: parent
|
|
text: "exit_to_app"
|
|
font.family: "Material Symbols Outlined"
|
|
font.pixelSize: 24
|
|
color: logoutArea.containsMouse ? Theme.onAccent : Theme.accentSecondary
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |