mirror of
https://invent.kde.org/plasma/plasma-desktop.git
synced 2026-11-03 14:49:44 -09:00
534 lines
20 KiB
QML
534 lines
20 KiB
QML
/*
|
|
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: Bound
|
|
|
|
import QtQml
|
|
import QtQuick
|
|
import QtQuick.Controls
|
|
import QtQuick.Layouts
|
|
import Qt5Compat.GraphicalEffects
|
|
|
|
import org.kde.plasma.components as PlasmaComponents3
|
|
import org.kde.plasma.private.accessibility
|
|
import org.kde.plasma.workspace.components as PW
|
|
import org.kde.plasma.workspace.keyboardlayout as Keyboards
|
|
import org.kde.plasma.private.keyboardindicator as KeyboardIndicator
|
|
import org.kde.kirigami as Kirigami
|
|
import org.kde.kscreenlocker as ScreenLocker
|
|
import org.kde.ki18n
|
|
|
|
import org.kde.plasma.private.sessions
|
|
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
|
|
// 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 selected
|
|
|
|
function 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 pendingPassword
|
|
|
|
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 = 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: 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 seenPositionChange: false
|
|
property bool blockUI: containsMouse && (mainStack.depth > 1 || mainBlock.mainPasswordBox.text.length > 0 || Keyboards.KWinVirtualKeyboard.visible)
|
|
|
|
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 = 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 state
|
|
interval: 1000
|
|
running: !authenticator.unlocked && parent.uiVisible
|
|
repeat: true
|
|
onTriggered: authenticator.startAuthenticating()
|
|
}
|
|
|
|
onBlockUIChanged: {
|
|
if (blockUI) {
|
|
fadeoutTimer.running = false;
|
|
uiVisible = true;
|
|
} else {
|
|
fadeoutTimer.restart();
|
|
}
|
|
}
|
|
ScreenLocker.ActiveScreenMonitor {
|
|
id: activeScreenMonitor
|
|
lockscreenState: ScreenLocker.LockscreenState
|
|
window: lockScreenRoot.Window.window
|
|
onActiveChanged: {
|
|
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: fadeoutTimer
|
|
interval: 10000
|
|
onTriggered: {
|
|
if (!lockScreenRoot.blockUI) {
|
|
mainBlock.mainPasswordBox.showPassword = false;
|
|
lockScreenRoot.uiVisible = false;
|
|
}
|
|
}
|
|
}
|
|
Timer {
|
|
id: notificationRemoveTimer
|
|
interval: 3000
|
|
onTriggered: root.notification = ""
|
|
}
|
|
|
|
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"
|
|
mainStack: mainStack
|
|
footer: footer
|
|
clock: clock
|
|
alwaysShowClock: config.alwaysShowClock && !config.hideClockWhenIdle
|
|
|
|
Component.onCompleted: source = wallpaper
|
|
}
|
|
|
|
DropShadow {
|
|
id: clockShadow
|
|
anchors.fill: clock
|
|
source: clock
|
|
visible: !lockScreenUi.softwareRendering && config.alwaysShowClock
|
|
radius: 7
|
|
verticalOffset: 0.8
|
|
samples: 15
|
|
spread: 0.2
|
|
color : Qt.rgba(0, 0, 0, 0.7)
|
|
opacity: lockScreenRoot.uiVisible ? 0 : 1
|
|
Behavior on opacity {
|
|
OpacityAnimator {
|
|
duration: Kirigami.Units.veryLongDuration * 2
|
|
easing.type: Easing.InOutQuad
|
|
}
|
|
}
|
|
}
|
|
|
|
Clock {
|
|
id: clock
|
|
property Item shadow: clockShadow
|
|
visible: y > 0 && config.alwaysShowClock
|
|
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("/")
|
|
: "",
|
|
})
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: authenticator
|
|
function onPamTimeoutChanged(): void {
|
|
if (!authenticator.pamTimeout && lockScreenUi.pendingPassword.length > 0) {
|
|
authenticator.respond(lockScreenUi.pendingPassword);
|
|
lockScreenUi.pendingPassword = "";
|
|
mainBlock.enabled = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
|
|
StackView.onStatusChanged: {
|
|
// prepare for presenting again to the user
|
|
if (StackView.status === StackView.Activating) {
|
|
mainPasswordBox.clear();
|
|
mainPasswordBox.focus = true;
|
|
root.notification = "";
|
|
}
|
|
}
|
|
userListModel: users
|
|
|
|
authenticationTypeItem: RowLayout {
|
|
Layout.alignment: Qt.AlignHCenter
|
|
Layout.fillWidth: true
|
|
visible: authenticationTypeRepeater.count > 1
|
|
|
|
Repeater {
|
|
id: authenticationTypeRepeater
|
|
model: ScreenLocker.AuthenticatorModel
|
|
|
|
delegate: PlasmaComponents3.Button {
|
|
required property int type
|
|
required property string iconName
|
|
required property bool passwordField
|
|
required property bool expectingPrompt
|
|
required property string tooltip
|
|
required property bool showPrompt
|
|
required property bool functional
|
|
|
|
onFunctionalChanged: {
|
|
if (functional) {
|
|
return
|
|
}
|
|
root.notification = ""
|
|
mainBlock.passwordInputVisible = false
|
|
lockScreenUi.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: functional
|
|
icon.name: iconName
|
|
icon.color: functional ? undefined : Kirigami.Theme.negativeTextColor
|
|
checked: authenticator.authenticator === type
|
|
onClicked: {
|
|
authenticator.authenticator = type
|
|
root.notification = ""
|
|
mainBlock.passwordInputVisible = passwordField
|
|
lockScreenUi.expectingPrompt = expectingPrompt
|
|
lockScreenUi.showPrompt = showPrompt
|
|
}
|
|
|
|
PlasmaComponents3.ToolTip.text: tooltip
|
|
PlasmaComponents3.ToolTip.delay: Kirigami.Units.toolTipDelay
|
|
PlasmaComponents3.ToolTip.visible: hovered
|
|
|
|
Component.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 = password
|
|
mainBlock.enabled = false
|
|
} else {
|
|
authenticator.respond(password)
|
|
}
|
|
}
|
|
|
|
actionItems: [
|
|
ActionButton {
|
|
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
|
|
text: 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.AlignTop
|
|
text: 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.AlignTop
|
|
text: 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 field
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: item ? item.implicitHeight : 0
|
|
active: config.showMediaControls
|
|
source: "MediaControls.qml"
|
|
}
|
|
}
|
|
}
|
|
|
|
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: 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 eaten
|
|
mainBlock.mainPasswordBox.forceActiveFocus();
|
|
Keyboards.KWinVirtualKeyboard.forceActivate();
|
|
}
|
|
}
|
|
|
|
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 {}
|
|
}
|
|
}
|
|
}
|