You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
614 lines
22 KiB
614 lines
22 KiB
/* |
|
SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com> |
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later |
|
*/ |
|
|
|
import QtQml 2.15 |
|
import QtQuick 2.8 |
|
import QtQuick.Controls 1.1 |
|
import QtQuick.Layouts 1.1 |
|
import QtGraphicalEffects 1.0 |
|
|
|
import org.kde.plasma.core 2.0 as PlasmaCore |
|
import org.kde.plasma.components 3.0 as PlasmaComponents3 |
|
import org.kde.plasma.workspace.components 2.0 as PW |
|
|
|
import org.kde.plasma.private.sessions 2.0 |
|
import "../components" |
|
import "../components/animation" |
|
|
|
PlasmaCore.ColorScope { |
|
|
|
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; |
|
|
|
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup |
|
|
|
Connections { |
|
target: authenticator |
|
function onFailed() { |
|
if (root.notification) { |
|
root.notification += "\n" |
|
} |
|
root.notification += i18nd("plasma_lookandfeel_org.kde.lookandfeel","Unlocking failed"); |
|
graceLockTimer.restart(); |
|
notificationRemoveTimer.restart(); |
|
rejectPasswordAnimation.start(); |
|
lockScreenUi.hadPrompt = false; |
|
} |
|
|
|
function onSucceeded() { |
|
if (lockScreenUi.hadPrompt) { |
|
Qt.quit(); |
|
} else { |
|
mainStack.push(Qt.resolvedUrl("NoPasswordUnlock.qml"), |
|
{"userListModel": users}); |
|
mainStack.forceActiveFocus(); |
|
} |
|
} |
|
|
|
function onInfoMessage(msg) { |
|
if (root.notification) { |
|
root.notification += "\n" |
|
} |
|
root.notification += msg; |
|
} |
|
|
|
function onErrorMessage(msg) { |
|
if (root.notification) { |
|
root.notification += "\n" |
|
} |
|
root.notification += msg; |
|
} |
|
|
|
function onPrompt(msg) { |
|
root.notification = msg; |
|
mainBlock.showPassword = true; |
|
mainBlock.mainPasswordBox.forceActiveFocus(); |
|
lockScreenUi.hadPrompt = true; |
|
} |
|
function onPromptForSecret(msg) { |
|
mainBlock.showPassword = false; |
|
mainBlock.mainPasswordBox.forceActiveFocus(); |
|
lockScreenUi.hadPrompt = true; |
|
} |
|
} |
|
|
|
SessionManagement { |
|
id: sessionManagement |
|
} |
|
|
|
Connections { |
|
target: sessionManagement |
|
function onAboutToSuspend() { |
|
root.clearPassword(); |
|
} |
|
} |
|
|
|
SessionsModel { |
|
id: sessionsModel |
|
showNewSessionEntry: false |
|
} |
|
|
|
PlasmaCore.DataSource { |
|
id: keystateSource |
|
engine: "keystate" |
|
connectedSources: "Caps Lock" |
|
} |
|
|
|
Loader { |
|
id: changeSessionComponent |
|
active: false |
|
source: "ChangeSession.qml" |
|
visible: false |
|
} |
|
|
|
RejectPasswordAnimation { |
|
id: rejectPasswordAnimation |
|
target: mainBlock |
|
} |
|
|
|
MouseArea { |
|
id: lockScreenRoot |
|
|
|
property bool calledUnlock: false |
|
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 |
|
drag.filterChildren: true |
|
onPressed: uiVisible = true; |
|
onPositionChanged: uiVisible = true; |
|
onUiVisibleChanged: { |
|
if (blockUI) { |
|
fadeoutTimer.running = false; |
|
} else if (uiVisible) { |
|
fadeoutTimer.restart(); |
|
} |
|
if (!calledUnlock) { |
|
calledUnlock = true |
|
authenticator.tryUnlock(); |
|
} |
|
} |
|
onBlockUIChanged: { |
|
if (blockUI) { |
|
fadeoutTimer.running = false; |
|
uiVisible = true; |
|
} else { |
|
fadeoutTimer.restart(); |
|
} |
|
} |
|
Keys.onEscapePressed: { |
|
uiVisible = !uiVisible; |
|
if (inputPanel.keyboardActive) { |
|
inputPanel.showHide(); |
|
} |
|
if (!uiVisible) { |
|
root.clearPassword(); |
|
} |
|
} |
|
Keys.onPressed: { |
|
uiVisible = true; |
|
event.accepted = false; |
|
} |
|
Timer { |
|
id: fadeoutTimer |
|
interval: 10000 |
|
onTriggered: { |
|
if (!lockScreenRoot.blockUI) { |
|
lockScreenRoot.uiVisible = false; |
|
} |
|
} |
|
} |
|
Timer { |
|
id: notificationRemoveTimer |
|
interval: 3000 |
|
onTriggered: root.notification = "" |
|
} |
|
Timer { |
|
id: graceLockTimer |
|
interval: 3000 |
|
onTriggered: { |
|
root.clearPassword(); |
|
authenticator.tryUnlock(); |
|
} |
|
} |
|
|
|
Component.onCompleted: PropertyAnimation { id: launchAnimation; target: lockScreenRoot; property: "opacity"; from: 0; to: 1; duration: PlasmaCore.Units.veryLongDuration * 2 } |
|
|
|
states: [ |
|
State { |
|
name: "onOtherSession" |
|
// for slide out animation |
|
PropertyChanges { target: lockScreenRoot; y: lockScreenRoot.height } |
|
// we also change the opacity just to be sure it's not visible even on unexpected screen dimension changes with possible race conditions |
|
PropertyChanges { target: lockScreenRoot; opacity: 0 } |
|
} |
|
] |
|
|
|
transitions: |
|
Transition { |
|
// we only animate switchting to another session, because kscreenlocker doesn't get notified when |
|
// coming from another session back and so we wouldn't know when to trigger the animation exactly |
|
from: "" |
|
to: "onOtherSession" |
|
|
|
PropertyAnimation { id: stateChangeAnimation; properties: "y"; duration: PlasmaCore.Units.longDuration; easing.type: Easing.InQuad} |
|
PropertyAnimation { properties: "opacity"; duration: PlasmaCore.Units.longDuration} |
|
|
|
onRunningChanged: { |
|
// after the animation has finished switch session: since we only animate the transition TO state "onOtherSession" |
|
// and not the other way around, we don't have to check the state we transitioned into |
|
if (/* lockScreenRoot.state == "onOtherSession" && */ !running) { |
|
mainStack.currentItem.switchSession() |
|
} |
|
} |
|
} |
|
|
|
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 |
|
horizontalOffset: 1 |
|
verticalOffset: 1 |
|
radius: 6 |
|
samples: 14 |
|
spread: 0.3 |
|
color : "black" // shadows should always be black |
|
Behavior on opacity { |
|
OpacityAnimator { |
|
duration: PlasmaCore.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, |
|
}) |
|
} |
|
} |
|
|
|
StackView { |
|
id: mainStack |
|
anchors { |
|
left: parent.left |
|
right: parent.right |
|
} |
|
height: lockScreenRoot.height + PlasmaCore.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 |
|
|
|
// This is a focus scope and QQC1 StackView (unlike QQC2) does not set focus to the current item |
|
focus: true |
|
|
|
showUserList: userList.y + mainStack.y > 0 |
|
|
|
enabled: !graceLockTimer.running |
|
|
|
Stack.onStatusChanged: { |
|
// prepare for presenting again to the user |
|
if (Stack.status === Stack.Activating) { |
|
mainPasswordBox.remove(0, mainPasswordBox.length) |
|
mainPasswordBox.focus = true |
|
root.notification = "" |
|
} |
|
} |
|
userListModel: users |
|
|
|
|
|
notificationMessage: { |
|
const parts = []; |
|
if (keystateSource.data["Caps Lock"]["Locked"]) { |
|
parts.push(i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Caps Lock is on")); |
|
} |
|
if (root.notification) { |
|
parts.push(root.notification); |
|
} |
|
return parts.join(" • "); |
|
} |
|
|
|
onPasswordResult: { |
|
authenticator.respond(password) |
|
} |
|
|
|
actionItems: [ |
|
ActionButton { |
|
text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Sleep") |
|
iconSource: "system-suspend" |
|
onClicked: root.suspendToRam() |
|
visible: root.suspendToRamSupported |
|
}, |
|
ActionButton { |
|
text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Hibernate") |
|
iconSource: "system-suspend-hibernate" |
|
onClicked: root.suspendToDisk() |
|
visible: root.suspendToDiskSupported |
|
}, |
|
ActionButton { |
|
text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Switch User") |
|
iconSource: "system-switch-user" |
|
onClicked: { |
|
// If there are no existing sessions to switch to, create a new one instead |
|
if (((sessionsModel.showNewSessionEntry && sessionsModel.count === 1) || |
|
(!sessionsModel.showNewSessionEntry && sessionsModel.count === 0)) && |
|
sessionsModel.canSwitchUser) { |
|
mainStack.pop({immediate:true}) |
|
sessionsModel.startNewSession(true /* lock the screen too */) |
|
lockScreenRoot.state = '' |
|
} else { |
|
mainStack.push(switchSessionPage) |
|
} |
|
} |
|
visible: sessionsModel.canStartNewSession && sessionsModel.canSwitchUser |
|
} |
|
] |
|
|
|
Loader { |
|
Layout.topMargin: PlasmaCore.Units.smallSpacing // some distance to the password field |
|
Layout.fillWidth: true |
|
Layout.preferredHeight: item ? item.implicitHeight : 0 |
|
active: config.showMediaControls |
|
source: "MediaControls.qml" |
|
} |
|
} |
|
|
|
Component.onCompleted: { |
|
if (defaultToSwitchUser) { //context property |
|
// If we are in the only session, then going to the session switcher is |
|
// a pointless extra step; instead create a new session immediately |
|
if (((sessionsModel.showNewSessionEntry && sessionsModel.count === 1) || |
|
(!sessionsModel.showNewSessionEntry && sessionsModel.count === 0)) && |
|
sessionsModel.canStartNewSession) { |
|
sessionsModel.startNewSession(true /* lock the screen too */) |
|
} else { |
|
mainStack.push({ |
|
item: switchSessionPage, |
|
immediate: true, |
|
}); |
|
} |
|
} |
|
} |
|
} |
|
|
|
Loader { |
|
id: inputPanel |
|
state: "hidden" |
|
readonly property bool keyboardActive: item ? item.active : false |
|
anchors { |
|
left: parent.left |
|
right: parent.right |
|
} |
|
function showHide() { |
|
state = state == "hidden" ? "visible" : "hidden"; |
|
} |
|
Component.onCompleted: { |
|
inputPanel.source = Qt.platform.pluginName.includes("wayland") ? "../components/VirtualKeyboard_wayland.qml" : "../components/VirtualKeyboard.qml" |
|
} |
|
|
|
onKeyboardActiveChanged: { |
|
if (keyboardActive) { |
|
state = "visible"; |
|
} else { |
|
state = "hidden"; |
|
} |
|
} |
|
|
|
states: [ |
|
State { |
|
name: "visible" |
|
PropertyChanges { |
|
target: mainStack |
|
y: Math.min(0, lockScreenRoot.height - inputPanel.height - mainBlock.visibleBoundary) |
|
} |
|
PropertyChanges { |
|
target: inputPanel |
|
y: lockScreenRoot.height - inputPanel.height |
|
} |
|
}, |
|
State { |
|
name: "hidden" |
|
PropertyChanges { |
|
target: mainStack |
|
y: 0 |
|
} |
|
PropertyChanges { |
|
target: inputPanel |
|
y: lockScreenRoot.height - lockScreenRoot.height/4 |
|
} |
|
} |
|
] |
|
transitions: [ |
|
Transition { |
|
from: "hidden" |
|
to: "visible" |
|
SequentialAnimation { |
|
ScriptAction { |
|
script: { |
|
inputPanel.item.activated = true; |
|
Qt.inputMethod.show(); |
|
} |
|
} |
|
ParallelAnimation { |
|
NumberAnimation { |
|
target: mainStack |
|
property: "y" |
|
duration: PlasmaCore.Units.longDuration |
|
easing.type: Easing.InOutQuad |
|
} |
|
NumberAnimation { |
|
target: inputPanel |
|
property: "y" |
|
duration: PlasmaCore.Units.longDuration |
|
easing.type: Easing.OutQuad |
|
} |
|
} |
|
} |
|
}, |
|
Transition { |
|
from: "visible" |
|
to: "hidden" |
|
SequentialAnimation { |
|
ParallelAnimation { |
|
NumberAnimation { |
|
target: mainStack |
|
property: "y" |
|
duration: PlasmaCore.Units.longDuration |
|
easing.type: Easing.InOutQuad |
|
} |
|
NumberAnimation { |
|
target: inputPanel |
|
property: "y" |
|
duration: PlasmaCore.Units.longDuration |
|
easing.type: Easing.InQuad |
|
} |
|
OpacityAnimator { |
|
target: inputPanel |
|
duration: PlasmaCore.Units.longDuration |
|
easing.type: Easing.InQuad |
|
} |
|
} |
|
ScriptAction { |
|
script: { |
|
inputPanel.item.activated = false; |
|
Qt.inputMethod.hide(); |
|
} |
|
} |
|
} |
|
} |
|
] |
|
} |
|
|
|
Component { |
|
id: switchSessionPage |
|
SessionManagementScreen { |
|
property var switchSession: finalSwitchSession |
|
|
|
Stack.onStatusChanged: { |
|
if (Stack.status == Stack.Activating) { |
|
focus = true |
|
} |
|
} |
|
|
|
userListModel: sessionsModel |
|
|
|
// initiating animation of lockscreen for session switch |
|
function initSwitchSession() { |
|
lockScreenRoot.state = 'onOtherSession' |
|
} |
|
|
|
// initiating session switch and preparing lockscreen for possible return of user |
|
function finalSwitchSession() { |
|
mainStack.pop({immediate:true}) |
|
if (userListCurrentItem === null) { |
|
console.warn("Switching to an undefined user") |
|
} else if (userListCurrentItem.vtNumber === undefined) { |
|
console.warn("Switching to an undefined VT") |
|
} |
|
sessionsModel.switchUser(userListCurrentItem.vtNumber) |
|
lockScreenRoot.state = '' |
|
} |
|
|
|
Keys.onLeftPressed: userList.decrementCurrentIndex() |
|
Keys.onRightPressed: userList.incrementCurrentIndex() |
|
Keys.onEnterPressed: initSwitchSession() |
|
Keys.onReturnPressed: initSwitchSession() |
|
Keys.onEscapePressed: mainStack.pop() |
|
|
|
ColumnLayout { |
|
Layout.fillWidth: true |
|
spacing: PlasmaCore.Units.largeSpacing |
|
|
|
PlasmaComponents3.Button { |
|
Layout.fillWidth: true |
|
font.pointSize: PlasmaCore.Theme.defaultFont.pointSize + 1 |
|
text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Switch to This Session") |
|
onClicked: initSwitchSession() |
|
visible: sessionsModel.count > 0 |
|
} |
|
|
|
PlasmaComponents3.Button { |
|
Layout.fillWidth: true |
|
font.pointSize: PlasmaCore.Theme.defaultFont.pointSize + 1 |
|
text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Start New Session") |
|
onClicked: { |
|
mainStack.pop({immediate:true}) |
|
sessionsModel.startNewSession(true /* lock the screen too */) |
|
lockScreenRoot.state = '' |
|
} |
|
} |
|
} |
|
|
|
|
|
actionItems: [ |
|
ActionButton { |
|
iconSource: "go-previous" |
|
text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","Back") |
|
onClicked: mainStack.pop() |
|
//Button gets cut off on smaller displays without this. |
|
anchors{ |
|
verticalCenter: parent.top |
|
} |
|
} |
|
] |
|
} |
|
} |
|
|
|
Loader { |
|
active: root.viewVisible |
|
source: "LockOsd.qml" |
|
anchors { |
|
horizontalCenter: parent.horizontalCenter |
|
bottom: parent.bottom |
|
bottomMargin: PlasmaCore.Units.largeSpacing |
|
} |
|
} |
|
|
|
RowLayout { |
|
id: footer |
|
anchors { |
|
bottom: parent.bottom |
|
left: parent.left |
|
right: parent.right |
|
margins: PlasmaCore.Units.smallSpacing |
|
} |
|
|
|
PlasmaComponents3.ToolButton { |
|
focusPolicy: Qt.TabFocus |
|
text: i18ndc("plasma_lookandfeel_org.kde.lookandfeel", "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 |
|
} |
|
|
|
PlasmaComponents3.ToolButton { |
|
focusPolicy: Qt.TabFocus |
|
Accessible.description: i18ndc("plasma_lookandfeel_org.kde.lookandfeel", "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 |
|
} |
|
|
|
Item { |
|
Layout.fillWidth: true |
|
} |
|
|
|
Battery {} |
|
} |
|
} |
|
}
|
|
|