diff --git a/components/sessionsprivate/sessionsmodel.cpp b/components/sessionsprivate/sessionsmodel.cpp index 6b71a8bdb..89fd51ed9 100644 --- a/components/sessionsprivate/sessionsmodel.cpp +++ b/components/sessionsprivate/sessionsmodel.cpp @@ -1,273 +1,277 @@ /* 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 (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; + + emit aboutToLockScreen(); 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; + + emit aboutToLockScreen(); 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 (!canStartNewSession()) { return; } 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() > 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(); } } int SessionsModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); 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 6381b6287..01274bf64 100644 --- a/components/sessionsprivate/sessionsmodel.h +++ b/components/sessionsprivate/sessionsmodel.h @@ -1,117 +1,118 @@ /* 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, //path to a file Name = Qt::UserRole + 1, DisplayNumber, VtNumber, Session, 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(); + void aboutToLockScreen(); 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/ksmserver/switchuserdialog.cpp b/ksmserver/switchuserdialog.cpp index e7e4003fe..b959e9b14 100644 --- a/ksmserver/switchuserdialog.cpp +++ b/ksmserver/switchuserdialog.cpp @@ -1,165 +1,172 @@ /* * Copyright 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ #include "switchuserdialog.h" #include "ksmserver_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KSMSwitchUserDialog::KSMSwitchUserDialog(KDisplayManager *dm, KWayland::Client::PlasmaShell *plasmaShell, QWindow *parent) : QQuickView(parent) , m_displayManager(dm) , m_waylandPlasmaShell(plasmaShell) { setClearBeforeRendering(true); setColor(QColor(Qt::transparent)); setFlags(Qt::FramelessWindowHint | Qt::BypassWindowManagerHint); setResizeMode(QQuickView::SizeRootObjectToView); QPoint globalPosition(QCursor::pos()); foreach (QScreen *s, QGuiApplication::screens()) { if (s->geometry().contains(globalPosition)) { setScreen(s); break; } } // Qt doesn't set this on unmanaged windows //FIXME: or does it? if (KWindowSystem::isPlatformX11()) { XChangeProperty( QX11Info::display(), winId(), XInternAtom( QX11Info::display(), "WM_WINDOW_ROLE", False ), XA_STRING, 8, PropModeReplace, (unsigned char *)"logoutdialog", strlen( "logoutdialog" )); XClassHint classHint; classHint.res_name = const_cast("ksmserver"); classHint.res_class = const_cast("ksmserver"); XSetClassHint(QX11Info::display(), winId(), &classHint); } KDeclarative::KDeclarative kdeclarative; kdeclarative.setDeclarativeEngine(engine()); kdeclarative.initialize(); kdeclarative.setupBindings(); } void KSMSwitchUserDialog::init() { rootContext()->setContextProperty(QStringLiteral("screenGeometry"), screen()->geometry()); KPackage::Package package = KPackage::PackageLoader::self()->loadPackage("Plasma/LookAndFeel"); KConfigGroup cg(KSharedConfig::openConfig("kdeglobals"), "KDE"); const QString packageName = cg.readEntry("LookAndFeelPackage", QString()); if (!packageName.isEmpty()) { package.setPath(packageName); } const QString fileName = package.filePath("userswitchermainscript"); if (QFile::exists(fileName)) { setSource(QUrl::fromLocalFile(fileName)); } else { qCWarning(KSMSERVER) << "Couldn't find a theme for the Switch User dialog" << fileName; return; } if (!errors().isEmpty()) { qCWarning(KSMSERVER) << errors(); } connect(rootObject(), SIGNAL(dismissed()), this, SIGNAL(dismissed())); + connect(rootObject(), SIGNAL(ungrab()), this, SLOT(ungrab())); connect(screen(), &QScreen::geometryChanged, this, [this] { setGeometry(screen()->geometry()); }); QQuickView::show(); requestActivate(); KWindowSystem::setState(winId(), NET::SkipTaskbar|NET::SkipPager); + // in case you change this make sure to adjust ungrab() also setKeyboardGrabEnabled(true); } bool KSMSwitchUserDialog::event(QEvent *e) { if (e->type() == QEvent::PlatformSurface) { if (auto pe = dynamic_cast(e)) { switch (pe->surfaceEventType()) { case QPlatformSurfaceEvent::SurfaceCreated: setupWaylandIntegration(); KWindowEffects::enableBlurBehind(winId(), true); break; case QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed: delete m_shellSurface; m_shellSurface = nullptr; break; } } } return QQuickView::event(e); } void KSMSwitchUserDialog::setupWaylandIntegration() { if (m_shellSurface) { // already setup return; } using namespace KWayland::Client; if (!m_waylandPlasmaShell) { return; } Surface *s = Surface::fromWindow(this); if (!s) { return; } m_shellSurface = m_waylandPlasmaShell->createSurface(s, this); // TODO: set a proper window type to indicate to KWin that this is the logout dialog // maybe we need a dedicated type for it? m_shellSurface->setPosition(geometry().topLeft()); } + +void KSMSwitchUserDialog::ungrab() +{ + setKeyboardGrabEnabled(false); +} diff --git a/ksmserver/switchuserdialog.h b/ksmserver/switchuserdialog.h index 958cdd79d..d5b9145fa 100644 --- a/ksmserver/switchuserdialog.h +++ b/ksmserver/switchuserdialog.h @@ -1,63 +1,66 @@ /* * Copyright 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ #ifndef SWITCHUSERDIALOG_H #define SWITCHUSERDIALOG_H #include class KDisplayManager; namespace KWayland { namespace Client { class PlasmaShell; class PlasmaShellSurface; } } class KSMSwitchUserDialog : public QQuickView { Q_OBJECT public: explicit KSMSwitchUserDialog(KDisplayManager *dm, KWayland::Client::PlasmaShell *plasmaShell = nullptr, QWindow *parent = nullptr); ~KSMSwitchUserDialog() override = default; void init(); signals: void dismissed(); protected: bool event(QEvent *e) override; +private slots: + void ungrab(); + private: void setupWaylandIntegration(); KDisplayManager *m_displayManager = nullptr; KWayland::Client::PlasmaShell *m_waylandPlasmaShell; KWayland::Client::PlasmaShellSurface *m_shellSurface = nullptr; }; #endif // SWITCHUSERDIALOG_H diff --git a/lookandfeel/contents/userswitcher/UserSwitcher.qml b/lookandfeel/contents/userswitcher/UserSwitcher.qml index 265ddec57..7d1295295 100644 --- a/lookandfeel/contents/userswitcher/UserSwitcher.qml +++ b/lookandfeel/contents/userswitcher/UserSwitcher.qml @@ -1,103 +1,106 @@ /*************************************************************************** * 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 + signal ungrab 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() + + onAboutToLockScreen: root.ungrab() } Controls.Action { onTriggered: root.dismissed() shortcut: "Escape" } Clock { anchors.bottom: parent.verticalCenter anchors.bottomMargin: units.gridUnit * 13 anchors.horizontalCenter: parent.horizontalCenter } MouseArea { anchors.fill: parent onClicked: root.dismissed() } 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 } } } } }