diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index e0ba9505..80821248 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -1,4 +1,6 @@ +add_definitions(-DTRANSLATION_DOMAIN=\"plasmashellprivateplugin\") + install(DIRECTORY workspace/ DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/workspace/components) add_subdirectory(shellprivate) add_subdirectory(keyboardlayout) -add_subdirectory(sessionsprivate) \ No newline at end of file +add_subdirectory(sessionsprivate) diff --git a/components/shellprivate/Messages.sh b/components/Messages.sh similarity index 100% rename from components/shellprivate/Messages.sh rename to components/Messages.sh diff --git a/components/sessionsprivate/CMakeLists.txt b/components/sessionsprivate/CMakeLists.txt index 2d8fcf1c..1eca937c 100644 --- a/components/sessionsprivate/CMakeLists.txt +++ b/components/sessionsprivate/CMakeLists.txt @@ -1,26 +1,27 @@ set(sessionsprivateplugin_SRCS sessionsmodel.cpp sessionsprivateplugin.cpp ) qt5_add_dbus_interface(sessionsprivateplugin_SRCS ${SCREENSAVER_DBUS_INTERFACE} screensaver_interface) kconfig_add_kcfg_files(sessionsprivateplugin_SRCS kscreensaversettings.kcfgc) add_library(sessionsprivateplugin SHARED ${sessionsprivateplugin_SRCS}) target_link_libraries(sessionsprivateplugin Qt5::Core Qt5::DBus Qt5::Quick Qt5::Qml Qt5::Gui KF5::CoreAddons KF5::ConfigCore KF5::ConfigGui + KF5::I18n PW::KWorkspace ) install(TARGETS sessionsprivateplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/private/sessions) install(FILES qmldir DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/private/sessions) diff --git a/components/sessionsprivate/sessionsmodel.cpp b/components/sessionsprivate/sessionsmodel.cpp index aaf4a03d..5517bfc6 100644 --- a/components/sessionsprivate/sessionsmodel.cpp +++ b/components/sessionsprivate/sessionsmodel.cpp @@ -1,229 +1,269 @@ /* Copyright 2015 Kai Uwe Broulik Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "sessionsmodel.h" #include #include +#include #include "kscreensaversettings.h" #include "screensaver_interface.h" SessionsModel::SessionsModel(QObject *parent) : QAbstractListModel(parent) , m_screensaverInterface( new org::freedesktop::ScreenSaver(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QDBusConnection::sessionBus(), this) ) { reload(); // wait for the screen locker to be ready before actually switching connect(m_screensaverInterface, &org::freedesktop::ScreenSaver::ActiveChanged, this, [this](bool active) { if (active) { if (m_pendingVt) { m_displayManager.switchVT(m_pendingVt); emit switchedUser(m_pendingVt); } else if (m_pendingReserve) { m_displayManager.startReserve(); emit startedNewSession(); } m_pendingVt = 0; m_pendingReserve = false; } }); } bool SessionsModel::canSwitchUser() const { return const_cast(this)->m_displayManager.isSwitchable() && KAuthorized::authorizeAction(QLatin1String("switch_user")); } bool SessionsModel::canStartNewSession() const { return const_cast(this)->m_displayManager.numReserve() > 0 && KAuthorized::authorizeAction(QLatin1String("start_new_session")); } bool SessionsModel::shouldLock() const { return m_shouldLock; } void SessionsModel::switchUser(int vt, bool shouldLock) { - if (!canSwitchUser() || vt <= 0) { + if (vt < 0) { + startNewSession(shouldLock); + return; + } + + if (!canSwitchUser()) { return; } if (!shouldLock) { m_displayManager.switchVT(vt); emit switchedUser(vt); return; } checkScreenLocked([this, vt](bool locked) { if (locked) { // already locked, switch right away m_displayManager.switchVT(vt); emit switchedUser(vt); } else { m_pendingReserve = false; m_pendingVt = vt; m_screensaverInterface->Lock(); } }); } void SessionsModel::startNewSession(bool shouldLock) { if (!canStartNewSession()) { return; } if (!shouldLock) { m_displayManager.startReserve(); emit startedNewSession(); return; } checkScreenLocked([this](bool locked) { if (locked) { // already locked, switch right away m_displayManager.startReserve(); emit startedNewSession(); } else { m_pendingReserve = true; m_pendingVt = 0; m_screensaverInterface->Lock(); } }); } void SessionsModel::reload() { static QHash kusers; const bool oldShouldLock = m_shouldLock; m_shouldLock = KAuthorized::authorizeAction(QStringLiteral("lock_screen")) && KScreenSaverSettings::autolock(); if (m_shouldLock != oldShouldLock) { emit shouldLockChanged(); } SessList sessions; m_displayManager.localSessions(sessions); const int oldCount = m_data.count(); beginResetModel(); m_data.clear(); m_data.reserve(sessions.count()); foreach (const SessEnt &session, sessions) { if (!session.vt || session.self) { continue; } SessionEntry entry; entry.name = session.user; entry.displayNumber = session.display; entry.vtNumber = session.vt; entry.session = session.session; entry.isTty = session.tty; auto it = kusers.constFind(session.user); if (it != kusers.constEnd()) { entry.realName = it->property(KUser::FullName).toString(); entry.icon = it->faceIconPath(); } else { KUser user(session.user); entry.realName = user.property(KUser::FullName).toString(); entry.icon = user.faceIconPath(); kusers.insert(session.user, user); } m_data.append(entry); } endResetModel(); if (oldCount != m_data.count()) { emit countChanged(); } } void SessionsModel::checkScreenLocked(const std::function &cb) { auto reply = m_screensaverInterface->GetActive(); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, cb](QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (!reply.isError()) { cb(reply.value()); } watcher->deleteLater(); }); } +void SessionsModel::setShowNewSessionEntry(bool showNewSessionEntry) +{ + if (showNewSessionEntry == m_showNewSessionEntry) { + return; + } + + int row = m_data.size(); + if (showNewSessionEntry) { + beginInsertRows(QModelIndex(), row, row); + m_showNewSessionEntry = showNewSessionEntry; + endInsertRows(); + } else { + beginRemoveRows(QModelIndex(), row, row); + m_showNewSessionEntry = showNewSessionEntry; + endRemoveRows(); + } + emit countChanged(); +} + + QVariant SessionsModel::data(const QModelIndex &index, int role) const { - if (index.row() < 0 || index.row() >= m_data.count()) { + if (index.row() < 0 || index.row() > rowCount(QModelIndex())) { return QVariant(); } + if (index.row() == m_data.count()) { + switch (static_cast(role)) { + case Role::RealName: return i18n("New Session"); + case Role::IconName: return QStringLiteral("list-add"); + case Role::Name: return i18n("New Session"); + case Role::DisplayNumber: return 0; //NA + case Role::VtNumber: return -1; //an invalid VtNumber - which we'll use to indicate it's to start a new session + case Role::Session: return 0; //NA + case Role::IsTty: return false; //NA + default: return QVariant(); + } + } + const SessionEntry &item = m_data.at(index.row()); switch (static_cast(role)) { case Role::RealName: return item.realName; case Role::Icon: return item.icon; case Role::Name: return item.name; case Role::DisplayNumber: return item.displayNumber; case Role::VtNumber: return item.vtNumber; case Role::Session: return item.session; case Role::IsTty: return item.isTty; + default: return QVariant(); } - return QVariant(); } int SessionsModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); - return m_data.count(); + return m_data.count() + (m_showNewSessionEntry ? 1 : 0); } QHash SessionsModel::roleNames() const { return { {static_cast(Role::Name), QByteArrayLiteral("name")}, {static_cast(Role::RealName), QByteArrayLiteral("realName")}, {static_cast(Role::Icon), QByteArrayLiteral("icon")}, + {static_cast(Role::IconName), QByteArrayLiteral("iconName")}, {static_cast(Role::DisplayNumber), QByteArrayLiteral("displayNumber")}, {static_cast(Role::VtNumber), QByteArrayLiteral("vtNumber")}, {static_cast(Role::Session), QByteArrayLiteral("session")}, {static_cast(Role::IsTty), QByteArrayLiteral("isTty")} }; } diff --git a/components/sessionsprivate/sessionsmodel.h b/components/sessionsprivate/sessionsmodel.h index 46016565..6381b628 100644 --- a/components/sessionsprivate/sessionsmodel.h +++ b/components/sessionsprivate/sessionsmodel.h @@ -1,110 +1,117 @@ /* Copyright 2015 Kai Uwe Broulik Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef SESSIONSMODEL_H #define SESSIONSMODEL_H #include #include #include class OrgFreedesktopScreenSaverInterface; namespace org { namespace freedesktop { using ScreenSaver = ::OrgFreedesktopScreenSaverInterface; } } struct SessionEntry { QString realName; QString icon; QString name; QString displayNumber; QString session; int vtNumber; bool isTty; }; class KDisplayManager; class SessionsModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(bool canSwitchUser READ canSwitchUser CONSTANT) Q_PROPERTY(bool canStartNewSession READ canStartNewSession CONSTANT) Q_PROPERTY(bool shouldLock READ shouldLock NOTIFY shouldLockChanged) + Q_PROPERTY(bool showNewSessionEntry MEMBER m_showNewSessionEntry WRITE setShowNewSessionEntry NOTIFY showNewSessionEntryChanged) Q_PROPERTY(int count READ rowCount NOTIFY countChanged) public: explicit SessionsModel(QObject *parent = nullptr); ~SessionsModel() override = default; enum class Role { RealName = Qt::DisplayRole, - Icon = Qt::DecorationRole, + Icon = Qt::DecorationRole, //path to a file Name = Qt::UserRole + 1, DisplayNumber, VtNumber, Session, - IsTty + IsTty, + IconName //name of an icon }; bool canSwitchUser() const; bool canStartNewSession() const; bool shouldLock() const; + void setShowNewSessionEntry(bool showNewSessionEntry); + Q_INVOKABLE void reload(); Q_INVOKABLE void switchUser(int vt, bool shouldLock = false); Q_INVOKABLE void startNewSession(bool shouldLock = false); QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QHash roleNames() const override; signals: void shouldLockChanged(); + void showNewSessionEntryChanged(); void countChanged(); void switchedUser(int vt); void startedNewSession(); private: void checkScreenLocked(const std::function &cb); KDisplayManager m_displayManager; QVector m_data; bool m_shouldLock = true; int m_pendingVt = 0; bool m_pendingReserve = false; + bool m_showNewSessionEntry = false; + org::freedesktop::ScreenSaver *m_screensaverInterface = nullptr; }; #endif // SESSIONSMODEL_H diff --git a/components/shellprivate/CMakeLists.txt b/components/shellprivate/CMakeLists.txt index 09b5128b..0d964b96 100644 --- a/components/shellprivate/CMakeLists.txt +++ b/components/shellprivate/CMakeLists.txt @@ -1,49 +1,48 @@ -add_definitions(-DTRANSLATION_DOMAIN=\"plasmashellprivateplugin\") if (KF5TextEditor_FOUND) set(interactiveconsole_SRCS interactiveconsole/interactiveconsole.cpp ) endif (KF5TextEditor_FOUND) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-shellprivate.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-shellprivate.h) set(plasmashellprivateplugin_SRCS widgetexplorer/kcategorizeditemsviewmodels.cpp widgetexplorer/plasmaappletitemmodel.cpp widgetexplorer/openwidgetassistant.cpp widgetexplorer/widgetexplorer.cpp shellprivateplugin.cpp ${interactiveconsole_SRCS} ) add_library(plasmashellprivateplugin SHARED ${plasmashellprivateplugin_SRCS}) target_link_libraries(plasmashellprivateplugin Qt5::Core Qt5::Quick Qt5::Qml Qt5::Gui Qt5::Widgets Qt5::Quick Qt5::Qml KF5::Plasma KF5::PlasmaQuick KF5::I18n KF5::Service KF5::NewStuff KF5::KIOFileWidgets KF5::WindowSystem KF5::Declarative KF5::Activities ) if (KF5TextEditor_FOUND) target_link_libraries(plasmashellprivateplugin KF5::TextEditor) endif (KF5TextEditor_FOUND) install(TARGETS plasmashellprivateplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/private/shell) install(FILES widgetexplorer/plasmoids.knsrc DESTINATION ${KDE_INSTALL_CONFDIR}) install(FILES qmldir DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/private/shell) diff --git a/lookandfeel/contents/components/UserList.qml b/lookandfeel/contents/components/UserList.qml index 1aca3b0d..62c95c74 100644 --- a/lookandfeel/contents/components/UserList.qml +++ b/lookandfeel/contents/components/UserList.qml @@ -1,88 +1,88 @@ /* * Copyright 2014 David Edmundson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 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 Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.2 ListView { id: view readonly property string selectedUser: currentItem ? currentItem.userName : "" readonly property int userItemWidth: units.gridUnit * 8 readonly property int userItemHeight: units.gridUnit * 8 implicitHeight: userItemHeight activeFocusOnTab : true /* * Signals that a user was explicitly selected */ signal userSelected; orientation: ListView.Horizontal highlightRangeMode: ListView.StrictlyEnforceRange //centre align selected item (which implicitly centre aligns the rest preferredHighlightBegin: width/2 - userItemWidth/2 preferredHighlightEnd: preferredHighlightBegin delegate: UserDelegate { avatarPath: model.icon || "" iconSource: model.iconName || "user-identity" name: { var displayName = model.realName || model.name - if (model.vtNumber === undefined) { + if (model.vtNumber === undefined || model.vtNumber < 0) { return displayName } if (!model.session) { return i18ndc("plasma_lookandfeel_org.kde.lookandfeel", "Nobody logged in on that session", "Unused") } var location = "" if (model.isTty) { location = i18ndc("plasma_lookandfeel_org.kde.lookandfeel", "User logged in on console number", "TTY %1", model.vtNumber) } else if (model.displayNumber) { location = i18ndc("plasma_lookandfeel_org.kde.lookandfeel", "User logged in on console (X display number)", "on TTY %1 (Display %2)", model.vtNumber, model.displayNumber) } if (location) { return i18ndc("plasma_lookandfeel_org.kde.lookandfeel", "Username (location)", "%1 (%2)", displayName, location) } } userName: model.name width: userItemWidth height: userItemHeight isCurrent: ListView.isCurrentItem onClicked: { ListView.view.currentIndex = index; ListView.view.userSelected(); } } Keys.onEscapePressed: view.userSelected() Keys.onEnterPressed: view.userSelected() Keys.onReturnPressed: view.userSelected() } diff --git a/lookandfeel/contents/lockscreen/LockScreenUi.qml b/lookandfeel/contents/lockscreen/LockScreenUi.qml index ffc34dc3..bcd989d7 100644 --- a/lookandfeel/contents/lockscreen/LockScreenUi.qml +++ b/lookandfeel/contents/lockscreen/LockScreenUi.qml @@ -1,191 +1,192 @@ /******************************************************************** 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.5 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 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: lockScreenRoot 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; } } SessionsModel { id: sessionsModel + showNewSessionEntry: true } PlasmaCore.DataSource { id: keystateSource engine: "keystate" connectedSources: "Caps Lock" } Loader { id: changeSessionComponent active: false source: "ChangeSession.qml" visible: false } Clock { anchors.bottom: parent.verticalCenter anchors.bottomMargin: units.gridUnit * 13 anchors.horizontalCenter: parent.horizontalCenter } ListModel { id: users Component.onCompleted: { users.append({name: kscreenlocker_userName, realName: kscreenlocker_userName, icon: kscreenlocker_userImage, }) if (sessionsModel.canStartNewSession) { users.append({realName: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "New Session"), name: "__new_session", iconName: "list-add" }) } } } StackView { id: mainStack anchors.fill: parent focus: true //StackView is an implicit focus scope, so we need to give this focus so the item inside will have it initialItem: MainBlock { 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 } onNewSession: { sessionsModel.startNewSession(false); } onLoginRequest: { root.notification = "" authenticator.tryUnlock(password) } actionItems: [ ActionButton { text: i18nd("org.kde.plasma_lookandfeel_org.kde.lookandfeel", "Switch User") iconSource: "system-switch-user" onClicked: mainStack.push(switchSessionPage) visible: sessionsModel.count > 1 && sessionsModel.canSwitchUser } ] } } Component { id: switchSessionPage SessionManagementScreen { userListModel: sessionsModel PlasmaComponents.Button { Layout.fillWidth: true text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Switch Session") onClicked: { sessionsModel.switchUser(userListCurrentModelData.vtNumber) mainStack.pop() } } actionItems: [ ActionButton { iconSource: "go-previous" text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","Back") onClicked: mainStack.pop() } ] } } Loader { active: root.viewVisible source: "LockOsd.qml" anchors { horizontalCenter: parent.horizontalCenter bottom: parent.bottom } } RowLayout { id: footer anchors { bottom: parent.bottom left: parent.left right: parent.right margins: units.smallSpacing } KeyboardLayoutButton { } Item { Layout.fillWidth: true } } Component.onCompleted: { // version support checks if (root.interfaceVersion < 1) { // ksmserver of 5.4, with greeter of 5.5 root.viewVisible = true; } } } diff --git a/lookandfeel/contents/userswitcher/UserSwitcher.qml b/lookandfeel/contents/userswitcher/UserSwitcher.qml index e748a937..eb3ea547 100644 --- a/lookandfeel/contents/userswitcher/UserSwitcher.qml +++ b/lookandfeel/contents/userswitcher/UserSwitcher.qml @@ -1,96 +1,98 @@ /*************************************************************************** * Copyright (C) 2015 Kai Uwe Broulik * * * * 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ import QtQuick 2.2 import QtQuick.Layouts 1.1 import QtQuick.Controls 1.1 as Controls 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: root colorGroup: PlasmaCore.Theme.ComplementaryColorGroup signal dismissed height:screenGeometry.height width: screenGeometry.width Rectangle { anchors.fill: parent color: PlasmaCore.ColorScope.backgroundColor opacity: 0.5 } SessionsModel { id: sessionsModel + showNewSessionEntry: true + // the calls takes place asynchronously; if we were to dismiss the dialog right // after startNewSession/switchUser we would be destroyed before the reply // returned leaving us do nothing (Bug 356945) onStartedNewSession: root.dismissed() onSwitchedUser: root.dismissed() } Controls.Action { onTriggered: root.dismissed() shortcut: "Escape" } Clock { anchors.bottom: parent.verticalCenter anchors.bottomMargin: units.gridUnit * 13 anchors.horizontalCenter: parent.horizontalCenter } SessionManagementScreen { id: block anchors.fill: parent userListModel: sessionsModel RowLayout { PlasmaComponents.Button { text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","Cancel") onClicked: root.dismissed() } PlasmaComponents.Button { id: commitButton text: i18nd("plasma_lookandfeel_org.kde.lookandfeel","Switch") visible: sessionsModel.count > 0 onClicked: { sessionsModel.switchUser(block.userListCurrentModelData.vtNumber, sessionsModel.shouldLock) } Controls.Action { onTriggered: commitButton.clicked() shortcut: "Return" } Controls.Action { onTriggered: commitButton.clicked() shortcut: "Enter" // on numpad } } } } }