Add lock screen appearance settings
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
|
||||
import qs.modules.settings
|
||||
|
||||
ShellRoot {
|
||||
id: root
|
||||
|
||||
readonly property string fixtureWallpaper: `${Quickshell.env("HOME")}/Pictures/Wallpapers/faroe_islands.jpg`
|
||||
|
||||
LockScreenPreview {
|
||||
id: screenshotPreview
|
||||
width: 620
|
||||
backgroundMode: "screenshot"
|
||||
blurLevel: 4
|
||||
showClock: true
|
||||
showDate: true
|
||||
showUser: true
|
||||
fadeOnEmpty: false
|
||||
wallpaperPath: root.fixtureWallpaper
|
||||
}
|
||||
|
||||
LockScreenPreview {
|
||||
id: wallpaperPreview
|
||||
width: 620
|
||||
backgroundMode: "wallpaper"
|
||||
blurLevel: 2
|
||||
showClock: false
|
||||
showDate: true
|
||||
showUser: false
|
||||
fadeOnEmpty: true
|
||||
wallpaperPath: root.fixtureWallpaper
|
||||
}
|
||||
|
||||
LockScreenPreview {
|
||||
id: solidPreview
|
||||
width: 620
|
||||
backgroundMode: "solid"
|
||||
blurLevel: 0
|
||||
showClock: true
|
||||
showDate: false
|
||||
showUser: true
|
||||
fadeOnEmpty: false
|
||||
wallpaperPath: root.fixtureWallpaper
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "lock-screen-settings-test"
|
||||
|
||||
function status(): string {
|
||||
return JSON.stringify({
|
||||
screenshot: {
|
||||
mode: screenshotPreview.previewMode,
|
||||
wallpaperVisible: screenshotPreview.wallpaperVisible,
|
||||
blurStrength: screenshotPreview.blurStrength,
|
||||
clockVisible: screenshotPreview.clockVisible,
|
||||
dateVisible: screenshotPreview.dateVisible,
|
||||
userVisible: screenshotPreview.userVisible,
|
||||
passwordVisible: screenshotPreview.passwordVisible
|
||||
},
|
||||
wallpaper: {
|
||||
mode: wallpaperPreview.previewMode,
|
||||
wallpaperVisible: wallpaperPreview.wallpaperVisible,
|
||||
blurStrength: wallpaperPreview.blurStrength,
|
||||
clockVisible: wallpaperPreview.clockVisible,
|
||||
dateVisible: wallpaperPreview.dateVisible,
|
||||
userVisible: wallpaperPreview.userVisible,
|
||||
passwordVisible: wallpaperPreview.passwordVisible
|
||||
},
|
||||
solid: {
|
||||
mode: solidPreview.previewMode,
|
||||
wallpaperVisible: solidPreview.wallpaperVisible,
|
||||
blurStrength: solidPreview.blurStrength,
|
||||
clockVisible: solidPreview.clockVisible,
|
||||
dateVisible: solidPreview.dateVisible,
|
||||
userVisible: solidPreview.userVisible,
|
||||
passwordVisible: solidPreview.passwordVisible
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,24 @@ SettingsPage {
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
title: "Lock screen"
|
||||
subtitle: LockScreen.lastError !== ""
|
||||
? LockScreen.lastError
|
||||
: "A representative preview of the screen shown before authentication."
|
||||
|
||||
LockScreenPreview {
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
ChoiceRow { setting: "lockBackgroundMode" }
|
||||
SliderRow { setting: "lockBlurLevel"; zeroLabel: "Off" }
|
||||
ToggleRow { setting: "lockShowClock" }
|
||||
ToggleRow { setting: "lockShowDate" }
|
||||
ToggleRow { setting: "lockShowUser" }
|
||||
ToggleRow { setting: "lockFadeOnEmpty"; divider: false }
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
title: "Colour scheme"
|
||||
subtitle: ColorScheme.lastError !== ""
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
|
||||
import qs.config
|
||||
import qs.services
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property string backgroundMode: DesktopPreferences.get("lockBackgroundMode")
|
||||
property int blurLevel: DesktopPreferences.get("lockBlurLevel")
|
||||
property bool showClock: DesktopPreferences.get("lockShowClock")
|
||||
property bool showDate: DesktopPreferences.get("lockShowDate")
|
||||
property bool showUser: DesktopPreferences.get("lockShowUser")
|
||||
property bool fadeOnEmpty: DesktopPreferences.get("lockFadeOnEmpty")
|
||||
property bool use24Hour: DesktopPreferences.get("use24Hour")
|
||||
property string wallpaperPath: Wallpaper.active !== ""
|
||||
? Wallpaper.active
|
||||
: (Wallpaper.configured !== "" ? Wallpaper.configured : Wallpaper.shippedPath)
|
||||
|
||||
readonly property string previewMode: root.backgroundMode
|
||||
readonly property real blurStrength: Math.max(0, Math.min(1, root.blurLevel / 5))
|
||||
readonly property bool wallpaperVisible: wallpaper.visible
|
||||
readonly property bool clockVisible: clockLabel.visible
|
||||
readonly property bool dateVisible: dateLabel.visible
|
||||
readonly property bool userVisible: userLabel.visible
|
||||
readonly property bool passwordVisible: passwordField.visible
|
||||
|
||||
width: parent ? parent.width : 620
|
||||
implicitHeight: 230
|
||||
radius: Theme.cardRadius
|
||||
clip: true
|
||||
color: Theme.bg
|
||||
border.width: 1
|
||||
border.color: Theme.alpha(Theme.fg, 0.10)
|
||||
|
||||
Image {
|
||||
id: wallpaper
|
||||
anchors.fill: parent
|
||||
visible: root.backgroundMode === "wallpaper"
|
||||
source: visible && root.wallpaperPath !== "" ? root.wallpaperPath : ""
|
||||
asynchronous: true
|
||||
cache: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
sourceSize.width: 960
|
||||
sourceSize.height: 540
|
||||
}
|
||||
|
||||
// Screenshot mode stays representative instead of taking a real desktop
|
||||
// capture inside Settings. The layered window silhouettes communicate the
|
||||
// selected softness without a live ShaderEffect or GPU repaint loop.
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: root.backgroundMode === "screenshot"
|
||||
color: Theme.bgDark
|
||||
|
||||
Rectangle {
|
||||
x: parent.width * 0.10
|
||||
y: parent.height * 0.12
|
||||
width: parent.width * 0.45
|
||||
height: parent.height * 0.66
|
||||
radius: 14 + root.blurStrength * 10
|
||||
color: Theme.alpha(Theme.bgHighlight, 0.52 - root.blurStrength * 0.18)
|
||||
border.width: 1
|
||||
border.color: Theme.alpha(Theme.accent, 0.14)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
x: parent.width * 0.48
|
||||
y: parent.height * 0.24
|
||||
width: parent.width * 0.40
|
||||
height: parent.height * 0.57
|
||||
radius: 14 + root.blurStrength * 10
|
||||
color: Theme.alpha(Theme.bgPanel, 0.64 - root.blurStrength * 0.20)
|
||||
border.width: 1
|
||||
border.color: Theme.alpha(Theme.accentSecondary, 0.13)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: root.backgroundMode === "wallpaper"
|
||||
? Theme.alpha(Theme.bg, 0.42)
|
||||
: Theme.alpha(Theme.bg, 0.12 + root.blurStrength * 0.16)
|
||||
}
|
||||
|
||||
Text {
|
||||
id: clockLabel
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 26
|
||||
visible: root.showClock
|
||||
text: Qt.formatDateTime(clock.date, root.use24Hour ? "HH:mm" : "h:mm")
|
||||
color: Theme.fg
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 42
|
||||
font.weight: Font.Light
|
||||
}
|
||||
|
||||
Text {
|
||||
id: dateLabel
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: clockLabel.visible ? clockLabel.bottom : parent.top
|
||||
anchors.topMargin: clockLabel.visible ? 1 : 35
|
||||
visible: root.showDate
|
||||
text: Qt.formatDateTime(clock.date, "dddd, MMMM d")
|
||||
color: Theme.fgDim
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: passwordField
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: root.showUser ? 48 : 30
|
||||
visible: !root.fadeOnEmpty
|
||||
width: Math.min(270, parent.width * 0.48)
|
||||
height: 36
|
||||
radius: height / 2
|
||||
color: Theme.alpha(Theme.bgPanel, 0.84)
|
||||
border.width: 1
|
||||
border.color: Theme.alpha(Theme.accent, 0.78)
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "Password"
|
||||
color: Theme.fgDim
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.italic: true
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: userLabel
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 19
|
||||
visible: root.showUser
|
||||
text: Quickshell.env("USER") || "User"
|
||||
color: Theme.alpha(Theme.fg, 0.90)
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
SystemClock {
|
||||
id: clock
|
||||
precision: SystemClock.Minutes
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,11 @@ the owner instead of duplicating it.
|
||||
| `lockMinutes` | Power | Privacy | Idle timing owns the mechanism; privacy owns the expectation that the unattended desktop locks. |
|
||||
| `lockOnSleep` | Power | Privacy | Suspend owns the transition; privacy owns whether waking requires authentication. |
|
||||
|
||||
Lock-screen visuals belong only to **Appearance**: background source, blur,
|
||||
clock, date, user name, and password-field presentation. **Power** owns when
|
||||
the session locks, while **Privacy** keeps only the established timing mirrors
|
||||
above. Visual controls must not be copied onto either page.
|
||||
|
||||
Mirrors must remain the same schema-backed control, never a second preference
|
||||
or a copied default. Additions to this table require a concrete discoverability
|
||||
reason and an update to `tests/quickshell/settings-ownership-contract.sh`.
|
||||
@@ -155,6 +160,7 @@ file with `qs -p`, and discover its PID from the exact Config path in
|
||||
| `$XDG_STATE_HOME/panama/panama-home.json` | Home accessory favourites and aliases |
|
||||
| `$XDG_STATE_HOME/panama/backups/` | Settings snapshots |
|
||||
| `$XDG_STATE_HOME/panama/hypridle.conf` | Generated idle config |
|
||||
| `$XDG_STATE_HOME/panama/hyprlock.conf` | Generated lock-screen appearance |
|
||||
|
||||
`SystemSettings.restoreDefaults()` spans all of them. A reset that silently
|
||||
skipped one would be worse than having no reset, because nothing would say so.
|
||||
|
||||
@@ -29,6 +29,7 @@ ChoiceRow 1.0 ChoiceRow.qml
|
||||
ActionRow 1.0 ActionRow.qml
|
||||
TextRow 1.0 TextRow.qml
|
||||
DesktopPreview 1.0 DesktopPreview.qml
|
||||
LockScreenPreview 1.0 LockScreenPreview.qml
|
||||
PowerPage 1.0 PowerPage.qml
|
||||
DateTimePage 1.0 DateTimePage.qml
|
||||
AccessibilityPage 1.0 AccessibilityPage.qml
|
||||
|
||||
@@ -34,6 +34,7 @@ Singleton {
|
||||
"windows": "appearance",
|
||||
"effects": "appearance",
|
||||
"wallpaper": "appearance",
|
||||
"lockAppearance": "appearance",
|
||||
"dock": "desktop",
|
||||
"focus": "desktop",
|
||||
"display": "displays",
|
||||
@@ -65,7 +66,9 @@ Singleton {
|
||||
{ label: "Restore defaults", detail: "Return every Panama setting to its shipped value", page: "desktop" },
|
||||
{ label: "Keyboard shortcuts", detail: "Every shortcut the compositor has bound", page: "shortcuts" },
|
||||
{ label: "System Health", detail: "Check Panama services, integrations, tools, and recovery actions", page: "services" },
|
||||
{ label: "Copy health report", detail: "Copy a redacted Panama doctor report", page: "services" }
|
||||
{ label: "Copy health report", detail: "Copy a redacted Panama doctor report", page: "services" },
|
||||
{ label: "Lock screen background", detail: "Choose a blurred desktop, wallpaper, or solid colour", page: "appearance" },
|
||||
{ label: "Password field", detail: "Choose whether the empty lock-screen field stays visible", page: "appearance" }
|
||||
]
|
||||
|
||||
function pageFor(group: string): string {
|
||||
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
preview="$repo_dir/config/dot/quickshell/modules/settings/LockScreenPreview.qml"
|
||||
appearance="$repo_dir/config/dot/quickshell/modules/settings/AppearancePage.qml"
|
||||
power="$repo_dir/config/dot/quickshell/modules/settings/PowerPage.qml"
|
||||
privacy="$repo_dir/config/dot/quickshell/modules/settings/PrivacyPage.qml"
|
||||
harness="$repo_dir/config/dot/quickshell/lock-screen-settings-harness.qml"
|
||||
state_home="$(mktemp -d /tmp/panama-lock-settings-state.XXXXXX)"
|
||||
shell_log="$state_home/quickshell.log"
|
||||
harness_pid=""
|
||||
|
||||
fail() {
|
||||
printf 'lock screen settings contract: %s\n' "$1" >&2
|
||||
[[ -s "$shell_log" ]] && sed -n '1,160p' "$shell_log" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
instances_for_harness() {
|
||||
qs list --all 2>/dev/null | awk -v expected="$harness" '
|
||||
/^Instance / { pid = "" }
|
||||
/^[[:space:]]*Process ID:/ { pid = $3 }
|
||||
/^[[:space:]]*Config path:/ {
|
||||
path = $0
|
||||
sub(/^[[:space:]]*Config path: /, "", path)
|
||||
if (path == expected && pid ~ /^[0-9]+$/) print pid
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if [[ "$harness_pid" =~ ^[0-9]+$ ]] && kill -0 "$harness_pid" 2>/dev/null; then
|
||||
kill "$harness_pid" 2>/dev/null || true
|
||||
fi
|
||||
rm -rf "$state_home"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
[[ -f "$preview" ]] || fail 'LockScreenPreview.qml is missing'
|
||||
rg -Fq 'title: "Lock screen"' "$appearance" || fail 'Appearance has no Lock screen card'
|
||||
for key in lockBackgroundMode lockBlurLevel lockShowClock lockShowDate lockShowUser lockFadeOnEmpty; do
|
||||
rg -Fq "setting: \"$key\"" "$appearance" || fail "Appearance does not expose $key"
|
||||
! rg -Fq "setting: \"$key\"" "$power" "$privacy" \
|
||||
|| fail "$key was duplicated onto Power or Privacy"
|
||||
done
|
||||
|
||||
python3 - "$appearance" <<'PY' || fail 'Lock screen card is not between Background and Shell typography'
|
||||
import sys
|
||||
|
||||
text = open(sys.argv[1], encoding="utf-8").read()
|
||||
background = text.index('title: "Background"')
|
||||
lock = text.index('title: "Lock screen"')
|
||||
typography = text.index('title: "Shell typography"')
|
||||
raise SystemExit(0 if background < lock < typography else 1)
|
||||
PY
|
||||
|
||||
qs_for_harness() {
|
||||
if [[ "$harness_pid" =~ ^[0-9]+$ && "${1:-}" == "ipc" ]]; then
|
||||
XDG_STATE_HOME="$state_home" qs -p "$harness" ipc --pid "$harness_pid" "${@:2}"
|
||||
else
|
||||
XDG_STATE_HOME="$state_home" qs -p "$harness" "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
before_lockers="$(pgrep -x hyprlock | sort -n || true)"
|
||||
qs_for_harness --daemonize >"$shell_log" 2>&1 || fail 'isolated preview harness did not launch'
|
||||
for _ in $(seq 1 60); do
|
||||
harness_pid="$(instances_for_harness | head -1)"
|
||||
if [[ "$harness_pid" =~ ^[0-9]+$ ]] \
|
||||
&& qs_for_harness ipc show 2>/dev/null | rg -q '^target lock-screen-settings-test$'; then
|
||||
break
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
[[ "$harness_pid" =~ ^[0-9]+$ ]] || fail 'isolated preview harness process did not start'
|
||||
|
||||
status="$(qs_for_harness ipc call lock-screen-settings-test status)"
|
||||
jq -e '
|
||||
.screenshot == {"mode":"screenshot","wallpaperVisible":false,"blurStrength":0.8,"clockVisible":true,"dateVisible":true,"userVisible":true,"passwordVisible":true}
|
||||
and .wallpaper == {"mode":"wallpaper","wallpaperVisible":true,"blurStrength":0.4,"clockVisible":false,"dateVisible":true,"userVisible":false,"passwordVisible":false}
|
||||
and .solid == {"mode":"solid","wallpaperVisible":false,"blurStrength":0,"clockVisible":true,"dateVisible":false,"userVisible":true,"passwordVisible":true}
|
||||
' <<<"$status" >/dev/null || fail "preview roles did not follow their settings: $status"
|
||||
|
||||
after_lockers="$(pgrep -x hyprlock | sort -n || true)"
|
||||
[[ "$after_lockers" == "$before_lockers" ]] || fail 'preview started a real hyprlock process'
|
||||
if rg -n 'ReferenceError|TypeError|Binding loop|Unable to assign|Cannot assign|Failed to load' "$shell_log"; then
|
||||
fail 'preview harness emitted a QML runtime warning'
|
||||
fi
|
||||
|
||||
printf 'lock screen settings contract: PASS\n'
|
||||
@@ -60,6 +60,8 @@ timezone|Timezone|datetime
|
||||
repeat delay|Repeat delay|shortcuts
|
||||
system health|System Health|services
|
||||
doctor|Copy health report|services
|
||||
lock screen background|Lock screen background|appearance
|
||||
password field|Password field|appearance
|
||||
CASES
|
||||
|
||||
! rg -Fq 'Startup & Services' "$repo_dir/config/dot/quickshell/services/SettingsSearch.qml" \
|
||||
|
||||
Reference in New Issue
Block a user