// SPDX-License-Identifier: GPL-2.0-only // Copyright (C) 2026 Danilo M. // // UDT SDDM greeter. The root item spans every screen. The wallpaper is drawn // once per screen; the login card is pinned to one screen (config uiScreen, // else the primary screen); input wakes the card after it fades. import QtQuick import "components" Item { id: root // SDDM sizes the greeter to the union of every screen. readonly property var union: screenModel.geometry(-1) width: union.width height: union.height // Palette. Values come from theme.conf, overridden live by theme.conf.user. readonly property string baseColor: config.stringValue("base") readonly property int cardRadius: 10 readonly property string wallpaperUrl: config.stringValue("background") === "" ? "" : "file://" + config.stringValue("background") readonly property string wantedScreen: config.stringValue("uiScreen") property var primaryGeometry: screenModel.geometry(screenModel.primary >= 0 ? screenModel.primary : 0) property var namedGeometry: primaryGeometry readonly property var cardGeometry: wantedScreen === "" ? primaryGeometry : namedGeometry // The Image delegate of the screen the card sits on. The frosted card // samples it. Null until the model has populated. property Item cardScreenImage: null Repeater { id: screens model: screenModel delegate: Item { x: geometry.x y: geometry.y width: geometry.width height: geometry.height Image { id: screenImage anchors.fill: parent source: root.wallpaperUrl fillMode: Image.PreserveAspectCrop sourceSize.width: parent.width sourceSize.height: parent.height smooth: true asynchronous: true } // Scrim so the card and text read on any wallpaper. Rectangle { anchors.fill: parent color: root.baseColor opacity: 0.45 } readonly property string screenName: name Component.onCompleted: root.registerScreen(index, screenName, screenImage, geometry) } } // Filled as the model populates: the primary screen's name, and the // wallpaper image plus geometry of whichever screen owns the card. property string primaryName: "" function registerScreen(index, screenName, image, geom) { if (index === screenModel.primary) root.primaryName = screenName if (screenName === root.cardScreenName()) { root.cardScreenImage = image root.namedGeometry = geom } } function cardScreenName() { return root.wantedScreen !== "" ? root.wantedScreen : root.primaryName } // Idle: fade the card after fadeoutMs, restore on any input. property bool uiVisible: true Timer { id: idle interval: config.intValue("fadeoutMs") > 0 ? config.intValue("fadeoutMs") : 60000 running: root.uiVisible repeat: false onTriggered: root.uiVisible = false } function wake() { root.uiVisible = true idle.restart() } MouseArea { anchors.fill: parent hoverEnabled: true onPressed: root.wake() onPositionChanged: root.wake() } Keys.onPressed: event => { root.wake() event.accepted = false } LoginCard { id: card x: root.cardGeometry.x + (root.cardGeometry.width - width) / 2 + card.shakeOffset y: root.cardGeometry.y + (root.cardGeometry.height - height) / 2 backdropSource: root.cardScreenImage visible: opacity > 0 opacity: root.uiVisible ? 1 : 0 Behavior on opacity { NumberAnimation { duration: 260; easing.type: Easing.OutCubic } } onWake: root.wake() } }