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.
553 lines
19 KiB
553 lines
19 KiB
/******************************************************************** |
|
KSld - the KDE Screenlocker Daemon |
|
This file is part of the KDE project. |
|
|
|
Copyright (C) 2004 Chris Howells <howells@kde.org> |
|
Copyright (C) 2011 Martin Gräßlin <mgraesslin@kde.org> |
|
|
|
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 <http://www.gnu.org/licenses/>. |
|
*********************************************************************/ |
|
#include "greeterapp.h" |
|
#include "kscreensaversettings.h" |
|
#include "authenticator.h" |
|
#include "noaccessnetworkaccessmanagerfactory.h" |
|
|
|
// KDE |
|
#include <KAuthorized> |
|
#include <KCrash> |
|
#include <kdeclarative/kdeclarative.h> |
|
#include <KDeclarative/KQuickAddons/QuickViewSharedEngine> |
|
#include <KUser> |
|
#include <KWindowSystem> |
|
#include <Solid/PowerManagement> |
|
//Plasma |
|
#include <KPackage/Package> |
|
#include <KPackage/PackageStructure> |
|
#include <KPackage/PackageLoader> |
|
// KWayland |
|
#include <KWayland/Client/connection_thread.h> |
|
#include <KWayland/Client/event_queue.h> |
|
#include <KWayland/Client/registry.h> |
|
// Qt |
|
#include <QAbstractNativeEventFilter> |
|
#include <QtCore/QTimer> |
|
#include <QtGui/QKeyEvent> |
|
#include <qscreen.h> |
|
#include <QThread> |
|
|
|
#include <QQuickView> |
|
#include <QQuickItem> |
|
#include <QQmlContext> |
|
#include <QQmlEngine> |
|
#include <QQmlProperty> |
|
|
|
#include <QX11Info> |
|
// Wayland |
|
#include <wayland-client.h> |
|
#include <wayland-ksld-client-protocol.h> |
|
// X11 |
|
#include <X11/Xatom.h> |
|
#include <X11/Xlib.h> |
|
#include <fixx11h.h> |
|
// |
|
#include <xcb/xcb.h> |
|
|
|
// this is usable to fake a "screensaver" installation for testing |
|
// *must* be "0" for every public commit! |
|
#define TEST_SCREENSAVER 0 |
|
|
|
namespace ScreenLocker |
|
{ |
|
|
|
class FocusOutEventFilter : public QAbstractNativeEventFilter |
|
{ |
|
public: |
|
bool nativeEventFilter(const QByteArray &eventType, void *message, long int *result) override { |
|
Q_UNUSED(result) |
|
if (qstrcmp(eventType, "xcb_generic_event_t") != 0) { |
|
return false; |
|
} |
|
xcb_generic_event_t *event = reinterpret_cast<xcb_generic_event_t*>(message); |
|
if ((event->response_type & ~0x80) == XCB_FOCUS_OUT) { |
|
return true; |
|
} |
|
return false; |
|
} |
|
}; |
|
|
|
// App |
|
UnlockApp::UnlockApp(int &argc, char **argv) |
|
: QGuiApplication(argc, argv) |
|
, m_resetRequestIgnoreTimer(new QTimer(this)) |
|
, m_delayedLockTimer(0) |
|
, m_testing(false) |
|
, m_ignoreRequests(false) |
|
, m_immediateLock(false) |
|
, m_authenticator(new Authenticator(this)) |
|
, m_graceTime(0) |
|
, m_noLock(false) |
|
{ |
|
connect(m_authenticator, &Authenticator::succeeded, this, &QCoreApplication::quit); |
|
initialize(); |
|
connect(this, &UnlockApp::screenAdded, this, &UnlockApp::desktopResized); |
|
if (QX11Info::isPlatformX11()) { |
|
installNativeEventFilter(new FocusOutEventFilter); |
|
} |
|
} |
|
|
|
UnlockApp::~UnlockApp() |
|
{ |
|
qDeleteAll(m_views); |
|
|
|
if (m_ksldInterface) { |
|
org_kde_ksld_destroy(m_ksldInterface); |
|
} |
|
if (m_ksldRegistry) { |
|
delete m_ksldRegistry; |
|
} |
|
if (m_ksldConnection) { |
|
m_ksldConnection->deleteLater(); |
|
m_ksldConnectionThread->quit(); |
|
m_ksldConnectionThread->wait(); |
|
} |
|
} |
|
|
|
void UnlockApp::initialize() |
|
{ |
|
// set up the request ignore timeout, so that multiple requests to sleep/suspend/shutdown |
|
// are not processed in quick (and confusing) succession) |
|
m_resetRequestIgnoreTimer->setSingleShot(true); |
|
m_resetRequestIgnoreTimer->setInterval(2000); |
|
connect(m_resetRequestIgnoreTimer, &QTimer::timeout, this, &UnlockApp::resetRequestIgnore); |
|
|
|
// disable DrKonqi as the crash dialog blocks the restart of the locker |
|
KCrash::setDrKonqiEnabled(false); |
|
|
|
KScreenSaverSettings::self()->load(); |
|
KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel")); |
|
KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), "KDE"); |
|
const QString packageName = cg.readEntry("LookAndFeelPackage", QString()); |
|
if (!packageName.isEmpty()) { |
|
package.setPath(packageName); |
|
} |
|
if (!KScreenSaverSettings::theme().isEmpty()) { |
|
package.setPath(KScreenSaverSettings::theme()); |
|
} |
|
|
|
m_mainQmlPath = QUrl::fromLocalFile(package.filePath("lockscreenmainscript")); |
|
|
|
installEventFilter(this); |
|
} |
|
|
|
void UnlockApp::desktopResized() |
|
{ |
|
const int nScreens = screens().count(); |
|
// remove useless views and savers |
|
while (m_views.count() > nScreens) { |
|
m_views.takeLast()->deleteLater(); |
|
} |
|
|
|
// extend views and savers to current demand |
|
const bool canLogout = KAuthorized::authorizeKAction(QStringLiteral("logout")) && KAuthorized::authorize(QStringLiteral("logout")); |
|
const QSet<Solid::PowerManagement::SleepState> spdMethods = Solid::PowerManagement::supportedSleepStates(); |
|
for (int i = m_views.count(); i < nScreens; ++i) { |
|
connect(QGuiApplication::screens()[i], &QObject::destroyed, this, &UnlockApp::desktopResized); |
|
// create the view |
|
auto *view = new KQuickAddons::QuickViewSharedEngine(); |
|
view->setColor(Qt::black); |
|
|
|
// first create KDeclarative, to be sure that it created a KIO Network Factory |
|
KDeclarative::KDeclarative declarative; |
|
declarative.setDeclarativeEngine(view->engine()); |
|
declarative.setupBindings(); |
|
// overwrite the factory set by kdeclarative |
|
auto oldFactory = view->engine()->networkAccessManagerFactory(); |
|
view->engine()->setNetworkAccessManagerFactory(nullptr); |
|
delete oldFactory; |
|
view->engine()->setNetworkAccessManagerFactory(new NoAccessNetworkAccessManagerFactory); |
|
|
|
if (!m_testing) { |
|
if (QX11Info::isPlatformX11()) { |
|
view->setFlags(Qt::X11BypassWindowManagerHint); |
|
} else { |
|
view->setFlags(Qt::FramelessWindowHint); |
|
} |
|
} |
|
|
|
if (m_ksldInterface) { |
|
view->create(); |
|
org_kde_ksld_x11window(m_ksldInterface, view->winId()); |
|
wl_display_flush(m_ksldConnection->display()); |
|
} |
|
|
|
// engine stuff |
|
QQmlContext* context = view->engine()->rootContext(); |
|
const KUser user; |
|
const QString fullName = user.property(KUser::FullName).toString(); |
|
|
|
context->setContextProperty(QStringLiteral("kscreenlocker_userName"), fullName.isEmpty() ? user.loginName() : fullName); |
|
context->setContextProperty(QStringLiteral("kscreenlocker_userImage"), user.faceIconPath()); |
|
context->setContextProperty(QStringLiteral("authenticator"), m_authenticator); |
|
context->setContextProperty(QStringLiteral("backgroundPath"), KScreenSaverSettings::themeBackground()); |
|
context->setContextProperty(QStringLiteral("org_kde_plasma_screenlocker_greeter_interfaceVersion"), 1); |
|
|
|
view->setSource(m_mainQmlPath); |
|
// on error, load the fallback lockscreen to not lock the user out of the system |
|
if (view->status() == QQmlComponent::Error) { |
|
static const QUrl fallbackUrl(QUrl(QStringLiteral("qrc:/fallbacktheme/LockScreen.qml"))); |
|
|
|
qWarning() << "Failed to load lockscreen QML, falling back to built-in locker"; |
|
m_mainQmlPath = fallbackUrl; |
|
view->setSource(fallbackUrl); |
|
} |
|
view->setResizeMode(KQuickAddons::QuickViewSharedEngine::SizeRootObjectToView); |
|
|
|
QQmlProperty lockProperty(view->rootObject(), QStringLiteral("locked")); |
|
lockProperty.write(m_immediateLock || (!m_noLock && !m_delayedLockTimer)); |
|
|
|
QQmlProperty sleepProperty(view->rootObject(), QStringLiteral("suspendToRamSupported")); |
|
sleepProperty.write(spdMethods.contains(Solid::PowerManagement::SuspendState)); |
|
if (spdMethods.contains(Solid::PowerManagement::SuspendState) && |
|
view->rootObject()->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("suspendToRam()").constData()) != -1) { |
|
connect(view->rootObject(), SIGNAL(suspendToRam()), SLOT(suspendToRam())); |
|
} |
|
|
|
QQmlProperty hibernateProperty(view->rootObject(), QStringLiteral("suspendToDiskSupported")); |
|
hibernateProperty.write(spdMethods.contains(Solid::PowerManagement::HibernateState)); |
|
if (spdMethods.contains(Solid::PowerManagement::HibernateState) && |
|
view->rootObject()->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("suspendToDisk()").constData()) != -1) { |
|
connect(view->rootObject(), SIGNAL(suspendToDisk()), SLOT(suspendToDisk())); |
|
} |
|
|
|
// verify that the engine's controller didn't change |
|
Q_ASSERT(dynamic_cast<NoAccessNetworkAccessManagerFactory*>(view->engine()->networkAccessManagerFactory())); |
|
|
|
m_views << view; |
|
} |
|
|
|
// update geometry of all views and savers |
|
for (int i = 0; i < nScreens; ++i) { |
|
auto *view = m_views.at(i); |
|
|
|
auto screen = QGuiApplication::screens()[i]; |
|
view->setGeometry(screen->geometry()); |
|
|
|
connect(screen, |
|
&QScreen::geometryChanged, |
|
view, |
|
static_cast<void (KQuickAddons::QuickViewSharedEngine::*)(const QRect&)>(&KQuickAddons::QuickViewSharedEngine::setGeometry) |
|
); |
|
|
|
if (m_testing) { |
|
view->show(); |
|
} else { |
|
view->showFullScreen(); |
|
} |
|
view->raise(); |
|
connect(view, &QQuickWindow::frameSwapped, this, &UnlockApp::markViewsAsVisible, Qt::QueuedConnection); |
|
} |
|
} |
|
|
|
void UnlockApp::markViewsAsVisible() |
|
{ |
|
auto *view = qobject_cast<KQuickAddons::QuickViewSharedEngine *>(sender()); |
|
disconnect(view, &QQuickWindow::frameSwapped, this, &UnlockApp::markViewsAsVisible); |
|
QQmlProperty showProperty(view->rootObject(), QStringLiteral("viewVisible")); |
|
showProperty.write(true); |
|
// random state update, actually rather required on init only |
|
QMetaObject::invokeMethod(this, "getFocus", Qt::QueuedConnection); |
|
} |
|
|
|
void UnlockApp::getFocus() |
|
{ |
|
if (m_views.isEmpty()) { |
|
return; |
|
} |
|
QWindow *w = 0; |
|
// this loop is required to make the qml/graphicsscene properly handle the shared keyboard input |
|
// ie. "type something into the box of every greeter" |
|
foreach (KQuickAddons::QuickViewSharedEngine *view, m_views) { |
|
view->requestActivate(); |
|
if (!m_testing) { |
|
view->setKeyboardGrabEnabled(true); // TODO - check whether this still works in master! |
|
} |
|
// w->setFocus(Qt::OtherFocusReason); // FIXME |
|
} |
|
// determine which window should actually be active and have the real input focus/grab |
|
// FIXME - QWidget::underMouse() |
|
// foreach (QQuickView *view, m_views) { |
|
// if (view->underMouse()) { |
|
// w = view; |
|
// break; |
|
// } |
|
// } |
|
if (!w) { // try harder |
|
foreach (KQuickAddons::QuickViewSharedEngine *view, m_views) { |
|
if (view->geometry().contains(QCursor::pos())) { |
|
w = view; |
|
break; |
|
} |
|
} |
|
} |
|
if (!w) { // fallback solution |
|
w = m_views.first(); |
|
} |
|
// activate window and grab input to be sure it really ends up there. |
|
// focus setting is still required for proper internal QWidget state (and eg. visual reflection) |
|
if (!m_testing) { |
|
w->setKeyboardGrabEnabled(true); // TODO - check whether this still works in master! |
|
} |
|
w->requestActivate(); |
|
// w->setFocus(Qt::OtherFocusReason); // FIXME |
|
} |
|
|
|
void UnlockApp::setLockedPropertyOnViews() |
|
{ |
|
delete m_delayedLockTimer; |
|
m_delayedLockTimer = 0; |
|
|
|
foreach (KQuickAddons::QuickViewSharedEngine *view, m_views) { |
|
QQmlProperty lockProperty(view->rootObject(), QStringLiteral("locked")); |
|
lockProperty.write(true); |
|
} |
|
} |
|
|
|
void UnlockApp::resetRequestIgnore() |
|
{ |
|
m_ignoreRequests = false; |
|
} |
|
|
|
void UnlockApp::suspendToRam() |
|
{ |
|
if (m_ignoreRequests) { |
|
return; |
|
} |
|
|
|
m_ignoreRequests = true; |
|
m_resetRequestIgnoreTimer->start(); |
|
|
|
Solid::PowerManagement::requestSleep(Solid::PowerManagement::SuspendState, 0, 0); |
|
|
|
} |
|
|
|
void UnlockApp::suspendToDisk() |
|
{ |
|
if (m_ignoreRequests) { |
|
return; |
|
} |
|
|
|
m_ignoreRequests = true; |
|
m_resetRequestIgnoreTimer->start(); |
|
|
|
Solid::PowerManagement::requestSleep(Solid::PowerManagement::HibernateState, 0, 0); |
|
} |
|
|
|
void UnlockApp::setTesting(bool enable) |
|
{ |
|
m_testing = enable; |
|
if (m_views.isEmpty()) { |
|
return; |
|
} |
|
if (enable) { |
|
// remove bypass window manager hint |
|
foreach (KQuickAddons::QuickViewSharedEngine * view, m_views) { |
|
view->setFlags(view->flags() & ~Qt::X11BypassWindowManagerHint); |
|
} |
|
} else { |
|
foreach (KQuickAddons::QuickViewSharedEngine * view, m_views) { |
|
view->setFlags(view->flags() | Qt::X11BypassWindowManagerHint); |
|
} |
|
} |
|
} |
|
|
|
void UnlockApp::setImmediateLock(bool immediate) |
|
{ |
|
m_immediateLock = immediate; |
|
} |
|
|
|
void UnlockApp::lockImmediately() |
|
{ |
|
setImmediateLock(true); |
|
setLockedPropertyOnViews(); |
|
} |
|
|
|
bool UnlockApp::eventFilter(QObject *obj, QEvent *event) |
|
{ |
|
if (obj != this && event->type() == QEvent::Show) { |
|
KQuickAddons::QuickViewSharedEngine *view(0); |
|
foreach (KQuickAddons::QuickViewSharedEngine *v, m_views) { |
|
if (v == obj) { |
|
view = v; |
|
break; |
|
} |
|
} |
|
if (view && view->winId() && QX11Info::isPlatformX11()) { |
|
// showing greeter view window, set property |
|
static Atom tag = XInternAtom(QX11Info::display(), "_KDE_SCREEN_LOCKER", False); |
|
XChangeProperty(QX11Info::display(), view->winId(), tag, tag, 32, PropModeReplace, 0, 0); |
|
} |
|
// no further processing |
|
return false; |
|
} |
|
|
|
if (event->type() == QEvent::KeyPress) { // react if saver is visible |
|
shareEvent(event, qobject_cast<KQuickAddons::QuickViewSharedEngine*>(obj)); |
|
return false; // we don't care |
|
} else if (event->type() == QEvent::KeyRelease) { // conditionally reshow the saver |
|
QKeyEvent *ke = static_cast<QKeyEvent *>(event); |
|
if (ke->key() != Qt::Key_Escape) { |
|
shareEvent(event, qobject_cast<KQuickAddons::QuickViewSharedEngine*>(obj)); |
|
return false; // irrelevant |
|
} |
|
return true; // don't pass |
|
} |
|
|
|
return false; |
|
} |
|
|
|
/* |
|
* This function forwards an event from one greeter window to all others |
|
* It's used to have the keyboard operate on all greeter windows (on every screen) |
|
* at once so that the user gets visual feedback on the screen he's looking at - |
|
* even if the focus is actually on a powered off screen. |
|
*/ |
|
|
|
void UnlockApp::shareEvent(QEvent *e, KQuickAddons::QuickViewSharedEngine *from) |
|
{ |
|
// from can be NULL any time (because the parameter is passed as qobject_cast) |
|
// m_views.contains(from) is atm. supposed to be true but required if any further |
|
// QQuickView are added (which are not part of m_views) |
|
// this makes "from" an optimization (nullptr check aversion) |
|
if (from && m_views.contains(from)) { |
|
// NOTICE any recursion in the event sharing will prevent authentication on multiscreen setups! |
|
// Any change in regarded event processing shall be tested thoroughly! |
|
removeEventFilter(this); // prevent recursion! |
|
const bool accepted = e->isAccepted(); // store state |
|
foreach (KQuickAddons::QuickViewSharedEngine *view, m_views) { |
|
if (view != from) { |
|
QCoreApplication::sendEvent(view, e); |
|
e->setAccepted(accepted); |
|
} |
|
} |
|
installEventFilter(this); |
|
} |
|
} |
|
|
|
void UnlockApp::setGraceTime(int milliseconds) |
|
{ |
|
m_graceTime = milliseconds; |
|
if (milliseconds < 0 || m_delayedLockTimer || m_noLock || m_immediateLock) { |
|
return; |
|
} |
|
m_delayedLockTimer = new QTimer(this); |
|
m_delayedLockTimer->setSingleShot(true); |
|
connect(m_delayedLockTimer, &QTimer::timeout, this, &UnlockApp::setLockedPropertyOnViews); |
|
m_delayedLockTimer->start(m_graceTime); |
|
} |
|
|
|
void UnlockApp::setNoLock(bool noLock) |
|
{ |
|
m_noLock = noLock; |
|
} |
|
|
|
|
|
static void osdProgress(void *data, org_kde_ksld *org_kde_ksld, const char *icon, int32_t percent, const char *text) |
|
{ |
|
Q_UNUSED(org_kde_ksld) |
|
reinterpret_cast<UnlockApp*>(data)->osdProgress(QString::fromUtf8(icon), percent, QString::fromUtf8(text)); |
|
} |
|
|
|
static void osdText(void *data, org_kde_ksld *org_kde_ksld, const char *icon, const char *text) |
|
{ |
|
Q_UNUSED(org_kde_ksld) |
|
reinterpret_cast<UnlockApp*>(data)->osdText(QString::fromUtf8(icon), QString::fromUtf8(text)); |
|
} |
|
|
|
static const struct org_kde_ksld_listener s_listener { |
|
osdProgress, |
|
osdText |
|
}; |
|
|
|
void UnlockApp::setKsldSocket(int socket) |
|
{ |
|
using namespace KWayland::Client; |
|
m_ksldConnection = new ConnectionThread; |
|
m_ksldConnection->setSocketFd(socket); |
|
|
|
m_ksldRegistry = new Registry(); |
|
EventQueue *queue = new EventQueue(m_ksldRegistry); |
|
|
|
connect(m_ksldRegistry, &Registry::interfaceAnnounced, this, |
|
[this, queue] (QByteArray interface, quint32 name, quint32 version) { |
|
if (interface != QByteArrayLiteral("org_kde_ksld")) { |
|
return; |
|
} |
|
m_ksldInterface = reinterpret_cast<org_kde_ksld*>(wl_registry_bind(*m_ksldRegistry, name, &org_kde_ksld_interface, version)); |
|
queue->addProxy(m_ksldInterface); |
|
if (version >= 2) { |
|
org_kde_ksld_add_listener(m_ksldInterface, &s_listener, this); |
|
} |
|
for (auto v : m_views) { |
|
org_kde_ksld_x11window(m_ksldInterface, v->winId()); |
|
wl_display_flush(m_ksldConnection->display()); |
|
} |
|
} |
|
); |
|
|
|
connect(m_ksldConnection, &ConnectionThread::connected, this, |
|
[this, queue] { |
|
m_ksldRegistry->create(m_ksldConnection); |
|
queue->setup(m_ksldConnection); |
|
m_ksldRegistry->setEventQueue(queue); |
|
m_ksldRegistry->setup(); |
|
wl_display_flush(m_ksldConnection->display()); |
|
}, Qt::QueuedConnection); |
|
|
|
m_ksldConnectionThread = new QThread(this); |
|
m_ksldConnection->moveToThread(m_ksldConnectionThread); |
|
m_ksldConnectionThread->start(); |
|
m_ksldConnection->initConnection(); |
|
} |
|
|
|
void UnlockApp::osdProgress(const QString &icon, int percent, const QString &additionalText) |
|
{ |
|
for (auto v : m_views) { |
|
auto osd = v->rootObject()->findChild<QQuickItem*>(QStringLiteral("onScreenDisplay")); |
|
if (!osd) { |
|
continue; |
|
} |
|
osd->setProperty("osdValue", percent); |
|
osd->setProperty("osdAdditionalText", additionalText); |
|
osd->setProperty("showingProgress", true); |
|
osd->setProperty("icon", icon); |
|
QMetaObject::invokeMethod(osd, "show"); |
|
} |
|
} |
|
|
|
void UnlockApp::osdText(const QString &icon, const QString &additionalText) |
|
{ |
|
for (auto v : m_views) { |
|
auto osd = v->rootObject()->findChild<QQuickItem*>(QStringLiteral("onScreenDisplay")); |
|
if (!osd) { |
|
continue; |
|
} |
|
osd->setProperty("showingProgress", false); |
|
osd->setProperty("osdValue", additionalText); |
|
osd->setProperty("icon", icon); |
|
QMetaObject::invokeMethod(osd, "show"); |
|
} |
|
} |
|
|
|
} // namespace |
|
|
|
|