/*SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com>SPDX-FileCopyrightText: 2026 Kristen McWilliam <kristen@kde.org>SPDX-License-Identifier: GPL-2.0-or-later*/pragma ComponentBehavior: Boundimport QtQmlimport QtQuickimport QtQuick.Controlsimport QtQuick.Layoutsimport Qt5Compat.GraphicalEffectsimport org.kde.plasma.components as PlasmaComponents3import org.kde.plasma.private.accessibilityimport org.kde.plasma.workspace.components as PWimport org.kde.plasma.workspace.keyboardlayout as Keyboardsimport org.kde.plasma.private.keyboardindicator as KeyboardIndicatorimport org.kde.kirigami as Kirigamiimport org.kde.kscreenlocker as ScreenLockerimport org.kde.ki18nimport org.kde.plasma.private.sessionsimport org.kde.breeze.componentsItem {id: lockScreenUi// 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.Software// Whether we expect a given authenticator to prompt for a credential at all.// This is a shortcut around the NoPasswordUnlock.qml screen when dealing with prompt-less authenticators.property bool expectingPrompt: false // this is set for real when an authenticator is selected// <!* Whether we are expected to visualize the prompt message for the authenticator *>property bool showPrompt: false // this is set for real when an authenticator is selectedfunction handleMessage(msg) {if (!msg) {return;}if (!root.notification) {root.notification += msg;} else if (root.notification.includes(msg)) {root.notificationRepeated();} else {root.notification += "\n" + msg}lockScreenRoot.uiVisible = true}property string pendingPasswordKirigami.Theme.inherit: falseKirigami.Theme.colorSet: Kirigami.Theme.ComplementaryConnections {target: authenticatorfunction onFailed(kind) {if (kind != 0) { // if this is coming from the noninteractive authenticatorsreturn;}const msg = i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:status", "Unlocking failed");lockScreenUi.handleMessage(msg);root.clearPassword();authenticator.startAuthenticating();notificationRemoveTimer.restart();rejectPasswordAnimation.start();}function onSucceeded() {// This is important. It stops the all pending authentication processes, preventing timeouts etc.authenticator.stopAuthenticating()if (!lockScreenUi.expectingPrompt) {Qt.quit()}if (authenticator.hadPrompt) {Qt.quit();} else {mainStack.replace(null, Qt.resolvedUrl("NoPasswordUnlock.qml"),{userListModel: users},StackView.Immediate,);mainStack.forceActiveFocus();}}function onInfoMessageChanged() {lockScreenUi.handleMessage(authenticator.infoMessage);}function onErrorMessageChanged() {lockScreenUi.handleMessage(authenticator.errorMessage);}function onPromptChanged() {lockScreenUi.handleMessage(authenticator.prompt);}function onPromptForSecretChanged() {mainBlock.showPassword = false;mainBlock.mainPasswordBox.forceActiveFocus();if (lockScreenUi.showPrompt) {lockScreenUi.handleMessage(authenticator.promptForSecret);}}}SessionManagement {id: sessionManagement}AccessibilityStatus {id: accessibilityStatus}KeyboardIndicator.KeyState {id: capsLockStatekey: Qt.Key_CapsLock}Connections {target: sessionManagementfunction onAboutToSuspend() {root.clearPassword();}}RejectPasswordAnimation {id: rejectPasswordAnimationtarget: mainBlock}MouseArea {id: lockScreenRootproperty bool uiVisible: falseproperty bool seenPositionChange: falseproperty bool blockUI: containsMouse && (mainStack.depth > 1 || mainBlock.mainPasswordBox.text.length > 0 || Keyboards.KWinVirtualKeyboard.visible)x: parent.xy: parent.ywidth: parent.widthheight: parent.heighthoverEnabled: truecursorShape: uiVisible ? Qt.ArrowCursor : Qt.BlankCursordrag.filterChildren: trueonPressed: uiVisible = true;onPositionChanged: {uiVisible = seenPositionChange;seenPositionChange = true;}onUiVisibleChanged: {if (uiVisible) {Window.window.requestActivate();}if (blockUI) {fadeoutTimer.running = false;} else if (uiVisible) {fadeoutTimer.restart();}authenticator.startAuthenticating();}Timer { // heart beat keeping the backend active for as long as we are in visible stateinterval: 1000running: !authenticator.unlocked && parent.uiVisiblerepeat: trueonTriggered: authenticator.startAuthenticating()}onBlockUIChanged: {if (blockUI) {fadeoutTimer.running = false;uiVisible = true;} else {fadeoutTimer.restart();}}ScreenLocker.ActiveScreenMonitor {id: activeScreenMonitorlockscreenState: ScreenLocker.LockscreenStatewindow: lockScreenRoot.Window.windowonActiveChanged: {if(!active) {lockScreenRoot.uiVisible = false;}}}Keys.onEscapePressed: {// If the escape key is pressed, kscreenlocker will turn off the screen.// We do not want to show the password prompt in this case.if (uiVisible) {uiVisible = false;if (Keyboards.KWinVirtualKeyboard.visible) {Keyboards.KWinVirtualKeyboard.active = false;}root.clearPassword();}}Keys.onPressed: event => {uiVisible = true;event.accepted = false;}Timer {id: fadeoutTimerinterval: 10000onTriggered: {if (!lockScreenRoot.blockUI) {mainBlock.mainPasswordBox.showPassword = false;lockScreenRoot.uiVisible = false;}}}Timer {id: notificationRemoveTimerinterval: 3000onTriggered: root.notification = ""}PropertyAnimation {id: launchAnimationtarget: lockScreenRootproperty: "opacity"from: 0to: 1duration: Kirigami.Units.veryLongDuration * 2}Component.onCompleted: launchAnimation.start();WallpaperFader {anchors.fill: parentstate: lockScreenRoot.uiVisible ? "on" : "off"mainStack: mainStackfooter: footerclock: clockalwaysShowClock: config.alwaysShowClock && !config.hideClockWhenIdleComponent.onCompleted: source = wallpaper}DropShadow {id: clockShadowanchors.fill: clocksource: clockvisible: !lockScreenUi.softwareRendering && config.alwaysShowClockradius: 7verticalOffset: 0.8samples: 15spread: 0.2color : Qt.rgba(0, 0, 0, 0.7)opacity: lockScreenRoot.uiVisible ? 0 : 1Behavior on opacity {OpacityAnimator {duration: Kirigami.Units.veryLongDuration * 2easing.type: Easing.InOutQuad}}}Clock {id: clockproperty Item shadow: clockShadowvisible: y > 0 && config.alwaysShowClockanchors.horizontalCenter: parent.horizontalCentery: (mainBlock.userList.y + mainStack.y)/2 - height/2Layout.alignment: Qt.AlignBaseline}ListModel {id: usersComponent.onCompleted: {users.append({name: kscreenlocker_userName,realName: kscreenlocker_userName,icon: kscreenlocker_userImage !== ""? "file://" + kscreenlocker_userImage.split("/").map(encodeURIComponent).join("/"): "",})}}Connections {target: authenticatorfunction onPamTimeoutChanged(): void {if (!authenticator.pamTimeout && lockScreenUi.pendingPassword.length > 0) {authenticator.respond(lockScreenUi.pendingPassword);lockScreenUi.pendingPassword = "";mainBlock.enabled = true;}}}StackView {id: mainStackanchors {left: parent.leftright: parent.right}height: lockScreenRoot.height + Kirigami.Units.gridUnit * 3focus: true //StackView is an implicit focus scope, so we need to give this focus so the item inside will have it// this isn't implicit, otherwise items still get processed for the scenegraphvisible: opacity > 0initialItem: MainBlock {id: mainBlocklockScreenUiVisible: lockScreenRoot.uiVisibleshowUserList: userList.y + mainStack.y > 0StackView.onStatusChanged: {// prepare for presenting again to the userif (StackView.status === StackView.Activating) {mainPasswordBox.clear();mainPasswordBox.focus = true;root.notification = "";}}userListModel: usersauthenticationTypeItem: RowLayout {Layout.alignment: Qt.AlignHCenterLayout.fillWidth: truevisible: authenticationTypeRepeater.count > 1Repeater {id: authenticationTypeRepeatermodel: ScreenLocker.AuthenticatorModeldelegate: PlasmaComponents3.Button {required property int typerequired property string iconNamerequired property bool passwordFieldrequired property bool expectingPromptrequired property string tooltiprequired property bool showPromptrequired property bool functionalonFunctionalChanged: {if (functional) {return}root.notification = ""mainBlock.passwordInputVisible = falselockScreenUi.handleMessage(KI18n.i18ndc("plasma_shell_org.kde.plasma.desktop","@info:status","This authentication method is not functioning correctly. Please try another method, and check your system configuration."))}// Be careful if you remove this to ensure defunct handling is present in all scenarios (e.g. onClicked)enabled: functionalicon.name: iconNameicon.color: functional ? undefined : Kirigami.Theme.negativeTextColorchecked: authenticator.authenticator === typeonClicked: {authenticator.authenticator = typeroot.notification = ""mainBlock.passwordInputVisible = passwordFieldlockScreenUi.expectingPrompt = expectingPromptlockScreenUi.showPrompt = showPrompt}PlasmaComponents3.ToolTip.text: tooltipPlasmaComponents3.ToolTip.delay: Kirigami.Units.toolTipDelayPlasmaComponents3.ToolTip.visible: hoveredComponent.onCompleted: {if (checked) {clicked()}}}}}notificationMessage: {const parts = [];if (capsLockState.locked) {parts.push(i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:status", "Caps Lock is on"));}if (accessibilityStatus.slowKeysEnabled) {parts.push(i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:status", "Slow Keys are enabled"));}if (root.notification) {parts.push(root.notification);}return parts.join(" • ");}onPasswordResult: password => {if (authenticator.pamTimeout) {lockScreenUi.pendingPassword = passwordmainBlock.enabled = false} else {authenticator.respond(password)}}actionItems: [ActionButton {Layout.alignment: Qt.AlignHCenter | Qt.AlignToptext: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "Slee&p")icon.name: "system-suspend"onClicked: sessionManagement.suspend()visible: sessionManagement.canSuspend},ActionButton {Layout.alignment: Qt.AlignHCenter | Qt.AlignToptext: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "&Hibernate")icon.name: "system-suspend-hibernate"onClicked: sessionManagement.hibernate()visible: sessionManagement.canHibernate},ActionButton {Layout.alignment: Qt.AlignHCenter | Qt.AlignToptext: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "Switch &User")icon.name: "system-switch-user"onClicked: {sessionManagement.switchUser();}visible: sessionManagement.canSwitchUser}]Loader {Layout.topMargin: Kirigami.Units.smallSpacing // some distance to the password fieldLayout.fillWidth: trueLayout.preferredHeight: item ? item.implicitHeight : 0active: config.showMediaControlssource: "MediaControls.qml"}}}Loader {z: 2active: root.viewVisiblesource: "LockOsd.qml"anchors {horizontalCenter: parent.horizontalCenterbottom: parent.bottombottomMargin: Kirigami.Units.gridUnit}}// 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 SDDM Main.qml footer.RowLayout {id: footeranchors {bottom: parent.bottomleft: parent.leftright: parent.rightmargins: Kirigami.Units.smallSpacing}spacing: Kirigami.Units.smallSpacingPlasmaComponents3.ToolButton {id: virtualKeyboardButtonfocusPolicy: Qt.TabFocustext: Keyboards.KWinVirtualKeyboard.visible? i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "Hide On-Screen Keyboard"): i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "Show On-Screen Keyboard")icon.name: Keyboards.KWinVirtualKeyboard.visible ? "input-keyboard-virtual-hide" : "input-keyboard-virtual-show"onClicked: {if (Keyboards.KWinVirtualKeyboard.visible) {Keyboards.KWinVirtualKeyboard.active = false;} else {// Otherwise the password field loses focus and on-screen keyboard// keystrokes get eatenmainBlock.mainPasswordBox.forceActiveFocus();Keyboards.KWinVirtualKeyboard.forceActivate();}}Layout.fillHeight: truecontainmentMask: Item {parent: virtualKeyboardButtonanchors.fill: parentanchors.leftMargin: -footer.anchors.marginsanchors.bottomMargin: -footer.anchors.margins}}PlasmaComponents3.ToolButton {id: keyboardButtonfocusPolicy: Qt.TabFocusAccessible.description: i18ndc("plasma_shell_org.kde.plasma.desktop", "Button to change keyboard layout", "Switch layout")icon.name: "input-keyboard"PW.KeyboardLayoutSwitcher {id: keyboardLayoutSwitcheranchors.fill: parentacceptedButtons: Qt.NoButton}text: keyboardLayoutSwitcher.layoutNames.longNameonClicked: keyboardLayoutSwitcher.keyboardLayout.switchToNextLayout()visible: keyboardLayoutSwitcher.hasMultipleKeyboardLayoutsLayout.fillHeight: truecontainmentMask: Item {parent: keyboardButtonanchors.fill: parentanchors.leftMargin: virtualKeyboardButton.visible ? 0 : -footer.anchors.marginsanchors.bottomMargin: -footer.anchors.margins}}Item {Layout.fillWidth: true}Battery {}}}}