Merge branch 'main' of github.com:Ly-sec/Noctalia into lockscreen-weather-fix

This commit is contained in:
Sébastien Atoch 2025-07-30 08:20:14 -04:00
commit 552bfba168
3 changed files with 91 additions and 56 deletions

View file

@ -27,15 +27,13 @@ WlSessionLock {
property double currentTemp: 0 property double currentTemp: 0
locked: false locked: false
// On component completed, request to fetch weather data, with a little delay. // Request to fetch weather with a little delay to ensure weatherCity is properly loaded.
// Without this delay the city name is not loaded yet.
Component.onCompleted: { Component.onCompleted: {
Qt.callLater(function () { Qt.callLater(function () {
fetchWeatherData(); fetchWeatherData();
}) })
} }
// Weather fetching function
function fetchWeatherData() { function fetchWeatherData() {
WeatherHelper.fetchCityWeather(weatherCity, function (result) { WeatherHelper.fetchCityWeather(weatherCity, function (result) {
weatherData = result.weather; weatherData = result.weather;
@ -65,7 +63,6 @@ WlSessionLock {
return "cloud"; return "cloud";
} }
// Authentication function
function unlockAttempt() { function unlockAttempt() {
console.log("Unlock attempt started"); console.log("Unlock attempt started");
if (!pamAvailable) { if (!pamAvailable) {
@ -129,9 +126,8 @@ WlSessionLock {
console.log("PAM start result:", started); console.log("PAM start result:", started);
} }
// Lock surface
WlSessionLockSurface { WlSessionLockSurface {
// Blurred wallpaper background // Wallpaper image to blur
Image { Image {
id: lockBgImage id: lockBgImage
anchors.fill: parent anchors.fill: parent
@ -139,28 +135,23 @@ WlSessionLock {
source: WallpaperManager.currentWallpaper !== "" ? WallpaperManager.currentWallpaper : "" source: WallpaperManager.currentWallpaper !== "" ? WallpaperManager.currentWallpaper : ""
cache: true cache: true
smooth: false smooth: false
visible: true // Show the original for FastBlur input visible: true // source for MultiEffect
} }
FastBlur {
MultiEffect {
id: lockBgBlur
anchors.fill: parent anchors.fill: parent
source: lockBgImage source: lockBgImage
radius: 22 // Adjust blur strength as needed blurEnabled: true
transparentBorder: true blur: 0.48 // controls blur strength (0 to 1)
blurMax: 128 // max blur radius in pixels
} }
Rectangle {
anchors.fill: parent
color: Qt.rgba(
Theme.backgroundPrimary.r,
Theme.backgroundPrimary.g,
Theme.backgroundPrimary.b, 0.6)
}
// Main content container (moved up, Rectangle removed)
ColumnLayout { ColumnLayout {
anchors.centerIn: parent anchors.centerIn: parent
spacing: 30 spacing: 30
width: Math.min(parent.width * 0.8, 400) width: Math.min(parent.width * 0.8, 400)
// User avatar/icon
Rectangle { Rectangle {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
width: 80 width: 80
@ -174,7 +165,7 @@ WlSessionLock {
anchors.margins: 4 anchors.margins: 4
source: Settings.settings.profileImage source: Settings.settings.profileImage
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
visible: false // Only show the masked version visible: false
asynchronous: true asynchronous: true
} }
OpacityMask { OpacityMask {
@ -188,7 +179,6 @@ WlSessionLock {
} }
visible: Settings.settings.profileImage !== "" visible: Settings.settings.profileImage !== ""
} }
// Fallback icon
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: "person" text: "person"
@ -197,7 +187,6 @@ WlSessionLock {
color: Theme.onAccent color: Theme.onAccent
visible: Settings.settings.profileImage === "" visible: Settings.settings.profileImage === ""
} }
// Glow effect
layer.enabled: true layer.enabled: true
layer.effect: Glow { layer.effect: Glow {
color: Theme.accentPrimary color: Theme.accentPrimary
@ -206,7 +195,6 @@ WlSessionLock {
} }
} }
// Username
Text { Text {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: Quickshell.env("USER") text: Quickshell.env("USER")
@ -216,7 +204,6 @@ WlSessionLock {
color: Theme.textPrimary color: Theme.textPrimary
} }
// Password input container
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
height: 50 height: 50
@ -242,7 +229,6 @@ WlSessionLock {
text: lock.password text: lock.password
onTextChanged: lock.password = text onTextChanged: lock.password = text
// Placeholder text
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: "Enter password..." text: "Enter password..."
@ -252,7 +238,6 @@ WlSessionLock {
visible: !passwordInput.text && !passwordInput.activeFocus visible: !passwordInput.text && !passwordInput.activeFocus
} }
// Handle Enter key
Keys.onPressed: function (event) { Keys.onPressed: function (event) {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
lock.unlockAttempt(); lock.unlockAttempt();
@ -265,7 +250,6 @@ WlSessionLock {
} }
} }
// Error message
Rectangle { Rectangle {
id: errorMessageRect id: errorMessageRect
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -286,7 +270,6 @@ WlSessionLock {
} }
} }
// Unlock button
Rectangle { Rectangle {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
width: 120 width: 120
@ -335,7 +318,6 @@ WlSessionLock {
offsetX: screen.width / 2 + 30 offsetX: screen.width / 2 + 30
offsetY: 0 offsetY: 0
anchors.top: parent.top anchors.top: parent.top
//anchors.horizontalCenter: parent.horizontalCenter
visible: Settings.settings.showCorners visible: Settings.settings.showCorners
z: 50 z: 50
} }
@ -348,7 +330,6 @@ WlSessionLock {
offsetX: - Screen.width / 2 - 30 offsetX: - Screen.width / 2 - 30
offsetY: 0 offsetY: 0
anchors.top: parent.top anchors.top: parent.top
//anchors.horizontalCenter: parent.horizontalCenter
visible: Settings.settings.showCorners visible: Settings.settings.showCorners
z: 51 z: 51
} }
@ -361,14 +342,13 @@ WlSessionLock {
bottomLeftRadius: 20 bottomLeftRadius: 20
bottomRightRadius: 20 bottomRightRadius: 20
// Top-center info panel (clock + weather)
ColumnLayout { ColumnLayout {
id: infoColumn id: infoColumn
anchors.top: parent.top anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 0 anchors.topMargin: 0
spacing: 8 spacing: 8
// Clock
Text { Text {
id: timeText id: timeText
text: Qt.formatDateTime(new Date(), "HH:mm") text: Qt.formatDateTime(new Date(), "HH:mm")
@ -389,12 +369,13 @@ WlSessionLock {
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
} }
// Weather info (centered, no city)
RowLayout { RowLayout {
spacing: 6 spacing: 6
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
visible: weatherData && weatherData.current_weather visible: weatherData && weatherData.current_weather
Text { Text {
text: weatherData && weatherData.current_weather ? materialSymbolForCode(weatherData.current_weather.weathercode) : "cloud" text: weatherData && weatherData.current_weather ? materialSymbolForCode(weatherData.current_weather.weathercode) : "cloud"
font.family: "Material Symbols Outlined" font.family: "Material Symbols Outlined"
@ -402,15 +383,16 @@ WlSessionLock {
color: Theme.accentPrimary color: Theme.accentPrimary
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
Text { Text {
text: weatherData && weatherData.current_weather ? (Settings.settings.useFahrenheit ? `${Math.round(weatherData.current_weather.temperature * 9 / 5 + 32)}°F` : `${Math.round(weatherData.current_weather.temperature)}°C`) : (Settings.settings.useFahrenheit ? "--°F" : "--°C") text: weatherData && weatherData.current_weather ? ((Settings.settings.useFahrenheit !== undefined ? Settings.settings.useFahrenheit : false) ? `${Math.round(weatherData.current_weather.temperature * 9 / 5 + 32)}°F` : `${Math.round(weatherData.current_weather.temperature)}°C`) : ((Settings.settings.useFahrenheit !== undefined ? Settings.settings.useFahrenheit : false) ? "--°F" : "--°C")
font.family: Theme.fontFamily font.family: Theme.fontFamily
font.pixelSize: 18 font.pixelSize: 18
color: Theme.textSecondary color: Theme.textSecondary
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
} }
// Weather error
Text { Text {
text: weatherError text: weatherError
color: Theme.error color: Theme.error
@ -423,7 +405,6 @@ WlSessionLock {
} }
} }
// Update clock every second
Timer { Timer {
interval: 1000 interval: 1000
running: true running: true
@ -434,7 +415,6 @@ WlSessionLock {
} }
} }
// Update weather every 10 minutes
Timer { Timer {
interval: 600000 // 10 minutes interval: 600000 // 10 minutes
running: true running: true
@ -444,13 +424,12 @@ WlSessionLock {
} }
} }
// System control buttons (bottom right)
ColumnLayout { ColumnLayout {
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.margins: 32 anchors.margins: 32
spacing: 12 spacing: 12
// Shutdown
Rectangle { Rectangle {
width: 48 width: 48
height: 48 height: 48
@ -458,6 +437,7 @@ WlSessionLock {
color: shutdownArea.containsMouse ? Theme.error : "transparent" color: shutdownArea.containsMouse ? Theme.error : "transparent"
border.color: Theme.error border.color: Theme.error
border.width: 1 border.width: 1
MouseArea { MouseArea {
id: shutdownArea id: shutdownArea
anchors.fill: parent anchors.fill: parent
@ -466,6 +446,7 @@ WlSessionLock {
Qt.createQmlObject('import Quickshell.Io; Process { command: ["shutdown", "-h", "now"]; running: true }', lock); Qt.createQmlObject('import Quickshell.Io; Process { command: ["shutdown", "-h", "now"]; running: true }', lock);
} }
} }
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: "power_settings_new" text: "power_settings_new"
@ -474,7 +455,7 @@ WlSessionLock {
color: shutdownArea.containsMouse ? Theme.onAccent : Theme.error color: shutdownArea.containsMouse ? Theme.onAccent : Theme.error
} }
} }
// Reboot
Rectangle { Rectangle {
width: 48 width: 48
height: 48 height: 48
@ -482,6 +463,7 @@ WlSessionLock {
color: rebootArea.containsMouse ? Theme.accentPrimary : "transparent" color: rebootArea.containsMouse ? Theme.accentPrimary : "transparent"
border.color: Theme.accentPrimary border.color: Theme.accentPrimary
border.width: 1 border.width: 1
MouseArea { MouseArea {
id: rebootArea id: rebootArea
anchors.fill: parent anchors.fill: parent
@ -490,6 +472,7 @@ WlSessionLock {
Qt.createQmlObject('import Quickshell.Io; Process { command: ["reboot"]; running: true }', lock); Qt.createQmlObject('import Quickshell.Io; Process { command: ["reboot"]; running: true }', lock);
} }
} }
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: "refresh" text: "refresh"
@ -498,7 +481,7 @@ WlSessionLock {
color: rebootArea.containsMouse ? Theme.onAccent : Theme.accentPrimary color: rebootArea.containsMouse ? Theme.onAccent : Theme.accentPrimary
} }
} }
// Logout
Rectangle { Rectangle {
width: 48 width: 48
height: 48 height: 48
@ -506,6 +489,7 @@ WlSessionLock {
color: logoutArea.containsMouse ? Theme.accentSecondary : "transparent" color: logoutArea.containsMouse ? Theme.accentSecondary : "transparent"
border.color: Theme.accentSecondary border.color: Theme.accentSecondary
border.width: 1 border.width: 1
MouseArea { MouseArea {
id: logoutArea id: logoutArea
anchors.fill: parent anchors.fill: parent
@ -514,6 +498,7 @@ WlSessionLock {
Qt.createQmlObject('import Quickshell.Io; Process { command: ["loginctl", "terminate-user", "' + Quickshell.env("USER") + '"]; running: true }', lock); Qt.createQmlObject('import Quickshell.Io; Process { command: ["loginctl", "terminate-user", "' + Quickshell.env("USER") + '"]; running: true }', lock);
} }
} }
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: "exit_to_app" text: "exit_to_app"

View file

@ -155,7 +155,7 @@ Rectangle {
Rectangle { Rectangle {
width: 160 width: 160
height: 180 height: 220
color: Theme.surface color: Theme.surface
radius: 8 radius: 8
border.color: Theme.outline border.color: Theme.outline
@ -395,34 +395,54 @@ Rectangle {
command: ["shutdown", "-h", "now"] command: ["shutdown", "-h", "now"]
running: false running: false
} }
Process { Process {
id: rebootProcess id: rebootProcess
command: ["reboot"] command: ["reboot"]
running: false running: false
} }
Process {
id: logoutProcess
command: ["niri", "msg", "action", "quit", "--skip-confirmation"]
running: false
}
Process { Process {
id: suspendProcess id: suspendProcess
command: ["systemctl", "suspend"] command: ["systemctl", "suspend"]
running: false running: false
} }
Process {
id: logoutProcessNiri
command: ["niri", "msg", "action", "quit", "--skip-confirmation"]
running: false
}
Process {
id: logoutProcessHyprland
command: ["hyprctl", "dispatch", "exit"]
running: false
}
function logout() {
if (WorkspaceManager.isNiri) {
logoutProcessNiri.running = true;
} else if (WorkspaceManager.isHyprland) {
logoutProcessHyprland.running = true;
} else {
// fallback or error
console.warn("No supported compositor detected for logout");
}
}
function suspend() { function suspend() {
suspendProcess.running = true; suspendProcess.running = true;
} }
function shutdown() { function shutdown() {
shutdownProcess.running = true; shutdownProcess.running = true;
} }
function reboot() { function reboot() {
rebootProcess.running = true; rebootProcess.running = true;
} }
function logout() {
logoutProcess.running = true;
}
property bool panelVisible: false property bool panelVisible: false

View file

@ -26,13 +26,20 @@ Scope {
return Math.round(value / step) * step; return Math.round(value / step) * step;
} }
// Volume property reflecting current audio volume in 0-100
// Will be kept in sync dynamically below
property int volume: (defaultAudioSink && defaultAudioSink.audio && !defaultAudioSink.audio.muted)
? Math.round(defaultAudioSink.audio.volume * 100)
: 0
// Function to update volume with clamping, stepping, and applying to audio sink
function updateVolume(vol) { function updateVolume(vol) {
var clamped = Math.max(0, Math.min(100, vol)); var clamped = Math.max(0, Math.min(100, vol));
var stepped = roundToStep(clamped, 5); var stepped = roundToStep(clamped, 5);
volume = stepped;
if (defaultAudioSink && defaultAudioSink.audio) { if (defaultAudioSink && defaultAudioSink.audio) {
defaultAudioSink.audio.volume = stepped / 100; defaultAudioSink.audio.volume = stepped / 100;
} }
volume = stepped;
} }
Component.onCompleted: { Component.onCompleted: {
@ -94,8 +101,8 @@ Scope {
id: notificationHistoryWin id: notificationHistoryWin
} }
// Reference to the default audio sink from Pipewire
property var defaultAudioSink: Pipewire.defaultAudioSink property var defaultAudioSink: Pipewire.defaultAudioSink
property int volume: defaultAudioSink && defaultAudioSink.audio && defaultAudioSink.audio.volume && !defaultAudioSink.audio.muted ? Math.round(defaultAudioSink.audio.volume * 100) : 0
PwObjectTracker { PwObjectTracker {
objects: [Pipewire.defaultAudioSink] objects: [Pipewire.defaultAudioSink]
@ -137,4 +144,27 @@ Scope {
} }
} }
} }
// --- NEW: Keep volume property in sync with actual Pipewire audio sink volume ---
Connections {
target: defaultAudioSink.audio
onVolumeChanged: {
if (defaultAudioSink.audio && !defaultAudioSink.audio.muted) {
volume = Math.round(defaultAudioSink.audio.volume * 100);
console.log("Volume changed externally to:", volume);
}
}
onMutedChanged: {
if (defaultAudioSink.audio) {
if (defaultAudioSink.audio.muted) {
volume = 0;
console.log("Audio muted, volume set to 0");
} else {
volume = Math.round(defaultAudioSink.audio.volume * 100);
console.log("Audio unmuted, volume restored to:", volume);
}
}
}
}
} }