diff --git a/libkworkspace/sessionmanagement.cpp b/libkworkspace/sessionmanagement.cpp index b1f0d771e..943b4c4c6 100644 --- a/libkworkspace/sessionmanagement.cpp +++ b/libkworkspace/sessionmanagement.cpp @@ -1,226 +1,228 @@ /* Copyright (C) 2019 David Edmundson This program is free software; you can redistribute it and/or modify it under the terms of the Lesser 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 Lesser GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "sessionmanagement.h" #include "sessionmanagementbackend.h" #include #include #include #include "kscreenlocker_interface.h" #include "ksmserver_interface.h" #include "logoutprompt_interface.h" #include "screenlocker_interface.h" #include "shutdown_interface.h" // add a constructor with the service names and paths pre-populated class LogoutPromptIface : public OrgKdeLogoutPromptInterface { Q_OBJECT public: LogoutPromptIface() : OrgKdeLogoutPromptInterface(QStringLiteral("org.kde.LogoutPrompt"), QStringLiteral("/LogoutPrompt"), QDBusConnection::sessionBus()) { } }; class ShutdownIface : public OrgKdeShutdownInterface { Q_OBJECT public: ShutdownIface() : OrgKdeShutdownInterface(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/Shutdown"), QDBusConnection::sessionBus()) { } }; SessionManagement::SessionManagement(QObject *parent) : QObject(parent) { auto backend = SessionBackend::self(); connect(backend, &SessionBackend::stateChanged, this, &SessionManagement::stateChanged); connect(backend, &SessionBackend::canShutdownChanged, this, &SessionManagement::canShutdownChanged); connect(backend, &SessionBackend::canRebootChanged, this, &SessionManagement::canRebootChanged); connect(backend, &SessionBackend::canSuspendChanged, this, &SessionManagement::canSuspendChanged); connect(backend, &SessionBackend::canHybridSuspendChanged, this, &SessionManagement::canHybridSuspendChanged); connect(backend, &SessionBackend::canHibernateChanged, this, &SessionManagement::canHibernateChanged); + connect(backend, &SessionBackend::aboutToSuspend, this, &SessionManagement::aboutToSuspend); + connect(backend, &SessionBackend::resumingFromSuspend, this, &SessionManagement::resumingFromSuspend); } bool SessionManagement::canShutdown() const { return canLogout() && SessionBackend::self()->canShutdown(); } bool SessionManagement::canReboot() const { return canLogout() && SessionBackend::self()->canReboot(); } bool SessionManagement::canLogout() const { // checking both is for compatibility with old kiosk configs // authorizeAction is the "correct" one return KAuthorized::authorizeAction(QStringLiteral("logout")) && KAuthorized::authorize(QStringLiteral("logout")); } bool SessionManagement::canSuspend() const { return SessionBackend::self()->canSuspend(); } bool SessionManagement::canHybridSuspend() const { return SessionBackend::self()->canHybridSuspend(); } bool SessionManagement::canHibernate() const { return SessionBackend::self()->canHibernate(); } bool SessionManagement::canSwitchUser() const { return KAuthorized::authorizeAction(QStringLiteral("start_new_session")); } bool SessionManagement::canLock() const { return KAuthorized::authorizeAction(QStringLiteral("lock_screen")); } bool SessionManagement::canSaveSession() const { const KConfigGroup c(KSharedConfig::openConfig(QStringLiteral("ksmserverrc")), "General"); return canLogout() && c.readEntry("loginMode") == QLatin1String("restoreSavedSession"); } SessionManagement::State SessionManagement::state() const { return SessionBackend::self()->state(); } void SessionManagement::requestShutdown(ConfirmationMode confirmationMode) { if (!canShutdown()) { return; } bool confirm = confirmationMode == ConfirmationMode::ForcePrompt; if (confirmationMode == ConfirmationMode::Default) { confirm = SessionBackend::self()->confirmLogout(); } if (confirm) { LogoutPromptIface iface; iface.promptShutDown(); } else { ShutdownIface iface; iface.logoutAndShutdown(); } } void SessionManagement::requestReboot(ConfirmationMode confirmationMode) { if (!canReboot()) { return; } bool confirm = confirmationMode == ConfirmationMode::ForcePrompt; if (confirmationMode == ConfirmationMode::Default) { confirm = SessionBackend::self()->confirmLogout(); } if (confirm) { LogoutPromptIface iface; iface.promptReboot(); } else { ShutdownIface iface; iface.logoutAndReboot(); } } void SessionManagement::requestLogout(ConfirmationMode confirmationMode) { if (!canLogout()) { return; } bool confirm = confirmationMode == ConfirmationMode::ForcePrompt; if (confirmationMode == ConfirmationMode::Default) { confirm = SessionBackend::self()->confirmLogout(); } if (confirm) { LogoutPromptIface iface; iface.promptLogout(); } else { ShutdownIface iface; iface.logout(); } } void SessionManagement::suspend() { if (!canSuspend()) { return; } SessionBackend::self()->suspend(); } void SessionManagement::hybridSuspend() { if (!canHybridSuspend()) { return; } SessionManagement::hybridSuspend(); } void SessionManagement::hibernate() { if (!canHibernate()) { return; } SessionBackend::self()->hibernate(); } void SessionManagement::lock() { if (!canLock()) { return; } OrgFreedesktopScreenSaverInterface iface(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QDBusConnection::sessionBus()); iface.Lock(); } void SessionManagement::switchUser() { if (!canSwitchUser()) { return; } OrgKdeScreensaverInterface iface(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QDBusConnection::sessionBus()); iface.SwitchUser(); } void SessionManagement::saveSession() { if (!canSaveSession()) { return; } OrgKdeKSMServerInterfaceInterface ksmserver(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/KSMServer"), QDBusConnection::sessionBus()); ksmserver.saveCurrentSession(); } #include "sessionmanagement.moc" diff --git a/lookandfeel/contents/lockscreen/LockScreenUi.qml b/lookandfeel/contents/lockscreen/LockScreenUi.qml index 4e0f6a0c6..cac457114 100644 --- a/lookandfeel/contents/lockscreen/LockScreenUi.qml +++ b/lookandfeel/contents/lockscreen/LockScreenUi.qml @@ -1,527 +1,538 @@ /******************************************************************** 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.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 } 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 { left: parent.left right: parent.right } height: lockScreenRoot.height + 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 initialItem: MainBlock { id: mainBlock lockScreenUiVisible: lockScreenRoot.uiVisible showUserList: userList.y + mainStack.y > 0 focus: true // Drop this and hack below when porting this stackview to QQC2 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 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; } } }