/* SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez SPDX-License-Identifier: GPL-2.0-or-later */ import QtQml 2.15 import QtQuick 2.8 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.1 import Qt5Compat.GraphicalEffects import org.kde.plasma.components 3.0 as PlasmaComponents3 import org.kde.plasma.workspace.components 2.0 as PW import org.kde.plasma.private.keyboardindicator as KeyboardIndicator import org.kde.kirigami 2.20 as Kirigami import org.kde.kscreenlocker 1.0 as ScreenLocker import org.kde.plasma.private.sessions 2.0 import org.kde.breeze.components Item { id: lockScreenUi // If we're using software rendering, draw outlines instead of shadows // See https://bugs.kde.org/show_bug.cgi?id=398317 readonly property bool softwareRendering: GraphicsInfo.api === GraphicsInfo.Software property bool hadPrompt: false function handleMessage(msg) { if (!root.notification) { root.notification += msg; } else if (root.notification.includes(msg)) { root.notificationRepeated(); } else { root.notification += "\n" + msg } } Kirigami.Theme.inherit: false Kirigami.Theme.colorSet: Kirigami.Theme.Complementary Connections { target: authenticator function onFailed(kind) { if (kind != 0) { // if this is coming from the noninteractive authenticators return; } const msg = i18nd("plasma_shell_org.kde.plasma.desktop", "Unlocking failed"); lockScreenUi.handleMessage(msg); graceLockTimer.restart(); notificationRemoveTimer.restart(); rejectPasswordAnimation.start(); lockScreenUi.hadPrompt = false; } function onSucceeded() { if (lockScreenUi.hadPrompt) { Qt.quit(); } else { mainStack.replace(null, Qt.resolvedUrl("NoPasswordUnlock.qml"), { userListModel: users }, StackView.Immediate, ); mainStack.forceActiveFocus(); } } function onInfoMessageChanged() { lockScreenUi.handleMessage(authenticator.infoMessage); lockScreenUi.hadPrompt = true; } function onErrorMessageChanged() { lockScreenUi.handleMessage(authenticator.errorMessage); } function onPromptChanged(msg) { lockScreenUi.handleMessage(authenticator.prompt); } function onPromptForSecretChanged(msg) { mainBlock.showPassword = false; mainBlock.mainPasswordBox.forceActiveFocus(); lockScreenUi.hadPrompt = true; } } SessionManagement { id: sessionManagement } KeyboardIndicator.KeyState { id: capsLockState key: Qt.Key_CapsLock } Connections { target: sessionManagement function onAboutToSuspend() { root.clearPassword(); } } RejectPasswordAnimation { id: rejectPasswordAnimation target: mainBlock } MouseArea { id: lockScreenRoot property bool uiVisible: false property bool blockUI: mainStack.depth > 1 || mainBlock.mainPasswordBox.text.length > 0 || inputPanel.keyboardActive x: parent.x y: parent.y width: parent.width height: parent.height hoverEnabled: true cursorShape: uiVisible ? Qt.ArrowCursor : Qt.BlankCursor drag.filterChildren: true onPressed: uiVisible = true; onPositionChanged: uiVisible = true; onUiVisibleChanged: { if (blockUI) { fadeoutTimer.running = false; } else if (uiVisible) { fadeoutTimer.restart(); } authenticator.startAuthenticating(); } onBlockUIChanged: { if (blockUI) { fadeoutTimer.running = false; uiVisible = true; } else { fadeoutTimer.restart(); } } 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 (inputPanel.keyboardActive) { inputPanel.showHide(); } root.clearPassword(); } } Keys.onPressed: event => { uiVisible = true; event.accepted = false; } Timer { id: fadeoutTimer interval: 10000 onTriggered: { if (!lockScreenRoot.blockUI) { mainBlock.mainPasswordBox.showPassword = false; lockScreenRoot.uiVisible = false; } } } Timer { id: notificationRemoveTimer interval: 3000 onTriggered: root.notification = "" } Timer { id: graceLockTimer interval: 3000 onTriggered: { root.clearPassword(); authenticator.startAuthenticating(); } } PropertyAnimation { id: launchAnimation target: lockScreenRoot property: "opacity" from: 0 to: 1 duration: Kirigami.Units.veryLongDuration * 2 } Component.onCompleted: launchAnimation.start(); WallpaperFader { anchors.fill: parent state: lockScreenRoot.uiVisible ? "on" : "off" source: wallpaper mainStack: mainStack footer: footer clock: clock } DropShadow { id: clockShadow anchors.fill: clock source: clock visible: !softwareRendering radius: 6 samples: 14 spread: 0.3 color : "black" // shadows should always be black Behavior on opacity { OpacityAnimator { duration: Kirigami.Units.veryLongDuration * 2 easing.type: Easing.InOutQuad } } } Clock { id: clock property Item shadow: clockShadow visible: y > 0 anchors.horizontalCenter: parent.horizontalCenter y: (mainBlock.userList.y + mainStack.y)/2 - height/2 Layout.alignment: Qt.AlignBaseline } ListModel { id: users Component.onCompleted: { users.append({ name: kscreenlocker_userName, realName: kscreenlocker_userName, icon: kscreenlocker_userImage !== "" ? "file://" + kscreenlocker_userImage.split("/").map(encodeURIComponent).join("/") : "", }) } } StackView { id: mainStack anchors { left: parent.left right: parent.right } height: lockScreenRoot.height + Kirigami.Units.gridUnit * 3 focus: 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 scenegraph visible: opacity > 0 initialItem: MainBlock { id: mainBlock lockScreenUiVisible: lockScreenRoot.uiVisible showUserList: userList.y + mainStack.y > 0 enabled: !graceLockTimer.running StackView.onStatusChanged: { // prepare for presenting again to the user if (StackView.status === StackView.Activating) { mainPasswordBox.clear(); mainPasswordBox.focus = true; root.notification = ""; } } userListModel: users notificationMessage: { const parts = []; if (capsLockState.locked) { console.log(authenticator); console.log(JSON.stringify(authenticator)); parts.push(i18nd("plasma_shell_org.kde.plasma.desktop", "Caps Lock is on")); } if (root.notification) { parts.push(root.notification); } return parts.join(" • "); } onPasswordResult: password => { authenticator.respond(password) } actionItems: [ ActionButton { text: i18nd("plasma_shell_org.kde.plasma.desktop", "Eepy") iconSource: "system-suspend" onClicked: root.suspendToRam() visible: root.suspendToRamSupported }, ActionButton { text: i18nd("plasma_shell_org.kde.plasma.desktop", "Hibernate") iconSource: "system-suspend-hibernate" onClicked: root.suspendToDisk() visible: root.suspendToDiskSupported }, ActionButton { text: i18nd("plasma_shell_org.kde.plasma.desktop", "Switch User") iconSource: "system-switch-user" onClicked: { sessionManagement.switchUser(); } visible: sessionManagement.canSwitchUser } ] Loader { Layout.topMargin: Kirigami.Units.smallSpacing // some distance to the password field Layout.fillWidth: true Layout.preferredHeight: item ? item.implicitHeight : 0 active: config.showMediaControls source: "MediaControls.qml" } } } VirtualKeyboardLoader { id: inputPanel z: 1 screenRoot: lockScreenRoot mainStack: mainStack mainBlock: mainBlock passwordField: mainBlock.mainPasswordBox } Loader { z: 2 active: root.viewVisible source: "LockOsd.qml" anchors { horizontalCenter: parent.horizontalCenter bottom: parent.bottom bottomMargin: 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: footer anchors { bottom: parent.bottom left: parent.left right: parent.right margins: Kirigami.Units.smallSpacing } spacing: Kirigami.Units.smallSpacing PlasmaComponents3.ToolButton { id: virtualKeyboardButton focusPolicy: Qt.TabFocus text: i18ndc("plasma_shell_org.kde.plasma.desktop", "Button to show/hide virtual keyboard", "Virtual Keyboard") icon.name: inputPanel.keyboardActive ? "input-keyboard-virtual-on" : "input-keyboard-virtual-off" onClicked: { // Otherwise the password field loses focus and virtual keyboard // keystrokes get eaten mainBlock.mainPasswordBox.forceActiveFocus(); inputPanel.showHide() } visible: inputPanel.status === Loader.Ready Layout.fillHeight: true containmentMask: Item { parent: virtualKeyboardButton anchors.fill: parent anchors.leftMargin: -footer.anchors.margins anchors.bottomMargin: -footer.anchors.margins } } PlasmaComponents3.ToolButton { id: keyboardButton focusPolicy: Qt.TabFocus Accessible.description: i18ndc("plasma_shell_org.kde.plasma.desktop", "Button to change keyboard layout", "Switch layout") icon.name: "input-keyboard" PW.KeyboardLayoutSwitcher { id: keyboardLayoutSwitcher anchors.fill: parent acceptedButtons: Qt.NoButton } text: keyboardLayoutSwitcher.layoutNames.longName onClicked: keyboardLayoutSwitcher.keyboardLayout.switchToNextLayout() visible: keyboardLayoutSwitcher.hasMultipleKeyboardLayouts Layout.fillHeight: true containmentMask: Item { parent: keyboardButton anchors.fill: parent anchors.leftMargin: virtualKeyboardButton.visible ? 0 : -footer.anchors.margins anchors.bottomMargin: -footer.anchors.margins } } Item { Layout.fillWidth: true } Battery {} } } }