import Quickshell import Quickshell.Services.UPower import Quickshell.Services.Mpris import QtQuick import Qt5Compat.GraphicalEffects // Shared appearance; authentication belongs to the greeter or session lock. Item { id: view required property var theme property bool busy: false property string errorMessage: "" signal submitted(string password) Image { id: wall anchors.fill: parent source: "file://" + view.theme.wallImage // cache:false — soft reload (qs ipc reload) keeps the process // alive, and the QML pixmap cache is keyed by URL, so a new // wallpaper behind the same path never shows without this. cache: false fillMode: Image.PreserveAspectCrop visible: false } FastBlur { anchors.fill: wall source: wall radius: 20 } // Lighter blur means less natural contrast, so the scrim // is a left-weighted gradient instead of a flat 0.4 wash: // dark under the text column, near-clear on the right. Rectangle { anchors.fill: parent gradient: Gradient { orientation: Gradient.Horizontal GradientStop { position: 0.0; color: "#b0000000" } GradientStop { position: 0.55; color: "#59000000" } GradientStop { position: 1.0; color: "#33000000" } } } SystemClock { id: lockClock precision: SystemClock.Seconds } // Lower-left, not centred: clock/date/password stack left // aligned against a 10% margin, sitting below the middle. Column { anchors.left: parent.left anchors.leftMargin: Math.round(parent.width * 0.10) anchors.verticalCenter: parent.verticalCenter anchors.verticalCenterOffset: Math.round(parent.height * 0.14) spacing: 6 Text { text: lockClock.date.toLocaleTimeString(Qt.locale(), "HH:mm") color: view.theme.base05 font.family: view.theme.fontFamily font.pixelSize: 104 font.weight: Font.DemiBold } Text { text: lockClock.date.toLocaleDateString(Qt.locale(), "dddd, d MMMM") color: view.theme.base04 font.family: view.theme.fontFamily font.pixelSize: 18 } Item { width: 1; height: 14 } // Power. UPower.displayDevice is the same DBus source // the bar reads — no polling, no /sys parsing. Row { id: lockBatt spacing: 8 property var dev: UPower.displayDevice property int level: dev && dev.ready ? Math.round(dev.percentage * 100) : 0 property bool charging: dev ? dev.state === UPowerDeviceState.Charging : false property string remaining: { if (!dev) return ""; let secs = charging ? dev.timeToFull : dev.timeToEmpty; if (!secs || secs <= 0) return ""; return Math.floor(secs / 3600) + "h " + Math.round((secs % 3600) / 60) + "m"; } visible: dev && dev.ready Text { anchors.verticalCenter: parent.verticalCenter text: lockBatt.charging ? "battery-charging" : lockBatt.level >= 75 ? "battery-full" : lockBatt.level >= 50 ? "battery-medium" : lockBatt.level >= 20 ? "battery-low" : "battery" font.family: view.theme.iconFont font.pixelSize: 17 color: lockBatt.level <= 15 ? view.theme.err : lockBatt.level <= 30 ? view.theme.warn : view.theme.base04 } Text { anchors.verticalCenter: parent.verticalCenter text: lockBatt.level + "%" + (lockBatt.remaining === "" ? "" : " · " + lockBatt.remaining + (lockBatt.charging ? " to full" : " left")) color: view.theme.base04 font.family: view.theme.fontFamily font.pixelSize: 15 } } // Now playing. Mpris is a global service, so this shows // whatever kept playing into the lock — nothing is woken. Row { id: lockMedia spacing: 8 property var player: Mpris.players.values.filter(p => p.trackTitle)[0] visible: !!player Text { anchors.verticalCenter: parent.verticalCenter text: "music" font.family: view.theme.iconFont font.pixelSize: 17 color: view.theme.base04 } Text { anchors.verticalCenter: parent.verticalCenter width: 380 elide: Text.ElideRight text: lockMedia.player ? lockMedia.player.trackTitle + (lockMedia.player.trackArtist ? " · " + lockMedia.player.trackArtist : "") : "" color: view.theme.base04 font.family: view.theme.fontFamily font.pixelSize: 15 } } Item { width: 1; height: 14 } // Password pill: dots for typed chars; border tracks // state (accent while checking, red on failure). Rectangle { width: 300 height: 46 radius: view.theme.radiusCard color: view.theme.cardBg border.width: view.theme.borderWidth border.color: view.busy ? view.theme.accent : (view.errorMessage !== "") ? view.theme.err : view.theme.base03 Behavior on border.color { ColorAnimation { duration: view.theme.animFade } } Row { anchors.centerIn: parent spacing: 8 Repeater { model: Math.min(pwInput.text.length, 24) Rectangle { width: 8; height: 8; radius: 4 color: view.theme.base05 } } } Text { anchors.centerIn: parent visible: pwInput.text === "" && (view.errorMessage !== "") text: view.errorMessage color: view.theme.err font.family: view.theme.fontFamily font.pixelSize: 13 } TextInput { id: pwInput anchors.fill: parent opacity: 0 enabled: !view.busy echoMode: TextInput.Password focus: true function go() { view.submitted(text); text = ""; } // Explicit key handlers: accepted() doesn't // fire reliably on the lock surface. Keys.onReturnPressed: go() Keys.onEnterPressed: go() onAccepted: go() Keys.onEscapePressed: text = "" } // Visible confirm button (after TextInput: on top) Rectangle { anchors.right: parent.right anchors.rightMargin: 6 anchors.verticalCenter: parent.verticalCenter width: 32; height: 32; radius: 16 color: confirmMa.containsMouse ? view.theme.base02 : "transparent" Behavior on color { ColorAnimation { duration: view.theme.animFade } } visible: pwInput.text.length > 0 Text { anchors.centerIn: parent text: "arrow-right" font.family: view.theme.iconFont font.pixelSize: 18 color: view.theme.accent } MouseArea { id: confirmMa anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: pwInput.go() } } } } Component.onCompleted: pwInput.forceActiveFocus() }