/*SPDX-FileCopyrightText: 2016 David Edmundson <davidedmundson@kde.org>SPDX-FileCopyrightText: 2026 Kristen McWilliam <kristen@kde.org>SPDX-License-Identifier: LGPL-2.0-or-later*/import QtQuickimport QtQuick.Layoutsimport QtQuick.Controls as QQC2import Qt5Compat.GraphicalEffectsimport org.kde.plasma.components as PlasmaComponents3import org.kde.plasma.private.keyboardindicator as KeyboardIndicatorimport org.kde.kirigami as Kirigamiimport org.kde.breeze.componentsItem {id: root// If we're using software rendering, draw outlines instead of shadows// See https://bugs.kde.org/show_bug.cgi?id=398317readonly property bool softwareRendering: GraphicsInfo.api === GraphicsInfo.SoftwareKirigami.Theme.colorSet: Kirigami.Theme.ComplementaryKirigami.Theme.inherit: falsewidth: 1600height: 900property string notificationMessageLayoutMirroring.enabled: Application.layoutDirection === Qt.RightToLeftLayoutMirroring.childrenInherit: trueKeyboardIndicator.KeyState {id: capsLockStatekey: Qt.Key_CapsLock}Item {id: wallpaperanchors.fill: parentRepeater {model: screenModelBackground {x: geometry.x; y: geometry.y; width: geometry.width; height: geometry.heightsceneBackgroundType: config.typesceneBackgroundColor: config.colorsceneBackgroundImage: config.background}}}RejectPasswordAnimation {id: rejectPasswordAnimationtarget: mainStack}MouseArea {id: loginScreenRootanchors.fill: parentproperty bool uiVisible: trueproperty bool blockUI: mainStack.depth > 1 || userListComponent.mainPasswordBox.text.length > 0 || inputPanel.keyboardVisible || config.type !== "image"hoverEnabled: truedrag.filterChildren: trueonPressed: uiVisible = true;onPositionChanged: uiVisible = true;onUiVisibleChanged: {if (blockUI) {fadeoutTimer.running = false;} else if (uiVisible) {fadeoutTimer.restart();}}onBlockUIChanged: {if (blockUI) {fadeoutTimer.running = false;uiVisible = true;} else {fadeoutTimer.restart();}}Keys.onPressed: event => {uiVisible = true;event.accepted = false;}//takes one full minute for the ui to disappearTimer {id: fadeoutTimerrunning: trueinterval: 60000onTriggered: {if (!loginScreenRoot.blockUI) {userListComponent.mainPasswordBox.showPassword = false;loginScreenRoot.uiVisible = false;}}}WallpaperFader {visible: config.type === "image"anchors.fill: parentstate: loginScreenRoot.uiVisible ? "on" : "off"source: wallpapermainStack: mainStackfooter: footerclock: clock}DropShadow {id: clockShadowanchors.fill: clocksource: clockvisible: !softwareRendering && config.showClock === "true"radius: 7verticalOffset: 0.8samples: 15spread: 0.2color : Qt.rgba(0, 0, 0, 0.7)opacity: loginScreenRoot.uiVisible ? 0 : 1Behavior on opacity {OpacityAnimator {duration: Kirigami.Units.veryLongDuration * 2easing.type: Easing.InOutQuad}}}Clock {id: clockproperty Item shadow: clockShadowvisible: y > 0 && config.showClock === "true"anchors.horizontalCenter: parent.horizontalCentery: (userListComponent.userList.y + mainStack.y)/2 - height/2Layout.alignment: Qt.AlignBaseline}QQC2.StackView {id: mainStackanchors {left: parent.leftright: parent.right}height: root.height + Kirigami.Units.gridUnit * 3// this isn't implicit, otherwise items still get processed for the scenegraphvisible: opacity > 0// If true (depends on the style and environment variables), hover events are always accepted// and propagation stopped. This means the parent MouseArea won't get them and the UI won't be shown.// Disable capturing those events while the UI is hidden to avoid that, while still passing events otherwise.// One issue is that while the UI is visible, mouse activity won't keep resetting the timer, but when it// finally expires, the next event should immediately set uiVisible = true again.hoverEnabled: loginScreenRoot.uiVisible ? undefined : falsefocus: true //StackView is an implicit focus scope, so we need to give this focus so the item inside will have itTimer {//SDDM has a bug in 0.13 where even though we set the focus on the right item within the window, the window doesn't have focus//it is fixed in 6d5b36b28907b16280ff78995fef764bb0c573db which will be 0.14//we need to call "window->activate()" *After* it's been shown. We can't control that in QML so we use a shoddy timer//it's been this way for all Plasma 5.x without a huge problemrunning: truerepeat: falseinterval: 200onTriggered: mainStack.forceActiveFocus()}initialItem: Login {id: userListComponentuserListModel: userModelloginScreenUiVisible: loginScreenRoot.uiVisibleuserListCurrentIndex: userModel.lastIndex >= 0 ? userModel.lastIndex : 0lastUserName: userModel.lastUsershowUserList: {if (!userListModel.hasOwnProperty("count")|| !userListModel.hasOwnProperty("disableAvatarsThreshold")) {return false}if (userListModel.count === 0 ) {return false}if (userListModel.hasOwnProperty("containsAllUsers") && !userListModel.containsAllUsers) {return false}return userListModel.count <= userListModel.disableAvatarsThreshold}notificationMessage: {const parts = [];if (capsLockState.locked) {parts.push(i18ndc("plasma-desktop-sddm-theme", "@info:status", "Caps Lock is on"));}if (root.notificationMessage) {parts.push(root.notificationMessage);}return parts.join(" β’ ");}actionItemsVisible: !inputPanel.keyboardVisibleactionItems: [ActionButton {icon.name: "system-hibernate"text: i18ndc("plasma-desktop-sddm-theme", "Suspend to disk", "Hibernate")onClicked: sddm.hibernate()enabled: sddm.canHibernate},ActionButton {icon.name: "system-suspend"text: i18ndc("plasma-desktop-sddm-theme", "@action:button Suspend to RAM", "Sleep")onClicked: sddm.suspend()enabled: sddm.canSuspend},ActionButton {icon.name: "system-reboot"text: i18ndc("plasma-desktop-sddm-theme", "@action:button", "Restart")onClicked: sddm.reboot()enabled: sddm.canReboot},ActionButton {icon.name: "system-shutdown"text: i18ndc("plasma-desktop-sddm-theme", "@action:button", "Shut Down")onClicked: sddm.powerOff()enabled: sddm.canPowerOff},ActionButton {icon.name: "system-user-prompt"text: i18ndc("plasma-desktop-sddm-theme", "@action:button For switching to a username and password prompt", "Otherβ¦")onClicked: mainStack.push(userPromptComponent)visible: !userListComponent.showUsernamePrompt}]onLoginRequest: {root.notificationMessage = ""sddm.login(username, password, sessionButton.currentIndex)}}Behavior on opacity {OpacityAnimator {duration: Kirigami.Units.longDuration}}readonly property real zoomFactor: 1.5popEnter: Transition {ScaleAnimator {from: mainStack.zoomFactorto: 1duration: Kirigami.Units.veryLongDurationeasing.type: Easing.OutCubic}OpacityAnimator {from: 0to: 1duration: Kirigami.Units.veryLongDurationeasing.type: Easing.OutCubic}}popExit: Transition {ScaleAnimator {from: 1to: 1 / mainStack.zoomFactorduration: Kirigami.Units.veryLongDurationeasing.type: Easing.OutCubic}OpacityAnimator {from: 1to: 0duration: Kirigami.Units.veryLongDurationeasing.type: Easing.OutCubic}}pushEnter: Transition {ScaleAnimator {from: 1 / mainStack.zoomFactorto: 1duration: Kirigami.Units.veryLongDurationeasing.type: Easing.OutCubic}OpacityAnimator {from: 0to: 1duration: Kirigami.Units.veryLongDurationeasing.type: Easing.OutCubic}}pushExit: Transition {ScaleAnimator {from: 1to: mainStack.zoomFactorduration: Kirigami.Units.veryLongDurationeasing.type: Easing.OutCubic}OpacityAnimator {from: 1to: 0duration: Kirigami.Units.veryLongDurationeasing.type: Easing.OutCubic}}}VirtualKeyboard {id: inputPanelz: 1screenRoot: rootmainStack: mainStackmainBlock: userListComponentpasswordField: userListComponent.mainPasswordBox}Component {id: userPromptComponentLogin {showUsernamePrompt: truenotificationMessage: root.notificationMessageloginScreenUiVisible: loginScreenRoot.uiVisiblefontSize: Kirigami.Theme.defaultFont.pointSize + 2// using a model rather than a QObject list to avoid QTBUG-75900userListModel: ListModel {ListElement {name: ""icon: ""}Component.onCompleted: {// as we can't bind inside ListElementsetProperty(0, "name", i18ndc("plasma-desktop-sddm-theme", "@info:usagetip", "Type in Username and Password"));setProperty(0, "icon", Qt.resolvedUrl("faces/.face.icon"))}}onLoginRequest: {root.notificationMessage = ""sddm.login(username, password, sessionButton.currentIndex)}actionItemsVisible: !inputPanel.keyboardVisibleactionItems: [ActionButton {icon.name: "system-suspend"text: i18ndc("plasma-desktop-sddm-theme", "@action:button Suspend to RAM", "Sleep")onClicked: sddm.suspend()enabled: sddm.canSuspend},ActionButton {icon.name: "system-reboot"text: i18ndc("plasma-desktop-sddm-theme", "@action:button", "Restart")onClicked: sddm.reboot()enabled: sddm.canReboot},ActionButton {icon.name: "system-shutdown"text: i18ndc("plasma-desktop-sddm-theme", "@action:button", "Shut Down")onClicked: sddm.powerOff()enabled: sddm.canPowerOff},ActionButton {icon.name: "system-user-list"text: i18ndc("plasma-desktop-sddm-theme", "@action:button", "List Users")onClicked: mainStack.pop()}]}}DropShadow {id: logoShadowanchors.fill: logosource: logovisible: !softwareRendering && config.showlogo === "shown"horizontalOffset: 1verticalOffset: 1radius: 6samples: 14spread: 0.3color : "black" // shadows should always be blackopacity: loginScreenRoot.uiVisible ? 0 : 1Behavior on opacity {//OpacityAnimator when starting from 0 is buggy (it shows one frame with opacity 1)"NumberAnimation {duration: Kirigami.Units.longDurationeasing.type: Easing.InOutQuad}}}Image {id: logovisible: config.showlogo === "shown"source: config.logoanchors.horizontalCenter: parent.horizontalCenteranchors.bottom: footer.topanchors.bottomMargin: Kirigami.Units.largeSpacingasynchronous: truesourceSize.height: heightopacity: loginScreenRoot.uiVisible ? 0 : 1fillMode: Image.PreserveAspectFitheight: Math.round(Kirigami.Units.gridUnit * 3.5)Behavior on opacity {// OpacityAnimator when starting from 0 is buggy (it shows one frame with opacity 1)"NumberAnimation {duration: Kirigami.Units.longDurationeasing.type: Easing.InOutQuad}}}// Note: Containment masks stretch clickable area of their buttons to// the screen edges, essentially making them adhere to Fitts's law.// Due to virtual keyboard button having an icon, buttons may have// different heights, so fillHeight is required.//// Note for contributors: Keep this in sync with LockScreenUi.qml footer.RowLayout {id: footeranchors {bottom: parent.bottomleft: parent.leftright: parent.rightmargins: Kirigami.Units.smallSpacing}spacing: Kirigami.Units.smallSpacingBehavior on opacity {OpacityAnimator {duration: Kirigami.Units.longDuration}}PlasmaComponents3.ToolButton {id: virtualKeyboardButtontext: i18ndc("plasma-desktop-sddm-theme", "Button to show/hide virtual keyboard", "Virtual Keyboard")icon.name: inputPanel.keyboardVisible ? "input-keyboard-virtual-on" : "input-keyboard-virtual-off"onClicked: inputPanel.toggle()visible: inputPanel.availableLayout.fillHeight: truecontainmentMask: Item {parent: virtualKeyboardButtonanchors.fill: parentanchors.leftMargin: -footer.anchors.marginsanchors.bottomMargin: -footer.anchors.margins}}KeyboardButton {id: keyboardButtononKeyboardLayoutChanged: {// Otherwise the password field loses focus and virtual keyboard// keystrokes get eatenuserListComponent.mainPasswordBox.forceActiveFocus();}Layout.fillHeight: truecontainmentMask: Item {parent: keyboardButtonanchors.fill: parentanchors.leftMargin: virtualKeyboardButton.visible ? 0 : -footer.anchors.marginsanchors.bottomMargin: -footer.anchors.margins}}SessionButton {id: sessionButtononSessionChanged: {// Otherwise the password field loses focus and virtual keyboard// keystrokes get eatenuserListComponent.mainPasswordBox.forceActiveFocus();}Layout.fillHeight: truecontainmentMask: Item {parent: sessionButtonanchors.fill: parentanchors.leftMargin: virtualKeyboardButton.visible || keyboardButton.visible? 0 : -footer.anchors.marginsanchors.bottomMargin: -footer.anchors.margins}}Item {Layout.fillWidth: true}Battery {}}}Connections {target: sddmfunction onLoginFailed() {notificationMessage = i18ndc("plasma-desktop-sddm-theme", "@info:status", "Login Failed")footer.enabled = truemainStack.enabled = trueuserListComponent.userList.opacity = 1rejectPasswordAnimation.start()}function onLoginSucceeded() {//note SDDM will kill the greeter at some random point after this//there is no certainty any transition will finish, it depends on the time it//takes to complete the initmainStack.opacity = 0footer.opacity = 0}}onNotificationMessageChanged: {if (notificationMessage) {notificationResetTimer.start();}}Timer {id: notificationResetTimerinterval: 3000onTriggered: notificationMessage = ""}}