/******************************************************************** This file is part of the KDE project. Copyright (C) 2014 Aleix Pol Gonzalez This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ 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 2.0 as PlasmaComponents import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.plasma.private.sessions 2.0 import "../components" 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 readonly property bool lightBackground: Math.max(PlasmaCore.ColorScope.backgroundColor.r, PlasmaCore.ColorScope.backgroundColor.g, PlasmaCore.ColorScope.backgroundColor.b) > 0.5 colorGroup: PlasmaCore.Theme.ComplementaryColorGroup Connections { target: authenticator onFailed: { root.notification = i18nd("plasma_lookandfeel_org.kde.lookandfeel","Unlocking failed"); } onGraceLockedChanged: { if (!authenticator.graceLocked) { root.notification = ""; root.clearPassword(); } } onMessage: { root.notification = msg; } onError: { root.notification = err; } } SessionManagement { id: sessionManagement } Connections { target: sessionManagement onAboutToSuspend: { mainBlock.mainPasswordBox.text = ""; } } SessionsModel { id: sessionsModel showNewSessionEntry: false } PlasmaCore.DataSource { id: keystateSource engine: "keystate" connectedSources: "Caps Lock" } Loader { id: changeSessionComponent active: false source: "ChangeSession.qml" visible: false } 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 drag.filterChildren: true onPressed: 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.onEscapePressed: { uiVisible = !uiVisible; if (inputPanel.keyboardActive) { inputPanel.showHide(); } if (!uiVisible) { mainBlock.mainPasswordBox.text = ""; } } Keys.onPressed: { uiVisible = true; event.accepted = false; } Timer { id: fadeoutTimer interval: 10000 onTriggered: { if (!lockScreenRoot.blockUI) { lockScreenRoot.uiVisible = false; } } } Component.onCompleted: PropertyAnimation { id: launchAnimation; target: lockScreenRoot; property: "opacity"; from: 0; to: 1; duration: 1000 } 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: 300; easing.type: Easing.InQuad} PropertyAnimation { properties: "opacity"; duration: 300} 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 z: -3 } DropShadow { id: clockShadow anchors.fill: clock source: clock visible: !softwareRendering horizontalOffset: 1 verticalOffset: 1 radius: 6 samples: 14 spread: 0.3 color: lockScreenUi.lightBackground ? PlasmaCore.ColorScope.backgroundColor : "black" // black matches Breeze window decoration and desktopcontainment Behavior on opacity { OpacityAnimator { duration: 1000 easing.type: Easing.InOutQuad } } } Clock { id: clock property Item shadow: clockShadow anchors.horizontalCenter: parent.horizontalCenter y: (mainBlock.userList.y + mainStack.y)/2 - height/2 visible: y > 0 Layout.alignment: Qt.AlignBaseline } ListModel { id: users Component.onCompleted: { users.append({name: kscreenlocker_userName, realName: kscreenlocker_userName, icon: kscreenlocker_userImage, }) } } StackView { id: mainStack anchors.centerIn: parent height: lockScreenRoot.height + units.gridUnit * 3 width: parent.width / 3 focus: true //StackView is an implicit focus scope, so we need to give this focus so the item inside will have it initialItem: MainBlock { id: mainBlock lockScreenUiVisible: lockScreenRoot.uiVisible showUserList: userList.y + mainStack.y > 0 Stack.onStatusChanged: { // prepare for presenting again to the user if (Stack.status == Stack.Activating) { mainPasswordBox.remove(0, mainPasswordBox.length) mainPasswordBox.focus = true } } userListModel: users notificationMessage: { var text = "" if (keystateSource.data["Caps Lock"]["Locked"]) { text += i18nd("plasma_lookandfeel_org.kde.lookandfeel","Caps Lock is on") if (root.notification) { text += " • " } } text += root.notification return text } onLoginRequest: { root.notification = "" authenticator.tryUnlock(password) } actionItems: [ 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 //Button gets cut off on smaller displays without this. anchors{ verticalCenter: parent.top } } ] Loader { 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 = "../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 opacity: 1 } }, State { name: "hidden" PropertyChanges { target: mainStack y: 0 } PropertyChanges { target: inputPanel y: lockScreenRoot.height - lockScreenRoot.height/4 opacity: 0 } } ] transitions: [ Transition { from: "hidden" to: "visible" SequentialAnimation { ScriptAction { script: { inputPanel.item.activated = true; Qt.inputMethod.show(); } } ParallelAnimation { NumberAnimation { target: mainStack property: "y" duration: units.longDuration easing.type: Easing.InOutQuad } NumberAnimation { target: inputPanel property: "y" duration: units.longDuration easing.type: Easing.OutQuad } OpacityAnimator { target: inputPanel duration: units.longDuration easing.type: Easing.OutQuad } } } }, Transition { from: "visible" to: "hidden" SequentialAnimation { ParallelAnimation { NumberAnimation { target: mainStack property: "y" duration: units.longDuration easing.type: Easing.InOutQuad } NumberAnimation { target: inputPanel property: "y" duration: units.longDuration easing.type: Easing.InQuad } OpacityAnimator { target: inputPanel duration: units.longDuration easing.type: Easing.InQuad } } ScriptAction { script: { 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}) sessionsModel.switchUser(userListCurrentModelData.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: units.largeSpacing PlasmaComponents.Button { Layout.fillWidth: true font.pointSize: theme.defaultFont.pointSize + 1 text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Switch to This Session") onClicked: initSwitchSession() visible: sessionsModel.count > 0 } PlasmaComponents.Button { Layout.fillWidth: true font.pointSize: 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: units.largeSpacing } } RowLayout { id: footer z: -2 anchors { bottom: parent.bottom left: parent.left right: parent.right margins: units.smallSpacing } PlasmaComponents.ToolButton { text: i18ndc("plasma_lookandfeel_org.kde.lookandfeel", "Button to show/hide virtual keyboard", "Virtual Keyboard") iconName: inputPanel.keyboardActive ? "input-keyboard-virtual-on" : "input-keyboard-virtual-off" onClicked: inputPanel.showHide() visible: inputPanel.status == Loader.Ready } KeyboardLayoutButton { } Item { Layout.fillWidth: true } Battery {} } } Component.onCompleted: { // version support checks if (root.interfaceVersion < 1) { // ksmserver of 5.4, with greeter of 5.5 root.viewVisible = true; } } }